@vladbasin/strong-api-mapping
v1.1.9
Published
Strongly typed API models. Mapping & validation
Downloads
314
Readme
Strong API mapping
Strongly typed API models. Mapping & validation. Use meaningful decorators. Don't repeat yourself.
Installation
npm
npm install reflect-metadata @vladbasin/strong-api-mapping
yarn
yarn add reflect-metadata @vladbasin/strong-api-mapping
Usage
This is a generic mapping library. You can integrate it with any HTTP stack. Currently the following packages use this library:
- @vladbasin/strong-api-middleware - generic middleware library which can integrate with any HTTP stack, by introducing additional layers of request/response pipeline and with help of this library can support any HTTP stack.
- @vladbasin/strong-api-middleware-aws-lambda - complete e2e HTTP pipeline integration for AWS Lambda
- @vladbasin/strong-api-middleware-express - IN PROGRESS - complete e2e HTTP pipeline integration for express server
- @vladbasin/strong-api-client - HTTP client which can use shared models to execute request. Therefore, no need to duplicate mapping & validation logic for API consumers & producers.
Step-by-step guide
- Import
reflect-metadata
ONCE in your index file:
import 'reflect-metadata';
- Define your model
import { body, header, path, query } from '@vladbasin/strong-api-mapping';
export class RequestPayload {
@path()
public userId!: number;
@path({ key: 'userId' })
public id!: number;
@query()
public name!: string;
@query()
public isAdmin!: boolean;
@query({ key: 'lastname' })
public surname!: string;
@query({ parser: String })
public cars!: string[];
@query({ parser: Number })
public cash!: number[];
@body()
public details!: DetailsType;
@header({ key: 'Content-Type' })
public contentType!: string;
@header({ key: 'X-Info', parser: String })
public info!: string[];
}
- Define validation rules with
Joi
export const RequestPayloadSchema = Joi.object<RequestPayload>({
surname: Joi.string().min(10),
cars: Joi.array().max(3),
// other rules for field content...
});
- Prepare
RawApiRequest
for mapping. For example, @vladbasin/strong-api-middleware-aws-lambda already does it for you for AWS Lambda. But you can create your own middleware for your stack and use this library to do mapping & validation for you.
// represents Query, Header, Route and Body values for HTTP request
export type RawApiRequestType = {
queryParams?: MaybeNullable<Record<string, Maybe<string>>>;
multiValueQueryParams?: MaybeNullable<Record<string, Maybe<string[]>>>;
pathParams?: MaybeNullable<Record<string, Maybe<string>>>;
headers?: MaybeNullable<Record<string, Maybe<string>>>;
multiValueHeaders?: MaybeNullable<Record<string, Maybe<string[]>>>;
body?: MaybeNullable<string>;
};
- Call the following methods to map HTTP request to your model and vice versa with DRY principle
// maps RawApiRequest to Model (RequestPayload) and validates it (throws `CodedError` with information if model is not valid)
mapRawApiRequestToPayload<RequestPayload>({
rawApiRequest,
PayloadConstructor: RequestPayload,
schema: RequestPayloadSchema,
});
// maps Model (RequestPayload) to RawApiRequest
mapPayloadToRawApiRequest(requestPayload);
Also applicable to response models (mapRawApiResponseToPayload()
, mapPayloadToRawApiResponse()
).
In case validation fails, the library throws CodedError
instance with information which properties are not valid:
{
message: 'ValidationError: "surname" is required'
code: 'ValidationFailed',
errors: [ { code: 'surname', message: 'any.required' } ],
}
Thus, you can share request/response models with your API consumers, so they don't need to repeat the same mapping & validation logic. See: @vladbasin/strong-api-client
Custom decorator/mapping
You can also specify custom mappings for custom decorator:
- Define custom decorator
import { defineDecorator, ParserType } from '@vladbasin/strong-api-mapping';
export const context = (options: { key?: string; parser?: ParserType }): PropertyDecorator =>
defineDecorator({
source: 'context',
useKey: true,
isKeyCaseSensitive: false,
key: options.key,
parser: options.parser,
isCustom: true,
});
- Specify data for custom context
mapRawApiRequestToPayload<RequestPayload>({
rawApiRequest,
PayloadConstructor: RequestPayload,
schema: RequestPayloadSchema,
customApiRequestData: {
context: {
customKey: 'customValue',
//...
},
},
});