lift-it
v7.1.0
Published
Forget about maintaining routes files, use dynamic routes created from files in a folder
Downloads
8
Readme
Lift It
Forget about maintaining routes files, use dynamic routes created from files in a folder
Install
npm install lift-it
Basic usage
// Lift the 'api' folder
let api = require('lift-it')('./api').lift()
// Execute the file ./api/item/create.js
api.run('item/create', {
name: 'Water',
value: 17
}, function (err, response) {
// Always async
})
Lift-it scans recursively a folder for *.js
files. Those files should export a handler
function, executed when someones 'run' them. The only ignored file is errors.js
.
An example of 'item/create':
// `body` is the argument passed by 'run' (in this case, `{name:'Water',value:17}`)
// `success` and `error` are callback functions, learn about it here:
// https://github.com/clubedaentrega/run-it
module.exports.handler = function (body, success, error) {
// Item.create represents an async job
Item.create(body, error(function (item) {
// Answer with {id: '54bd55fc13e53ce1077415a2'}
success({
id: item.id
})
}))
}
Express example
This module can be easily used with express (or any other route-based web framework).
An example of simple JSON-based POST api:
// Express
let app = require('express')()
app.use(require('body-parser').json())
// Lift
let api = require('lift-it')('./api').lift()
// Set routes
api.actions.forEach(function (action) {
// `action` is an object. See the docs bellow
app.post(action.name, function (req, res) {
// action.run(...) is the same as api.run(action.name, ...)
action.run(req.body, function (err, response) {
res.json(err ? {error: String(err)} : response)
})
})
})
Plugins
The base module will scan a folder and look for exported handler in *.js
files. This base behaviour can be expanded with plugins.
The available bundled plugins are:
- validate: validate the input with validate-fields
- filters: add filters (like middlewares)
- profile: enable profiling async jobs
Docs
require('lift-it')
A function with one parameter, the folder path to scan: require('lift-it')(folder)
.
Returns a lifter
object.
require('lift-it').lean()
Do a lean lift. See lifter.lean()
. Returns a lifted instance
lifter.use(plugIn)
Add the given plugin to the lift process. Read more about available and custom plugins bellow
lifter.recursive
Whether the folder should be scanned recursively or not. The default value is true
.
lifter.requireFile
A callback to handle requiring a file. This allows one to completly change the semantics of a required file. The default value is function (require, path) { return require(path) }
.
lifter.profile
lifter.errorClass
lifter.enableErrorCode
Change settings of run-it. Must be changed only before lifter.lift()
is called.
lifter.lift()
Run the lift process (sync), throws if any error happens. For each *.js
file in the target folder, all plugins will be executed in the order they were 'use'd.
This returns a lifted
object.
lifter.leanLift()
Run the lean lift process, that is, only look for file names. This will not require files, nor apply plugins.
This returns a lifted
object with the lean
flag on. No action can be executed on it.
This is useful if you just want to check action names, not execute them.
lifted.get(name)
Return an action by name. Return an action
object (or null
if not found).
lifted.run(name, ...args, callback)
Run the action with the given arguments. callback(err,...output)
is executed when done. The action is executed in its own domain, see run-it for more info about that.
Throws if no action with the given name is found.
If profile is enabled, the last argument for callback
is the profile data.
lifted.getRunner(name)
Return a run-it runner for the given action. Call runner.exec(...args, callback)
to actually execute the action. See action.getRunner()
.
Throws if no action with the given name is found.
lifted.actions
An array of action
objects
lifted.lean
A boolean. If true
, this is a lean lift (see lifter.leanLift()
), so no action can be executed.
action.path
File path, like '/api/item/create.js'
action.name
Action name, like 'item/create'
. This is the relative path to the target folder, without the trailing '.js'
.
action.filters
An array of filters, executed before the action handler. This is used by plugins.
action.postFilters
An array of post filters, executed after the action handler. This is used by plugins.
action.module
The result of require
-ing the file. action.module.handler
is the handler for this action.
action.profile
A boolean that flags whether profiling is enabled for this action.
action.run(...args, callback)
Run this action. This is the same as lifted.run(action.name, ...args, callback)
.
action.getRunner()
Return the run-it runner for the action. Call runner.exec(...args, callback)
to actually execute the action.
This is useful to make some adhoc changes to the runner, like runner.runInfo(info).profile(true).exec(...args, callback)
. See docs on run-it.
Available plugins
Profile
If the require
-d file exports profile
(a boolean), this value will overwrite the global lift.profile
. Get this plugin with require('lift-it').profile()
.
For example, this file will have profiling always on:
module.exports.profile = true
module.exports.handler = function (body, success, error) {
// ...
}
Use:
let liftIt = require('lift-it'),
lift = liftIt('./api')
lift.use(liftIt.profile())
let api = lift.lift()
api.run('item/create', item, function (err, response, profileData) {
// ...
})
Validate
Add input validation with validate-fields. Get it with require('lift-it').validate(options)
.
options
is an optional object with the properties displayed bellow (default values indicated):
options = {
// The export property name
exportName: 'fields',
// Whether to error out if the file does not export it
optional: false,
// Check input or output
direction: 'input',
// Which argument to validate
// The default will check the first
position: 0,
// A function to return a value if the desired input/output is not present
getDefaultValue: function () {
throw new Error('Insufficient number of arguments')
},
// If lifter.enableErrorCode is true, which code to use in error(code, msg)
code: 101,
// If lifter.enableErrorCode is true, which code to use in error(HTTPStatusCode, code, msg)
HTTPStatusCode: 500,
// A function to handle error cases
errorHandler: function (action, value, err) {
// `action` is an Action instance
// `value` is the offending value
// `err` is a lifter.errorClass instance
// `err.code` is `options.code`
throw err
},
// Let you define your own custom types
// See doc on validate-fields module for that
defineTypes: function (validate) {},
// Options passed to schema.validate() of validate-fields
options: {}
}
Example of a file using it:
module.exports.fields = {
name: String,
value: 'uint'
}
module.exports.handler = function (body, success, error) {
// ...
}
let liftIt = require('lift-it'),
lift = liftIt('./api')
lift.use(liftIt.validate())
let api = lift.lift()
api.run('item/create', {}, function (err) {
// err is new Error('I was expecting a value in name')
})
This plugin will set action.module[exportName+'-schema']
to validate.parse(action.module[exportName])
.
Filters
Add support for filters. Filters are functions executed sequentially before the handler. One example of their use is implementing authentication. Get it with require('lift-it').filters(folder)
.
Filter handlers are implemented and exported by files in the filters folder. Each of those files may export as many filters as you want.
See an example of a file implementing two filters:
module.exports = function (body, success, error) {
// Check auth, read the DB, etc
success('Some data I got elsewhere')
}
module.exports.double = function (body, success, error) {
// Simply change the body input
body.value *= 2
success()
}
The 'item/create'
action may use those filters this way:
module.exports.filters = ['myFilter', 'myFilter.double']
module.exports.handler = function (body, moreData, success, error) {
// body.value will be doubled
// moreData will be the string create by the first filter
// ...
}
If this filter file path is 'filters/myFilter.js'
, the main file (the one that lifts everything) may be:
let liftIt = require('lift-it'),
lift = liftIt('./api')
lift.use(liftIt.filters('./filters'))
let api = lift.lift()
api.run('item/create', {value: 10}, function (err, response) {
// ...
})
Custom plugins
A plugin is a function like function (action, lifter) {}
. That function is called once for every file that is found in the lifted folder. Creating your own plugin is that simple:
let myPlugin = function (action, lifter) {
// Do something with the action, like checking something
if (action.name.indexOf('drop')) {
throw new Error('Sorry, we do not put up with dropping things...')
}
// Add a filter to every action to delay them by 1s
action.filters.push(function (body, success) {
setTimeout(success, 1e3)
})
// Add a post filter to check something
action.postFilters.push(function (response, success) {
if (typeof response.status !== 'string') {
throw new Error('Response should have the status field')
}
success(response)
})
}
Warn: the lifter
object should not be modified. It should be treated as read-only.