Overview
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. However, LoopBack adds the concept of middleware phases, to clearly define the order in which middleware is called. Using phases helps to avoid ordering issues that can occur with standard Express middleware.
LoopBack supports the following types of middleware:
- Pre-processing middleware for custom application logic.
- Dynamic request handling middleware to serve dynamically-generated responses, for example HTML pages rendered from templates and JSON responses to REST API requests.
- Static middleware to serve static client-side assets.
- Error-handling middleware to deal with request errors.
How to add middleware
To add middleware to your application:
- Specify the middleware function:
- If using an existing function or package, add the code to your application or install the package.
- If you are creating a new middleware function, write it. See Defining a middleware handler function.
- Register the middleware:
- Edit
server/middleware.json
. This is the recommended way to register middleware. See Registering middleware in middleware.json. - Alternatively, register the middleware in application code. See Registering middleware in JavaScript.
- Edit
Middleware phases
LoopBack defines a number of phases, corresponding to different aspects of application execution. When you register middleware, you can specify the phase in which the application will call it. See Registering middleware in middleware.json and Using the LoopBack API. If you register middleware (or routes) with the Express API, then it is executed at the beginning of the routes
phase.
The predefined phases are:
initial
- The first point at which middleware can run.session
- Prepare the session object.auth
- Handle authentication and authorization.parse
- Parse the request body.routes
- HTTP routes implementing your application logic. Middleware registered via the Express APIapp.use
,app.route
,app.get
(and other HTTP verbs) runs at the beginning of this phase. Use this phase also for sub-apps likeloopback/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.
Each phase has "before" and "after" subphases in addition to the main phase, encoded following the phase name, separated by a colon. For example, for the "initial" phase, middleware executes in this order:
initial:before
initial
initial:after
Middleware within a single subphase executes in the order in which it is registered. However, you should not rely on such order. Always explicitly order the middleware using appropriate phases when order matters.
Specifying a middleware function
Using built-in middleware
LoopBack provides convenience middleware for commonly-used Express/Connect middleware, as described in the following table.
When you use this middleware, you don't have to write any code or install any packages; you just specify in which phase you want it to be called; see Registering middleware in middleware.json.
Middleware ID | Code accessor | External package |
---|---|---|
loopback#favicon | loopback.favicon() | serve-favicon |
loopback#rest | loopback.rest() | N/A |
loopback#static | loopback.static() | serve-static |
loopback#status | loopback.status() | N/A |
loopback#token | loopback.token() | N/A |
loopback#urlNotFound | loopback.urlNotFound() | N/A |
To simplify migration from LoopBack 1.x and Express 3.x, LoopBack provides middleware that was built-in to in Express 3.x, as shown in the following table. Best practice is to load this middleware directly via require()
and not rely on LoopBack's compatibility layer.
LoopBack middleware | Express/Connect middleware |
---|---|
loopback.body-parser() | body-parser |
loopback.compress() | compression |
loopback.timeout() | connect-timeout |
loopback.cookieParser() | cookie-parser |
loopback.cookieSession() | cookie-session |
loopback.csrf() | csurf |
loopback.errorHandler() | errorhandler |
loopback.session() | express-session |
loopback.methodOverride() | method-override |
loopback.logger() | morgan |
loopback.responseTime() | response-time |
loopback.directory() | serve-index |
loopback.vhost() | vhost |
Using other middleware
You can use any middleware compatible with Express; see Express documentation for a partial list. Simply install it:
npm install --save <module-name>
Then simply register it so that it is called as needed; see Registering middleware in middleware.json and Registering middleware in JavaScript.
Defining a new middleware handler function
If no existing middleware does what you need, you can easily write your own middleware handler function. To register the middleware function in middleware.json
, you need to create a constructor (factory) function that returns the middleware function.
By convention, place middleware functions in the server/middleware
directory.
A middleware handler function accepts three arguments, or four arguments if it is error-handling middleware. The general form is:
Name | Type | Optional? | Description |
---|---|---|---|
err | Object | Required for error-handling middleware. | Use only for error-handling middleware. Error object, usually an instance or |
req | Object | No | The Express request object. |
res | Object | No | The Express response object. |
next | Function | No | Call next() after your application logic runs to pass control to the next middleware handler. |
An example of a middleware function with three arguments, called to process the request when previous handlers did not report an error:
return function myMiddleware(req, res, next) { // ... }
Here is a constructor (factory) for this function; use this form when registering it in middleware.json
:
module.exports = function() { return function myMiddleware(req, res, next) { // ... } }
An example a middleware function with four arguments, called only when an error was encountered.
function myErrorHandler(err, req, res, next) { // ... }
Packaging a middleware function
To share middleware across multiple projects, create a package that exports a middleware constructor (factory) function that accepts configuration options and returns a middleware handler function; for example, as shown below.
If you have an existing project created via slc loopback
, to implement a new middleware handler that you can share with other projects, place the file with the middleware constructor in the server/middleware
directory, for example, server/middleware/myhandler.js
.
module.exports = function(options) { return function customHandler(req, res, next) { // use options to control handler's b behavior } };
Registering middleware in middleware.json
The easiest way to register middleware is in server/middleware.json
. This file specifies all an application's middleware functions and the phase in which they are called.
When you create an application using the slc loopback
application generator, it creates a default middleware.json
file that looks as follows:
{ "initial:before": { "loopback#favicon": {} }, "initial": { "compression": {} }, "session": { }, "auth": { }, "parse": { }, "routes": { }, "files": { }, "final": { "loopback#urlNotFound": {} }, "final:after": { "errorhandler": {} } }
Each top-level key in middleware.json
defines a middleware phase or sub-phase, for example "initial", "session:before", or "final". Phases run in the order they occur in the file.
Each phase is a JSON object containing a key for each middleware function to be called in that phase. For example, "loopback/server/middleware/favicon" or "compression".
In general, each phase has the following syntax:
middlewarePath : {
[ enabled: [true | false], ]
[ params : paramSpec, ]
[ paths : routeSpec ]
}
}
- phase is one of the predefined phases listed above (initial, session, auth, and so on) or a custom phase; see Adding a custom phase.
- sub-phase (optional) can be
before
orafter
. - middlewarePath specifies the path to the middleware function, as described below.
The optional middleware configuration properties are described below.
Path to middleware function
Specify the path to the middleware function (middlewarePath) in the following ways:
For an external middleware module installed in the project, just use the name of the module; for example
compression
. See Using other middleware.For a script in a module installed in the project, use the path to the module; for example
loopback/server/middleware/rest
.For a script with a custom middleware function, use the path relative to
middleware.json
, for example./middleware/custom
.Absolute path to the script file (not recommended).
Additionally, you can use the shorthand format {module}#{fragment}
, where fragment is:
A property exported by module, for example
loopback#favicon
is resolved torequire('loopback').favicon
.A file in module's
server/middleware
directory, for examplerequire('loopback/server/middleware/favicon')
A file in modules'
middleware
directory, for examplerequire('loopback/middleware/favicon')
Middleware configuration properties
You can specify the following properties in each middleware section. They are all optional:
Property | Type | Description | Default |
---|---|---|---|
enabled | Boolean | Whether to register or enable the middleware. You can override this property in environment-specific files, for example to disable certain middleware when running in production. For more information, see Environment-specific configuration | true |
params | Object or Array | Parameters to pass to the middleware handler (constructor) function. Most middleware constructors take a single "options" object parameter; in that case the To specify a project-relative path (for example, to a directory containing static assets), start the string with the prefix See examples below. | N/A |
path | String | Specifies the REST endpoint that triggers the middleware. 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 app.use (Express documentation). | Triggers on all routes |
Example of a typical middleware function that takes a single "options" object parameter:
"compression": { "params": { "threshold": 512 } }
Example of a middleware function that takes more than one parameter, where you use an array of arguments:
"morgan": { "params": ["dev", { "buffer": true } ] }
Example or an entry for static middleware to serve content from the client
directory in the project's root:
... "files": { "loopback#static": { "params": "$!../client" } } ...
Adding a custom phase
In addition to the predefined phases in middleware.json
, you can add your own custom phase simply by adding a new top-level key.
For example, below is a middleware.json
file defining a new phase "log" that comes after "parse" and before "routes":
{ ... "parse": {}, "log": { ... }, "routes": {} ... }
Environment-specific configuration
You can further customize configuration through middleware.local.js
, middleware.local.
json
, and middleware.env.js
or middleware.env.json
, where env
is the value of NODE_ENV
environment variable (typically development
or production
).
See Environment-specific configuration for more information.
Registering middleware in JavaScript
You can register middleware in JavaScript code with:
- LoopBack API; you can specify the phase in which you want the middleware to execute.
- Express API; the middleware is executed at the beginning of the
routes
phase.
Using the LoopBack API
To register middleware with the LoopBack phases API, use the following app
methods:
For example:
var loopback = require('loopback'); var morgan = require('morgan'); var errorhandler = require('error-handler'); var app = loopback(); app.middleware('routes:before', morgan('dev')); app.middleware('final', errorhandler()); app.middleware('routes', loopback.rest());
Using the Express API
You can define middleware the "regular way" you do with Express in the main application script file, /server/server.js
by calling app.use()
to specify middleware for all HTTP requests to the specified route; You can also use app.get()
to specify middleware for only GET requests, app.post()
to specify middleware for only POST requests, and so on. For more information, see app.METHOD in Express documentation.
Here is the general signature for app.use()
:
app.use([route], function([err,] req, res, next) {
...
next();
});
app
is the LoopBack application object: app = loopback()
. The parameters are:
route
, an optional parameter that specifies the URI route or "mount path" to which the middleware is bound. When the application receives an HTTP request at this route, it calls (or triggers) the handler function. See Specifying routes.- The middleware handler function (or just "middleware function"). See Defining a new middleware handler function.
For example:
var loopback = require('loopback'); var boot = require('loopback-boot'); var app = module.exports = loopback(); // Bootstrap the application, configure models, datasources and middleware. // Sub-apps like REST API are mounted via boot scripts. boot(app, __dirname); // this middleware is invoked in the "routes" phase app.use('/status', function(req, res, next) { res.json({ running: true }); });
Specifying routes
The route
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().
For example, to register middleware for all endpoints that start with "/greet":
app.use('/greet', function(req, res, next ) { ... })
To register middleware for all endpoints:
app.use(function(req, res, next ) { ... })
Caveats
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.
Examples
Static middleware
Example or an entry for static middleware to serve content from the client
directory in the project's root:
... "files": { "loopback#static": { "params": "$!../client" } } ...
Pre-processing middleware
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.
Pre-processing middleware must callnext()
at the end of the handler function to pass control to the next middleware. If you don't do this, 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(); }; };
- 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.
... Request tracking middleware triggered on /. The request processing time is 4.281957 ms. //your results will vary
Routing middleware
You can register routes in middleware.json
, but standard practice is to do so in server.js
or a boot script, as you do with Express. For more information, see Using the Express API.
Error-handling middleware
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
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
middleware is from the errorhandler module, previously available in Express v.3 asexpress.errorHandler
.
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
/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.