@vladbasin/strong-api-mapping

Strongly typed API models. Mapping & validation. Use meaningful decorators. Don't repeat yourself.

Usage no npm install needed!

<script type="module">
  import vladbasinStrongApiMapping from 'https://cdn.skypack.dev/@vladbasin/strong-api-mapping';
</script>

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:

Step-by-step guide

  1. Import reflect-metadata ONCE in your index file:
import 'reflect-metadata';
  1. 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[];
}
  1. 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...
});
  1. 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>;
};
  1. 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(rawApiRequest, RequestPayload, 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