Docs for version v0.7.x.
Nodecaf is an Express framework for developing REST APIs in a quick and
convenient manner.
Using Nodecaf you'll get:
If you are unfamiliar with Express, checkout
their routing docs
so that you can better grasp Nodecaf features and whatnot.
Get Started
- Install the cli utilities:
npm i -P -g nodecaf-cli
. - Create or just go to your node project directory (you must have a
package.json
). - Add to your project with:
npm i -P nodecaf
. - Create a skelleton project with:
nodecaf init
. - Add your globals in
lib/main.js
const { AppServer } = require('nodecaf');
const api = require('./api');
module.exports = function init(){
let app = new AppServer();
let shared = {};
app.expose(shared);
app.onRuoteError = function(input, err, send){
};
app.beforeStart = async function(){
};
app.afterStop = async function(){
};
app.api(api);
return app;
}
- Add your routes in
lib/api.js
module.exports = function({ post, get, del, head, patch, put }){
get('/foo/:f/bar/:b', Foo.read, Bar.read);
post('/foo/:f/bar', Foo.read, Bar.write);
};
Reporting Bugs
If you have found any problems with this module, please:
- Open an issue.
- Describe what happened and how.
- Also in the issue text, reference the label
~bug
.
We will make sure to take a look when time allows us.
Proposing Features
If you wish to get that awesome feature or have some advice for us, please:
- Open an issue.
- Describe your ideas.
- Also in the issue text, reference the label
~proposal
.
Contributing
If you have spotted any enhancements to be made and is willing to get your hands
dirty about it, fork us and
submit your merge request
so we can collaborate effectively.
Manual
On top of all the cool features Express offers, check out how to use all
the awesome goodies Nodecaf introduces.
Handler Args
In this manual we address as handler args the keys in the object passed as
the only argument of any route handler function. The code below shows all
handler args exposed by Nodecaf:
function({ req, res, next, query, params, body, flash, conf, log, error, headers }){
}
Quick reference:
req
, res
, next
: The good old parameters used regularly in Express.query
, parameters
, body
, headers
: Shortcuts to the homonymous properties of req
.
They contain respectively the query string, the URL parameters, and the request
body data.flash
: Is a shortcut to Express req.locals
. Keys inserted in this a object
are preserved for the lifetime of a request and can be accessed in all handlers
of a route chain.conf
: This object contains the entire
application configuration data.log
: A GoLog logger instance. Use it to log custom events of
your application.- Also all keys of the globally exposed object are available
as handler args for all routes.
error
: A function to output REST errors and abort the
handler chain execution.
Settings File
Nodecaf allow you to read a configuration file and use it's data in all routes
and server configuration.
Use this feature to manage:
- external services data such as database credentials
- Nodecaf settings such as SSL and logging
- Your own server application settings for your users
Suported config formats: TOML, YAML, JSON
Check out how to generate a project with configuration file already plugged in
To setup a config file for an existing project, open the binary for your server
in bin/proj-name.js
. Then add a confPath
key to the run parameter object
whose value must be a string path pointing to your conf file.
The config data can be passed as an object to the app constructor in lib/main.js
:
module.exports = function init(){
let conf = { key: 'value' };
let app = new AppServer(conf);
}
You can use the config data through it's handler arg in
all route handlers as follows:
post('/foo', function({ conf }){
console.log(conf.key);
});
Layered Configs
You can also use the app.setup
to add a given configuration
file or object on top of the current one as follows:
app.setup('/path/to/settings.toml');
app.setup('/path/to/settings.yaml');
app.setup({ key: 'value' });
app.setup({ key: 'new-value', foo: 'bar' });
Layering is useful, for example, to keep a default settings file in your server
source code to be overwritten by your user's.
Live Reload
When in debug mode (app.setup({ debug: true })
) changes to loaded config files
will trigger aspplication reloads, so you don't have to manually restart the
whole node process.
Logging
To setup Nodecaf logger you must add a log
key to the application server config
object like this:
conf.log = { file: 'path/to/conf/file.log' };
conf.log = { stream: process.stdout };
let app = new AppServer(conf);
You can also setup a log file in your settings file to be automatically
transferred to the conf
argument of init
.
[log]
file = "path/to/conf/file.log"
In your route handlers, use the log
handler arg as a
GoLog instance:
function({ log }){
log.info('hi');
log.warn({lang: 'fr'}, 'au revoir');
}
Nodecaf will automatically log some useful server events as described in the
table below:
Level | Event |
---|
warn | An error happened inside a route after the headers were already sent |
error | An error happened inside a route and was not caught |
fatal | An error happened that crashed the server process |
debug | A request has arrived |
Async Handlers
Nodecaf brings the useful feature of accepting async functions as route handlers
with zero configuration. All rejections/error within your async handler will be
gracefully handled by the same routine the deals with regular functions. You will
be able to avoid callback hell without creating bogus adapters for your promises.
get('/my/thing',
function({ res, next }){
res.send('My regular function works!');
next();
},
async function({ res }){
await myAsyncThing();
res.end('My async function works too!');s
}
);
Error Handling
In Nodecaf, any uncaught synchronous error happening inside route handler will be
automatically converted into a harmless RESTful 500.
post('/my/thing', function(){
throw new Error('Should respond with a 500');
});
To support the callback error pattern, use the error
handler arg.
const fs = require('fs');
post('/my/thing', function({ error, res }){
fs.readFile('./my/file', 'utf8', function(err, contents){
if(err)
return error(err, 'Optional message to replace the original');
res.end(contents);
});
});
To use other HTTP status codes you can send a string in the first parameter of
error
. The supported error names are the following:
Error name | Status Code |
---|
NotFound | 404 |
Unauthorized | 401 |
ServerFault | 500 |
InvalidActionForState | 405 |
InvalidCredentials | 400 |
InvalidContent | 400 |
post('/my/thing', function({ error }){
try{
doThing();
}
catch(e){
error('NotFound', 'Optional message for the JSON response');
}
});
You can always deal with uncaught exceptions in all routes through a default
global error handler. In your lib/main.js
add an onRuoteError
function
property to the app
.
app.onRuoteError = function(input, err, send){
if(err instanceof MyDBError)
send('ServerFalut', 'Sorry! Database is sleeping.');
else if(err instanceof ValidationError)
send('InvalidContent', err.data);
}
- The
send
function will instruct Nodecaf to output the given error. - The
input
arg contain all handler args for the request. - If you do nothing for a specific type of
Error
the normal 500 behavior will
take place.
REST Assertions
Nodecaf provides you with an assertion module containing functions to generate
the most common REST outputs based on some condition. Check an example to
trigger a 404 in case a database record doesn't exist.
let { exist } = require('nodecaf').assertions;
get('/my/thing/:id', function({ params, db }){
let thing = await db.getById(params.id);
exist(thing, 'thing not found');
doStuff();
});
If the record is not found, the exist
call will stop the route execution right
away and generate a RESTful NotFound
error.
Along with exist
, the following assertions with similar behavior are provided:
Method | Error to be output |
---|
valid | InvalidContent |
authorized | Unauthorized |
authn | InvalidCredentials |
able | InvalidActionForState |
To use it with callback style functions, pass the error
handler arg as the
third parameter.
let { exist } = require('nodecaf').assertions;
post('/my/file/:id', function({ error, res, params }){
fs.readFile('./my/file/' + params.id, 'utf8', function(err, contents){
exist(!err, 'File not found', error);
res.end(contents);
});
});
Expose Globals
Nodecaf makes it simple to share global objects (eg.: database connections,
instanced libraries) across all route handlers. In your lib/main.js
you can
expose an object of which all keys will become handler args.
app.expose({
db: myDbConnection,
libX: new LibXInstance()
});
Then in all routes you can:
get('/my/thing', function({ db, libX }){
});
CORS
Nodecaf provides a setting to enable permissive CORS on all routes. Defaults to
disabled. In your conf file:
cors = true
HTTPS
In production it's generally desirable to have an HTTPS setup for both client to
API and API to API communications. You can enable SSL for your server by adding
a ssl
key to your config, containing both the path for your key and cert.
[ssl]
key = "/path/to/key.pem"
cert = "/path/to/cert.pem"
When SSL is enabled the default server port becomes 443.
Filter Requests by Mime-type
Nodecaf allow you to reject request bodies whose mime-type is not in a defined
white-list. Denied requests will receive a 400 response with the apporpriate
message.
Define a filter for the entire app on your api.js
:
module.exports = function({ }){
this.accept(['json', 'text/html']);
}
Override the global accept per route on your api.js
:
const { accept } = require('nodecaf');
module.exports = function({ post, put }){
this.accept(['json', 'text/html']);
let json = accept('json');
let img = accept([ 'png', 'jpg', 'svg', 'image/*' ]);
post('/my/json/thing', json, myJSONHandler);
post('/my/img/thing', img, myImageHandler);
}
API Description
Nodecaf allows you to descibe your api and it's functionality, effectively turning
your code in the single source of truth. The described API can later be used to
generate an
Open API compatible
document.
In lib/api.js
describe your API as whole through the info
parameter:
module.exports = function({ get, info }){
info({
description: 'My awesome API that foos the bars and bazes the bahs'
});
get('/my/thing/:id', function(){
});
}
The info
funciton expects an object argument on the OpenAPI
Info Object
format. If not defined the title
and version
keys will default to your server's.
Describe your API endpoints by chaining a desc
method to each route definition.
module.exports = function({ get }){
get('/my/thing/:id', function(){
}).desc('Retrieves a thing from the database\n' +
`Searches the database for the thing with the given :id. Returns a
NotFound error in case no thing is found.`);
}
The desc
method takes a single string argument and uses it's first line (before \n
)
to set the
Operation object's
summary
property and the rest of the text to set the description
(CommonMark).
Other Settings
Property | Type | Description | Default |
---|
app.name | String | Name to be displayed in logs and documentation | 'express' |
app.version | String | Verison to be displayed in logs and documentation | '0.0.0' |
app.settings.port | Integer | Port for the web server to listen (also exposed as user conf) | 80 or 443 |
app.shouldParseBody | Boolean | Wether supported request body types should be parsed | true |
app.settings.formFileDir | Path | Where to store files uploaded as form-data | OS default temp dir |
app.alwaysRebuildAPI | Boolean | Wether the API should be rebuilt dynamically for every start or setup operation | false |
app.cookieSecret | String | A secure random string to be used for signing cookies | none |