{"id":22779049,"url":"https://github.com/clubedaentrega/api-lift","last_synced_at":"2025-04-15T14:21:47.670Z","repository":{"id":26975752,"uuid":"30439338","full_name":"clubedaentrega/api-lift","owner":"clubedaentrega","description":"Create a ready-to-go express router for a REST API with filters, input validation, versioning, automatic routes and logging","archived":false,"fork":false,"pushed_at":"2018-12-21T17:25:23.000Z","size":721,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-28T21:11:25.357Z","etag":null,"topics":["express-router","framework","rest-api"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clubedaentrega.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-02-07T00:25:50.000Z","updated_at":"2018-12-21T17:25:25.000Z","dependencies_parsed_at":"2022-09-14T04:40:30.587Z","dependency_job_id":null,"html_url":"https://github.com/clubedaentrega/api-lift","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clubedaentrega%2Fapi-lift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clubedaentrega%2Fapi-lift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clubedaentrega%2Fapi-lift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clubedaentrega%2Fapi-lift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clubedaentrega","download_url":"https://codeload.github.com/clubedaentrega/api-lift/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249085452,"owners_count":21210270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express-router","framework","rest-api"],"created_at":"2024-12-11T20:07:38.358Z","updated_at":"2025-04-15T14:21:47.645Z","avatar_url":"https://github.com/clubedaentrega.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# API Lift\n[![Build Status](https://travis-ci.org/clubedaentrega/api-lift.svg?branch=master)](https://travis-ci.org/clubedaentrega/api-lift)\n[![Inline docs](https://inch-ci.org/github/clubedaentrega/api-lift.svg?branch=master)](https://inch-ci.org/github/clubedaentrega/api-lift)\n[![Dependency Status](https://david-dm.org/clubedaentrega/api-lift.svg)](https://david-dm.org/clubedaentrega/api-lift)\n\nCreate a ready-to-go express router for a REST API with filters, input validation, output checking, versioning, automatic routes and logging.\n\nSee on [npm](https://www.npmjs.com/package/api-lift)\n\n## Install\n`npm install api-lift`\n\n## Important note\nThis module is built on top of [express](https://www.npmjs.com/package/express) and [lift-it](https://www.npmjs.com/package/lift-it) and *is* meant to be very opinionated.\n\n## Status\nStable, missing some docs\n\n## Assumptions\nThis module is locked down by some strong assumptions:\n\n* The routes are created and named after the files in the file system\n* The endpoints are to be implemented as supported by [lift-it](https://www.npmjs.com/package/lift-it). See details in section \"Endpoint handler\"\n* Works with REST pattern, generating the following routes for each http method:\n\t* DELETE /resource/id -\u003e call resource/rest_delete.js\n\t* GET /resource -\u003e call resource/rest_list.js\n\t* GET /resource/id -\u003e call resource/rest_read.js\n\t* PUT /resource/id -\u003e call resource/rest_update.js\n\t* POST /resource -\u003e call resource/rest_create.js\n\t* POST /resource/id/action -\u003e call resource/action.js\n* 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\n* Standard output for success: `{failure:null}`, for error: `{failure:{code:Number,message:String}}`\n\n## Features\nOkay, after complying to all the rules outlined above, you get:\n\n* Filters, input validation, output checking and profiling (by `lift-it`)\n* Versioning: don't break old consumers and yet let the API evolve\n* Logging: simple logging interface to connect to any logging solution (like [log-sink](https://www.npmjs.com/package/log-sink))\n\n## Options\n```js\nlet apiLift = require('api-lift')\n\nlet api = apiLift({\n\t// All options are optional :P\n\t// The default values are described bellow\n\t// Some are not configurable and can not be changed,\n\t// those are listed only for your information\n\t\n\t// Options for `lift-it`\n\tfolder: './api',\n\tprofile: false,\n\terrorClass: apiLift.APIError, // \u003c-- can't be changed\n\tenableErrorCode: true, // \u003c-- can't be changed\n\t// Custom lift-it plugins to use\n\tplugins: [],\n\t\n\t// Options for validate plugin of `lift-it`\n\tvalidate: {\n\t\t// Use the plugin defaults\n\t},\n\t\n\t// Options for validate plugin of `lift-it`\n\tvalidateOutput: {\n\t\tdirection: 'output', // \u003c-- can't be changed\n\t\texportName: 'outFields',\n\t\toptional: true,\n\t\tgetDefaultValue: function () {\n\t\t\treturn {}\n\t\t},\n\t\tcode: 100,\n\t\terrorHandler: function (action, value, err) {\n\t\t\tthrow err\n\t\t},\n\t\toptions: {\n\t\t\tstrict: true\n\t\t}\n\t},\n\n\t// Options for filters plugin of `lift-it`\n\tfilters: './filters',\n\t\n\t// Options for bodyParser.json() of `body-parser`\n\tbodyParser: {},\n\t\n\t// Options for this module\n\tminVersion: 1, // the min version to support\n\tdataScrub: [/session|password|serial|token/i], // describe fields to hide in the body\n\tisRest: true,\n\thasApiKeyAuth: false // HTTP header apiKey will be passed in json to the endpoints,\n\thasOAuth: true // HTTP header Authorization will be passed in json to the endpoints\n\tcheckId: function(x){\n\t\t// Checks whether the string x is an id of the resource\n\t},\n\tcallToJSON: function (x) {\n\t\t// function to convert log value to JSON\n\t\treturn x.toJSON()\n\t},\n\tonsuccess: function (response, runInfo, body, endpoint) {\n\t\t// Called right before a request is answered with success\n\t\t// `response` is the JSON object to send\n\t\t// `runInfo` carries data about the execution\n\t\t// `runInfo.req` is the express request object (if routed with express)\n\t\t// `runInfo.requestId` a unique identifier for this execution\n\t\t// `runInfo.beginTime` is the value of Date.now() when the request was received\n\t\t// `runInfo.profileData` is the profile data (if enabled)\n\t\t// `body` is the cleaned (see bellow) JSON input\n\t\t// `endpoint` is the instance of the executed Endpoint\n\t},\n\tonfailure: function (response, runInfo, body, endpoint, error) {\n\t\t// Called right before a request is answered with error\n\t\t// `response`, `runInfo`, `body` and `endpoint` behave the same as onsuccess\n\t\t// `endpoint` may be undefined if the error is not linked to any\n\t\t// `error` is the original error object\n\t},\n\ttimeout: 30e3,\n\tontimeout: function (runInfo, body, endpoint) {\n\t\t// Called when an endpoint does not resolve within time\n\t\t// `runInfo`, `body` and `endpoint` behave the same as onsuccess\n\t\t// The timeout will not interfere on the endpoint's normal operation, that is,\n\t\t// the request won't be modified in any way and the endpoint still have\n\t\t// oportunity to answer the request properly\n\t},\n\t\n\t// Options for generating openApi spec\n\t// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md\n\topenApi: {\n\t\t// Whether to add routes to serve the spec, like:\n\t\t// /swagger.json -\u003e all versions\n\t\t// /v3/swagger.json -\u003e specific version\n\t\t// /v-last/swagger.json -\u003e last version\n\t\tserve: false,\n\t\t// File name used to serve\n\t\tserveAs: 'swagger.json',\n\t\t// Base swagger to start completing\n\t\t// Usually, we have info, basePath, host and schemes properties from root object\n\t\tmiddleware: function (req, res, next) {\n\t\t\t// An express middleware, set on the spec-serving rout\n\t\t\t// May be used to implement authentication, for example\n\t\t\tnext()\n\t\t},\n\t\tprepareEndpoint: function (endpoint, pathItem) {\n\t\t\t// Called for each \"Path Item Object\" created\n\t\t\t// `endpoint` is the instance of Endpoint\n\t\t\t// `pathItem` is an object following the OpenAPI spec for \"Path Item Object\"\n\t\t\t// The pathItem to use should be returned\n\t\t\t// If nothing is returned, this endpoint will be omitted from the output\n\t\t\treturn pathItem\n\t\t},\n\t\tprepareSpec: function (spec) {\n\t\t\t// Called for each \"Path Item Object\" created\n\t\t\t// `endpoint` is the instance of Endpoint\n\t\t\t// `pathItem` is an object following the OpenAPI spec for \"Path Item Object\"\n\t\t\treturn spec\n\t\t}\n\t}\n})\n\n// `api.router` is an express router object\n// You can, for example:\nlet app = apiLift.express() // see note bellow about apiLift.express\napp.use('/api', api.router)\nrequire('http').createServer(app).listen(80)\n```\n\nThis 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`\n\nThe 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]'}`\n\n## Returned value\nThe return of `apiLift()` call is an instance of `API`. Its properties are:\n\n* `{express:Router} router`: an express Router instance\n* `{number} minVersion`: the minimum supported version\n* `{number} maxVersion`: the maximum supported version\n* `{Array\u003cstring\u003e} versions`: The list of supported versions, ordered from oldest to newest. Example: `['v3', 'v4']`\n* `{Array\u003cEndpoint\u003e} endpoints`: the list of available endpoints\n* `{Object\u003cEndpoint\u003e} endpointByUrl`: a map from url to an Endpoint instance\n\nIf you are not interested in the router, but in the returned meta-data (like max version), use `apiLift.info(options)` instead:\n\n```js\nlet apiLift = require('api-lift')\n\nlet info = apiLift.info({\n\t// The default values are described bellow\n\tfolder: './api',\n\tminVersion: 1\n})\n\ninfo.maxVersion // a number\n```\n\n## Generated Doc\nAll public methods and properties are described in the [generated docs](http://clubedaentrega.github.io/api-lift/docs)\n\n## Versioning\nThis 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](http://semver.org/)).\n\nBy 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!\n\nNote 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).\n\nAfter some time, the support for version 1 may be dropped, by increasing the `minVersion` option and removing old v1 files.\n\n### Complete example\nFor the following files in the api folder:\n```\napi\n\tuser\n\t\tcreate.js\n\t\tcreate-v2.js\n\t\tfindbyname-v1.js\n\t\tgetinfo.js\n```\n\nAssuming `minVersion` is 1, those endpoints will be created:\n```\n/v1\n\t/user/create -\u003e api/user/create-v2.js\n\t/user/findbyname -\u003e api/user/findbyname-v1.js\n\t/user/getinfo -\u003e api/user/getinfo.js\n/v2\n\t/user/create -\u003e api/user/create-v2.js\n\t/user/getinfo -\u003e api/user/getinfo.js\n/v3\n\t/user/create -\u003e api/user/create.js\n\t/user/getinfo -\u003e api/user/getinfo.js\n```\n\nThe 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.\n\n## Run Info\nWhile 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.\n\n## Logging\nTODO\n\n## Endpoint handler\nTODO\n\n### Body Limit\nEach endpoint can set its own body size limit by setting the `module.exports.bodyLimit` property (syntax from [bytes](https://www.npmjs.com/package/bytes)). If it doesn't set this property, the limit will be the one defined in the `bodyParser` field from `options`.\n\n## Success\nThe default HTTP status code to a correct execution of `success` is `200 Ok`.\nIf the success function has in its `output` the property `HTTPStatusCode`, this one is answered. \nNote: `HTTPStatusCode` is a private property and will be deleted after sent. Do not use it as a property in your `output`.\n\n## Error codes\nThe generated erros respects the [APIError class](https://github.com/clubedaentrega/api-lift/blob/master/lib/APIError.js), 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.\n\nThe APIError will always respect the information received from `error` function.\nThe invalid `path` errors are always `404 Not Found` and invalid content-type/json `400 Bad Request`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclubedaentrega%2Fapi-lift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclubedaentrega%2Fapi-lift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclubedaentrega%2Fapi-lift/lists"}