1.1.0 • Published 2 years ago

openapi-ts-backend v1.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

openapi-ts-backend

Enables easy implementions of OpenAPI REST APIs in TypeScript with full typings of schemas and operations.

Note: This module is deprecated and replaced by https://www.npmjs.com/package/@openapi-ts/backend which separates the core module from service bindings for AWS Lambda and Express.

Introduction

This module allows for simple REST API implementation in TypeScript using OpenAPI 3.0 specifications. It can be easily integrated with any HTTP framework such as Express, AWS Lambda etc. A connector for AWS Lambda is provided within this module.

The module uses the excellent https://www.npmjs.com/package/openapi-backend module for routing and validation, and adds some useful features on top:

  • Executable for generating TypeScript types for all schemas and operations. This is built on top of https://www.npmjs.com/package/openapi-typescript, and adds full types for all the operations specified in the API.
  • Typed requests, responses etc, with headers and other parameters being coerced to fit the API schemas.
  • Interceptors ("middleware") support
  • Support for multiple OpenAPI specifications, mounted at different root paths.
  • Simplified authorization
  • Customizable response validation and trimming
  • Simple error handling and customizable error response formatting

Installation

$ npm install openapi-ts-backend

Usage

Simple Hello World API example:

Create API:

const api = new OpenApi()
  .register({
    definition: './greet-api.yml', // JSON or YAML
    operations: {
      // map of specification operationIds to handler functions
      greet: req => {
        return `Hello, ${req.params.name}!`;
      }, 
      ... 
    }
  });

Invoke API:

const res = await api.handleRequest({
  method: 'GET',
  path: '/greet/world',
  headers: {},
});

Generating types for APIs

Consider this example API:

greet-api.yml

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Greet API
components:
  securitySchemes:
    AccessToken:
      type: oauth2
      description: 'Validates a bearer token'
      flows:
        password:
          tokenUrl: 'https://api.example.com/oauth/token'
          scopes:
            full: Full access
            some: Some access
  schemas:
    Title:
      type: string
      enum:
        - Mr
        - Mrs
        - Miss
    Person:
      type: object
      description: A person
      required:
        - name
      properties:
        name:
          type: string
        title:
          $ref: '#/components/schemas/Title'
        photo:
          type: string
          format: byte
    Greeting:
      type: object
      required:
        - message
      properties:
        message:
          type: string
paths:
  /greet/{name}:
    get:
      operationId: greet
      summary: Greet the caller
      description: This greets the caller
      security:
        - AccessToken:
            - some
            - full
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
        - in: query
          name: title
          schema:
            $ref: '#/components/schemas/Title'
          description: Bar
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'

To generate TypeScript types for all requests and operation handlers, an executable is provided:

$ npx openapi-ts-backend generate-types greet-api.yml src/gen/greet-api
...
Types written to src/gen/greet-api

Now, types for all schemas and operations are generated in src/gen/greet-api, and a backend service can be set up type safely.

Example AWS Lambda API with typed operations

src/api.ts

import {LambdaOpenApi} from 'openapi-ts-backend';
import * as GreetApi from './gen/greet-api';

const operations: GreetApi.OperationHandlers<LambdaSource> = {
  greet: (req, res, params) => {
    const {params: {name}, query: {title}} = req; 

    // All request data is typed:
    // * req.params is {name: string}
    // * req.query is {title?: 'Mr' | 'Mrs' | 'Miss'}.
    // * Response body is {message: string} | void
    //   (all handlers may return void and mutate res instead)

    return {
      message: `Hello, ${title ? title + ' ' : ''}${name}!`;
    }
  }
};

const api = new LambdaOpenApi()
    .intercept(((req, res, params) => {
      console.log(`Event:`, params.data.lambda.event);
    }))
    .register({
      definition: './greet-api.yml',
      operations,
      path: '/'
    });
    
export default api.eventHandler();

The OperationHandlers interface contains all the operations specified by the OpenAPI specification, each one with typings for headers, path params, query params, cookies, bodies and responses, making it really easy to implement the API using TypeScript.

For simplicity, each operation may return a response body, or the provided res object may be mutated instead. If the response object is not mutated and a body is returned, the response will use the returned value as the body. The response status code is set by modifying res.statusCode, however as a convenience it may be omitted if the operation only has a single defined successful status code. If statusCode is not set and there are multiple successful status codes defined for the operation, a HTTP 500 error will be thrown.

Requests are always validated, and headers, path parameters, query parameters and cookies are parsed an coerced to the operation schema. If a request does not match the operation schema, a HTTP 400 response is returned.

Responses are by default validated with errors simply logged. By using responseValidationStrategy: 'throw', invalid responses will instead render HTTP 500 errors.

All error responses are customizable by supplying a custom errorHandler which maps thrown Error objects to HTTP responses.

Each handler is invoked with a params object containing API data such as the operation, authorizations etc. Custom data can be provided in params by supplying them to handleRequest and by specifying a custom type parameter to the OpenApi constructor:

const api = new OpenApi<MyCustomData>().register(...);
api.handleRequest(req, myCustomData);

The myCustomData object will now be present in each handler as params.data.

Interceptors

Interceptors are functions invoked on every request, similar to Express middleware. Note that interceptors are invoked before requests are parsed and routed.

Authorizers

Authorizers are functions implementing a security scheme as defined in the OpenAPI specification. An authorizer function is called with the parsed request as input together with the scopes required for the current operation (if applicable to the security scheme). The authorizer should either return some data, such as a session or user object, or throw an error. If multiple security requirements are provided for an operation, only one must be successful.