Rest Abstraction for Serverless API
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:
const api = require('lambda-serverless-api').Api({});
api.wrap('POST register', [
api.Str('name', 'json', { required: false }),
api.Email('email', 'json'),
api.Str('password', 'json')
], ({ name, email, password }) => {
if (new Date().getHours() === 4) {
throw api.ApiError('I am a teapot', 418);
}
return api.JsonResponse({ message: 'Success!' });
});
module.exports = 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 bodyquery
: parsed from the query stringheader
: parsed from the header and case insensitivecontext
: 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
module.exports = api.wrap('POST name', [
api.Str('name', 'json', true, { getter: (input, params) => input.trim() })
], ({ name }) => {
console.log(name);
});
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
const fs = require('smart-fs');
const path = require('path');
const updateSwagger = async () => {
const swaggerFile = path.join(__dirname, '..', '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.