JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 71
  • Score
    100M100P100Q80042F
  • License GPL-3.0-only

Strongly typed API models. Mapping & validation

Package Exports

  • @vladbasin/strong-api-mapping
  • @vladbasin/strong-api-mapping/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@vladbasin/strong-api-mapping) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

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<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:

  1. 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,
    });
  1. Specify data for custom context
mapRawApiRequestToPayload<RequestPayload>({
    rawApiRequest,
    PayloadConstructor: RequestPayload,
    schema: RequestPayloadSchema,
    customApiRequestData: {
        context: {
            customKey: 'customValue',
            //...
        },
    },
});