Skip to content

blackflux/lambda-serverless-api

Repository files navigation

Rest Abstraction for Serverless API

Build Status Test Coverage Dependabot Status Dependencies NPM Downloads Semantic-Release Gardener

Middleware for AWS Lambda and Api Gateway.

Provides support for:

  • Fully customizable parameter types
  • Support for header, json, query and context, parameter positions
  • Abstraction for text, json, and binary response types
  • Full support for custom authentication
  • Full support for cors
  • Full support for versioning and deprecation
  • Automatic generation of Swagger documentation
  • Rate limiting using in-memory or S3
  • Access and error logging using lambda-monitor

Install

$ npm install --save lambda-serverless-api

Getting Started

Define api and handlers in handler.js as follows:

import { Api } from 'lambda-serverless-api';

const api = Api({/* options */});

api.wrap('POST register', [
  api.Str('name', 'json', { required: false }),
  api.Email('email', 'json'),
  api.Str('password', 'json')
], /* options, */ ({ name, email, password }) => {
  // handle registration logic here ...
  if (new Date().getHours() === 4) {
    throw api.ApiError('I am a teapot', 418);
  }
  return api.JsonResponse({ message: 'Success!' });
});

export default api;

then hook the router endpoint into the Serverless Framework configuration as

functions:
  router:
    handler: handler.router
    events:
      - schedule: rate(10 minutes)
      - http:
          path: /{proxy+}
          method: ANY

Api Options

The api is plugin based and all options are tied to plugins. The following plugins are customizable:

  • cors: Used to handle CORS Option requests as well as injecting CORS headers into responses
  • versioning: Used to declare valid api version and handle deprecation
  • logger: Log responses to console (CloudWatch), which can then be picked up by lambda-monitor
  • preValidation: Hook to allow pre-validation (e.g. authentication)
  • rateLimit: Used for rate limiting
  • router: Used to modify the router, e.g. set a custom route prefix
  • robots: Used to modify robots.txt response
  • responseHeaders: Used to inject certain predefined response headers, such as Date
  • preLogic: Used to e.g. modify parameters after they are parsed

Please see implementation for details.

Endpoint Definition: wrap()

Takes the following positional arguments:

  • route string: The method and the uri of the format ${method} ${uri}
  • params Array<Parameter>: Parameter definitions
  • options: Endpoint options (optional)
  • handler function: The handler using parsed parameters as the first argument

Note: The (slightly modified) original event and context can be accessed as additional handler parameters

Api Parameters

There are various pre-defined parameters available

  • Bool: Boolean input
  • IsoDate: Date input as YYYY-MM-DD
  • Email: Email input
  • Enum: Enum input
  • FieldsParam: String input that gets parsed as object-fields
  • GeoPoint: GPS coordinate input
  • GeoPoly: GPS coordinate of polygon input and holes
  • GeoPolyList: List of GPS coordinate of polygon input and holes
  • GeoRect: GPS coordinate rectangle input
  • GeoShape: GPS coordinate polygon input
  • GeoShapeList: GPS coordinate polygon list input
  • Int: Integer input
  • IntShort: Short Integer input
  • IsoTimestamp: Iso timestamp input
  • Json: Json input that has to conform to Joi schema
  • Json-list: List of Json input that has to conform to Joi schema
  • List: List input
  • NumberList: Number list input
  • NumberParam: Number input
  • Regex: String input that has to conform to a regex
  • Str: String input
  • StrList: String list input
  • UUID: Uuid input

Parameter Types

There are four types of parameter types:

  • json: parsed from the json request body
  • query: parsed from the query string
  • header: parsed from the header and case insensitive
  • context: parsed from the lambda event context. Useful to e.g. obtain the client IP

Parameter Options

Below are the default parameter options. Most parameters have several customization options. Please see implementation for details.

required

Allow input to be not present

Type: boolean
Default: true

nullable

Allow input value to be null.
Note: Parameter position must be in [json, context]

Type: boolean
Default: false

getter

Experimental

Note: only recommended for advanced use cases.
Optionally asynchronous custom "getting" of variables.
Getter function takes raw input from event, IE a query parameter will always pass String values into the getter function.
Warnings:

  • If used with { nullable: true }, if a rawInput is passed as null, or if a non-required parameter is not sent, the getter function will not be used.
  • Some params (such as Bool, Int, Json, etc) do extra processing after the getter function has returned, and may return inconsistent results. Thorough testing is recommended.

Takes unresolved parameters as second parameter. Simple parameters can be accessed directly, unresolved can be resolved by using await.

Type: Function
Default: null

Example

/* { "name": "  John   "} */
export default api.wrap('POST name', [
  api.Str('name', 'json', true, { getter: (input, params) => input.trim() })
], ({ name }) => {
  console.log(name); // "John"
});

Parameter Names

Parameter names are converted to camel case when passed into the handler.

E.g. the header X-Custom-Header would be passed as xCustomHeader.

Api Response

There are two types of api responses: Success and error responses. Error responses need to be thrown, while success responses need to be returned from the endpoint handler. The following responses are available:

  • Success: ApiResponse (e.g. for plain text), JsonResponse (for json), BinaryResponse (e.g. for images)
  • Failure: ApiError

Success responses take the positional parameters data (required), statusCode (optional) and headers (optional).

Error responses take the positional parameters message (required), statusCode (optional), messageId (optional) and context (optional). They are always of Json format. It is recommended that an easily identifiable messageId is set on all error responses.

Please see implementation for details.

Rate Limiting

Done globally through the API configuration. However can be overwritten on a per-endpoint basis by specifying limit as an integer in the endpoint definition option.

Rate limiting uses memory by default, which is not shared across function containers. However one can configure S3 to share the rate limiting (recommended). This will (1) increase latency and (2) increase costs per api call.

By default the client IP (['requestContext.identity.sourceIp']) is used for rate limiting. However this can be customized.

General Notes

Each endpoint definition can also be exposed as a separate lambda function. However this is not recommended for larger APIs due to CloudFormation limitations (maximum number of resources and stack invalidation issues).

Consider using yaml-boost for loading Serverless Framework configuration.

Consider using a single router function instead of declaring each function individually.

Swagger Documentation

To generate swagger documentation we can call api.generateSwagger() after the api is initialized with routes. For merge-overwrite the swagger definition fs.smartWrite() can be used.

Example

import path from 'path';
import fs from 'smart-fs';

const updateSwagger = async () => {
  const swaggerFile = path.join(fs.dirname(import.meta.url), '..', 'swagger.yml');
  const swaggerContent = await api.generateSwagger();
  const result = fs.smartWrite(swaggerFile, swaggerContent);
  expect(result, 'Swagger file updated').to.equal(false);
};
updateSwagger();

Logging Api Errors / Exceptions

To monitor api errors and exceptions, lambda-monitor should be configured with rollbar.