API Lift
Create a ready-to-go express router for a REST API with filters, input validation, output checking, versioning, automatic routes and logging.
See on npm
Install
npm install api-lift
Important note
This module is built on top of express and lift-it and is meant to be very opinionated.
Status
Stable, missing some docs
Assumptions
This module is locked down by some strong assumptions:
- The routes are created and named after the files in the file system
- The endpoints are to be implemented as supported by lift-it. See details in section "Endpoint handler"
- Works with REST pattern, generating the following routes for each http method:
- DELETE /resource/id -> call resource/rest_delete.js
- GET /resource -> call resource/rest_list.js
- GET /resource/id -> call resource/rest_read.js
- PUT /resource/id -> call resource/rest_update.js
- POST /resource -> call resource/rest_create.js
- POST /resource/id/action -> call resource/action.js
- Query params (in GET method), resource id (from url) and header (apiKey if hasApiKey, Authorization if hasOAuth) will be added to json body in the endpoints calls
- Standard output for success:
{failure:null}
, for error: {failure:{code:Number,message:String}}
Features
Okay, after complying to all the rules outlined above, you get:
- Filters, input validation, output checking and profiling (by
lift-it
) - Versioning: don't break old consumers and yet let the API evolve
- Logging: simple logging interface to connect to any logging solution (like log-sink)
Options
let apiLift = require('api-lift')
let api = apiLift({
folder: './api',
profile: false,
errorClass: apiLift.APIError,
enableErrorCode: true,
plugins: [],
validate: {
},
validateOutput: {
direction: 'output',
exportName: 'outFields',
optional: true,
getDefaultValue: function () {
return {}
},
code: 100,
errorHandler: function (action, value, err) {
throw err
},
options: {
strict: true
}
},
filters: './filters',
bodyParser: {},
minVersion: 1,
dataScrub: [/session|password|serial|token/i],
isRest: true,
hasApiKeyAuth: false
hasOAuth: true
checkId: function(x){
},
callToJSON: function (x) {
return x.toJSON()
},
onsuccess: function (response, runInfo, body, endpoint) {
},
onfailure: function (response, runInfo, body, endpoint, error) {
},
timeout: 30e3,
ontimeout: function (runInfo, body, endpoint) {
},
openApi: {
serve: false,
serveAs: 'swagger.json',
middleware: function (req, res, next) {
next()
},
prepareEndpoint: function (endpoint, pathItem) {
return pathItem
},
prepareSpec: function (spec) {
return spec
}
}
})
let app = apiLift.express()
app.use('/api', api.router)
require('http').createServer(app).listen(80)
This module uses express
internally to create the router object. To avoid compatibility problems, it's adviced to use the same lib this module is using. This is exported as require('api-lift').express
The parameter body
given to onsuccess
and onfailure
has properties matching one of the regular expressions in dataScrub
scrubbed (even in deep objects and arrays). This is meant to make it log-safe. Example: {password: '123456'}
becomes {password: '[HIDDEN]'}
Returned value
The return of apiLift()
call is an instance of API
. Its properties are:
{express:Router} router
: an express Router instance{number} minVersion
: the minimum supported version{number} maxVersion
: the maximum supported version{Array<string>} versions
: The list of supported versions, ordered from oldest to newest. Example: ['v3', 'v4']
{Array<Endpoint>} endpoints
: the list of available endpoints{Object<Endpoint>} endpointByUrl
: a map from url to an Endpoint instance
If you are not interested in the router, but in the returned meta-data (like max version), use apiLift.info(options)
instead:
let apiLift = require('api-lift')
let info = apiLift.info({
folder: './api',
minVersion: 1
})
info.maxVersion
Generated Doc
All public methods and properties are described in the generated docs
Versioning
This module aims to make endpoint versioning very simple, pragmatic and source-control friendly. The system only cares about backwards-incompatible changes, that is, MAJOR changes (as defined by semantic versioning).
By default (options.minVersion
), all endpoints start at version 1. That is, a file in the path api/user/create.js
is served at the url /v1/user/create
. If a breaking change is to be made in this endpoint, the API version must be bumped to 2. To do this, the current file is copied to api/user/create-v1.js
and new changes can be freely applied to the current api/user/create.js
file. The new url will be /v2/user/create
and will be mapped to the current file. The old url will keep working and will point to the old v1 file. Any other endpoint that hasn't been changed will be served equally in v1 and v2. Magic!
Note that the v1 file is like a snapshot. From the point of view of a revision control system (like git), the file has evolved linearly: no move/rename or any other trick (like symlinks).
After some time, the support for version 1 may be dropped, by increasing the minVersion
option and removing old v1 files.
Complete example
For the following files in the api folder:
api
user
create.js
create-v2.js
findbyname-v1.js
getinfo.js
Assuming minVersion
is 1, those endpoints will be created:
/v1
/user/create -> api/user/create-v2.js
/user/findbyname -> api/user/findbyname-v1.js
/user/getinfo -> api/user/getinfo.js
/v2
/user/create -> api/user/create-v2.js
/user/getinfo -> api/user/getinfo.js
/v3
/user/create -> api/user/create.js
/user/getinfo -> api/user/getinfo.js
The file api/user/getinfo.js
is available in all versions. api/user/create-v2.js
is the snapshot for v1 and v2, api/user/create.js
is used in v3. api/user/findbyname-v1.js
is the snapshot for v1 only and is not available in next versions.
Run Info
While processing the request, process.domain.runInfo
is an express request instance. req.requestId
is a string, unique for each request. As a result, in any async process created by the request, process.domain.runInfo.requestId
can be used. Useful for logs, for example.
Logging
TODO
Endpoint handler
TODO
Body Limit
Each endpoint can set its own body size limit by setting the module.exports.bodyLimit
property (syntax from bytes). If it doesn't set this property, the limit will be the one defined in the bodyParser
field from options
.
Success
The default HTTP status code to a correct execution of success
is 200 Ok
.
If the success function has in its output
the property HTTPStatusCode
, this one is answered.
Note: HTTPStatusCode
is a private property and will be deleted after sent. Do not use it as a property in your output
.
Error codes
The generated erros respects the APIError class, this is, always have an internal code and a message. A HTTP status code is optional, and if not set the 500 Internal Server Error
is answered.
The APIError will always respect the information received from error
function.
The invalid path
errors are always 404 Not Found
and invalid content-type/json 400 Bad Request