typescript-openapi-router

Multi purpose yet simple nodejs router with OpenAPI 3 compatibility for REST APIs


Keywords
nodejs, typescript, serverless, router
License
EUPL-1.2
Install
npm install typescript-openapi-router@0.1.37

Documentation

openapi-router

A (very opinionated, no dependency) NodeJS Typescript router that implements the OpenAPI 3 spec implemented with the serverless framework in mind.

Early stage development, not to be used in production apps.

See app implementation example here

Router components

  • Router
  • Validator
  • Class and method decorators for OpenAPI 3
  • Documentation generator

Router

Router is provided as a singleton and can be obtained by calling getRouter(). The router exposes two methods handleEvent and getApiDoc.

The handleEvent method receives a Request parameter (see bellow) that contains all the information needed to process the request.

export class Request<RequestBody = any> {
  headers?: { [key: string]: string };
  path: string;
  method: HttpMethod;
  body?: RequestBody;
  pathParams?: IPathParams | null;
  queryParams?: IQueryParams | null;
  ...
}

The handleEvent method will search for the correct handler to process the request.

Middleware

The router also supports optional middleware that can execute functions before and after the original handler (you can add a top level logging of the request and response for all handlers for example or alter the request or the response)

The @Route supports the middleware property which in turn has the before and after properties (which are arrays of BeforeMiddlewareHandler and AfterMiddlewareHandler)

The @ApiRouter supports global middleware. The global before functions will be executed before each handler's middleware functions. The global after functions will be executed after each handler's middleware functions. It's good for situations where you want to log/persist all the requests and all the responses.

Example

export const beforeAuth: BeforeMiddlewareHandler = async (request:Request<AuthRequest>): Promise<Request> => {
  if (request.body) {
    request.body.email = `before-${request.body.email}`;
  }
  return request;
};

export const afterAuth: AfterMiddlewareRequestHandler = async (response: Response<AuthResponse>): Promise<Response> => {
  if (response.body) {
    const body = response.getBody();
    if (body) {
      body.message = body.message + " after";
      response.setBody(body);
    }
  }
  return response;
};


@Route({
    ...
    middleware: {
      before: [
        beforeAuth
      ],
      after: [
        afterAuth
      ]
    }
   ...
  })

See here a more detailed implementation

Validator

The validator is used to validate the input and output. It is done automatically and relies on the OpenAPI 3 specs (see implementation, tests and example project). The validator returns common sense error messages like AuthRequest.email is required, UserDetails.lastName should be of type string etc

Decorators

There are two decorators defined DocMetadata which defines the documentation info and global settings and Route which registers a method with the router and defines the behaviour and documentation for a route.

DocMetadata example

@DocMetadata({
  version: "1.0.0",
  title: "A test app",
  description: "Exactly as the name suggests",
  contact: {
    name: "Vali Draganescu",
    email: "vali.draganescu88@gmail.com",
    url: "https://technoloid.com"
  },
  termsOfService: "https://technoloid.com/terms",
  license: {
    name: "DWETFYW | Do what ever the fuck you want",
    url: "https://technoloid.com/license"
  },
  securitySchemes: {
    ApiKeyAuth: {
      type: "apiKey",
      in: "header",
      name: "X-API-Key"
    },
    BearerAuth: {
      type: "http",
      scheme: "bearer"
    }
  },
  security: [
    {
      ApiKeyAuth: []
    },
    {
      BearerAuth: []
    }
  ],
  servers: [{
    url: "localhost:8080",
    description: "localhost"
  }, {
    url: "https://dev-api.test.com",
    description: "Development"
  }, {
    url: "https://api.test.com",
    description: "Production"
  }],
  globalResponses: [
    {
      statusCode: 500,
      description: "Internal server error",
      body: ErrorResponse
    }
  ]
})

Route example

@Route({
    description: "simple method, just says hello to the user",
    summary: "This is the summary",
    method: HttpMethod.GET,
    path: "/hello",
    responses: [{
      statusCode: 200,
      description: "hello response",
      body: HelloResponse
    }]
  })

Documentation generator

The router also defines the method getApiDoc which returns an OpenAPI 3 standard json documentation based on the DocMetadata and Routes defined in the API implementation. The globalResponses from the DocMetadata can be used to define global responses for all routes. Think like 404, 401, 500 etc status messages. This responses will be added to any route that does not already define them.

Default headers for all API responses

{
    "Content-Type": "application/json",
    "Cache-Control": "private, max-age=0, no-cache, no-store, must-revalidate'",
    "Expires": "-1",
    "Pragma": "no-cache",
    "Access-Control-Expose-Headers": "X-Api-Version",
    "Access-Control-Allow-Origin" : "*",
    "Access-Control-Allow-Credentials": "true"
}

Can be overridden using one of the following methods

  addHeader = (key: string, value: string) => {
    this.headers[key] = value;
  };

  addHeaders = (headers: { [key: string]: string }) => {
    for (const [key, value] of Object.entries(headers)) {
      this.addHeader(key, value);
    }
  };

  setHeaders = (headers: { [key: string]: string }) => {
    this.headers = headers;
  };

Licence

License

Distributed under European Union Public License, version 1.2 (EUPL-1.2)