INTERNAL NOTE: the features described in this document are supported since loopback 2.8.0, loopback-boot 2.4.0 and yo loopback
1.6.0.
Middleware refers to functions executed when HTTP requests are made to REST endpoints. Since LoopBack is based on Express, LoopBack middleware is the same as Express middleware.
Projects scaffolded via slc loopback
keep all middleware configuration
in the file server/middleware.json
Example:
{
"initial:before": {
"loopback#favicon": {
}
},
"initial": {
"compression": {
"enabled": true,
"params": {
"threshold": 512
}
},
},
"routes": {
"loopback#status": {
"paths": "/"
}
},
"final": {
"loopback#urlNotFound": {
}
},
"final:after": {
"errorhandler": {
}
}
}
The configuration can be further customized through middleware.local.js/json
and middleware.{env}.js/json
.
The configuration file has the following format:
-
At the top level, the keys define named phases in which the middleware functions are executed, e.g. "initial" or "final". The phases are run in the same order as they are listed in the file.
-
Each phase section contains a map of middleware to run in this phase, keyed by a path to the file exporting the middleware constructor. For example, "loopback/server/middleware/favicon" or "compression".
-
A middleware section contains additional middleware properties as described below.
Every loopback application has the following predefined phases:
-
initial
- Register middleware that must run among the first. -
session
- Prepare the session object. -
auth
- Handle authentication and authorization. -
parse
- Parse the request body. -
routes
- Express routes implementing your application logic.Any middleware registered via the express API
app.use
,app.route
,app.get
(and other HTTP verbs) is run at the beginning of this phase.Use this phase also for sub-apps like
loopback/server/middleware/rest
orloopback-explorer
. -
files
- Serve static assets (requests are hitting the file system here). -
final
- Deal with errors and requests for unknown URLs.
The phases listed in middleware.json
are merged with the predefined phases
in such way that both orderings are preserved.
For example, below is a middleware.json
file defining a new phase "log"
that should be run after "parse" and before "routes":
{
"parse": {},
"log": {},
"routes": {}
}
Each phase provides three sub-phases: before, main, and after. The before/after phases are encoded in the phase name as a postfix. For example, the "initial" phase executes middleware registered in "initial:before" first, then in "initial" and at last "initial:after".
Middleware registered with the same phase name is invoked in the order in which it was registered. However, you should not rely on such order. Always explicitly order the middleware using appropriate phases when the order matters.
There are multiple ways of specifying the path to the file exporting the middleware constructor:
-
A name of the module that is installed in the project, e.g.
compression
. -
A path to a file located inside a module, e.g.
loopback/server/middleware/rest
-
A path relative to
middleware.json
, e.g../middleware/custom
-
An absolute path (not recommended).
Additionally, there is also a shorthand format {module}#{fragment}
available.
Paths in this format are resolved in the following order:
-
The fragment is a property exported by the module, e.g.
loopback#favicon
is resolved torequire('loopback').favicon
. -
The fragment is a file in module's
server/middleware
directory, e.g.require('loopback/server/middleware/favicon')
-
The fragment is a file in modules'
middleware
directory, e.g.require('loopback/middleware/favicon')
The following properties can be specified in each middleware section:
-
enabled
- whether the middleware should be registered at all,true
by default. You can override this property in environment-specific files, for example to disable certain middleware when running in production. -
params
- parameters to pass to the middleware constructor. Most middleware constructors expects a single "options" object, in that case theparams
value is this object:"compression": { "params": { "threshold": 512 } }
To configure constructors accepting more than one parameter, you can provide an array of arguments:
"morgan": { "params": ["dev", { "buffer": true } ] }
-
paths
- This parameter is a string that specifies the REST endpoint that will trigger the middleware. If you don't provide the parameter, then the middleware will trigger on all routes. In addition to a literal string, route can be a path matching pattern, a regular expression, or an array including all these types. For more information, see the Express documentation for app.use().
In order to support project-relative paths, the params
object is scanned for string values starting with the prefix $!
. Such values are interpreted as paths relative to the file middleware.json
and resolved into absolute paths. This feature is typically used to specify paths to directories containing static assets.
An example configuring a static middleware serving the client
directory located in project's root:
{
"files": {
"loopback#static": {
"params": "$!../client"
}
}
}
LoopBack provides convenience middleware for some commonly-used Express/Connect middleware, as described in the following table.
middleware id | code accessor | Express/Connect middleware |
---|---|---|
loopback#favicon |
loopback.favicon() |
serve-favicon |
loopback#static |
loopback.static() |
serve-static |
In order to simplify migration from LoopBack 1.x and Express 3.x, LoopBack
provides also middleware that used to be a part of Express 3.x. Developers
are encouraged to modify their applications to load this middleware directly
via require()
and do not rely on loopback's compatibility layer.
LoopBack middleware | Express/Connect middleware |
---|---|
... | ... |
(Copy the table from Express middleware and remove favicon entry, as it was already described above.)
The middleware can be configured directly from your javascript code too.
See API docs for app.middleware
, app.middlewareFromConfig
and
app.defineMiddlewarePhases
.
Example:
var loopback = require('loopback');
var morgan = require('morgan');
var errorhandler = require('error-handler');
var app = loopback();
// (configure datasources and models)
app.middleware('routes:before', morgan('dev'));
app.middleware('final', errorhandler());
app.middleware('routes', loopback.rest());
// this will be invoked in `routes` phase
app.get('/status', function(req, res, next) {
res.json({ running: true });
});
A route is a special case of express middleware, the handler function is
expected to send back the response and omit the call of next()
.
While it is possible to use app.use
and app.middleware
to define routes,
the more common approach is to use the API app.route
, app.get
, app.post
,
etc.
Example:
app.get('/', function(req, res, next) {
res.send('hello from `get` route');
});
app.post('/', function(req, res, next) {
res.send('hello from `post` route')
});
NOTE: Handlers registered through Express API are always executed at the beginning
of the routes
phase.
There are some things to look out for when using middleware, mostly to do with middleware declaration order. Be aware of the order of your middleware registration when using "catch-all" routes. For example:
...
app.get('/', function(req, res, next) {
res.send('hello from `get` route');
});
app.use(function(req, res, next) {
console.log('hello world from "catch-all" route');
next();
});
app.post('/', function(req, res, next) {
res.send('hello from `post` route')
});
...
In this case, since the GET /
middleware ends the response chain, the
"catch-all" middleware is never triggered when a get request is made. However,
when you make a POST request to /, the "catch-all" route is triggered because
it is declared before the post route. Doing a POST will show the console
message from both the "catch-all" route and the POST /
route.
A middleware handler is a function accepting three or four arguments.
A middleware function with three arguments is called to process the request when there was no error reported by the previous handlers.
function myMiddleware(req, res, next) {
// ...
}
A middleware function with four arguments is called only when an error was encountered.
function myErrorHandler(err, req, res, next) {
// ...
}
(Copy the section "Middleware function parameters" from the current docs)
Middleware modules typically export a constructor (factory) function
that accepts configuraton options and returns a middleware handler
function. You must follow this pattern if you want to load your
custom middleware via middleware.json
.
module.exports = function(options) {
return function customHandler(req, res, next) {
// use options to control handler's behaviour
}
};
Use pre-processing middleware to apply custom logic for various endpoints in
your application. Do this by registering handler functions to perform certain
operations when HTTP requests are made to specific endpoint or endpoints.
Always register pre-processing middleware with phases invoked before routes
,
e.g. initial
or parse
.
Pre-processing middleware must call next()
at the end of the handler function
to pass control to the next middleware. If you don't do this, then your
application will essentially "freeze." Technically, next()
doesn't have to
occur at the end of the function (for example, it could occur in an if
/ else
block), but the handler function must call it eventually.
module.exports = function() {
return function tracker(req, res, next) {
console.log('Request tracking middleware triggered on %s', req.url);
var start = process.hrtime();
res.once('finish', function() {
var diff = process.hrtime(start);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
console.log('The request processing time is %d ms.', ms);
});
next();
};
};
This middleware tells the server to display the time it takes to process the incoming HTTP request on all application routes.
You can see this middleware in action in using the basic LoopBack application from Getting started with LoopBack (or any standard LoopBack application):
-
Add the code above to
/server/middleware/tracker.js
. -
Edit (or create) the file
/server/middleware.json
and register the new middleware in the "initial" phase:{ "initial": { "./middleware/tracker": {} } }
-
Start the application with slc run.
-
Load http://localhost:3000/ in your browser.
In the console, you will see (for example):
...
Request tracking middleware triggered on /.
The request processing time is 4.281957 ms. //your results will vary
Use error-handling middleware to deal with request errors. While you are free to register any number of error-handling middleware, be sure to register them in the "final" phase.
LoopBack registers two error-handling middleware by default: urlNotFound
and
errorhandler
. The urlNotFound
middleware converts all requests that reach
the middleware into an Error object with status 404, so that 404 error responses
are consistent with "usual" error responses.
errorhandler
is the middleware provided by
errorhandler module,
it is be available as express.errorHandler
in Express version 3.
Example of a custom error processing middleware:
module.exports = function() {
return function logError(err, req, res, next) {
console.log('ERR', req.url, err);
};
};
-
Add the code above to
/server/middleware/log-error.js
. -
Edit (or create) the file
/server/middleware.json
and register the new middleware in the "final" phase:{ "final": { "./middleware/log-error": {} } }
-
Start the application with slc run.
-
Load http://localhost:3000/url-does-not-exist in your browser.