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. See example of pre-processing middleware.
- Dynamic request handling middleware to serve dynamically-generated responses, for example HTML pages rendered from templates and JSON responses to REST API requests. See [example pending].
- Static middleware to serve static client-side assets. See example of static middleware.
- Error-handling middleware to deal with request errors. See example of error-handling middleware.
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 new 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 Express middleware
Note: LoopBack v.3 does not provide convenience methods for Express middleware. For details, see the Release notes.
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:
function myMiddlewareFunc([err,] req, res, next) {
// ...
}
Name | Type | Optional? | Description |
---|---|---|---|
err | Object | Required for error-handling middleware. |
Use only for error-handling middleware.
Error object, usually an instance or Error ; for more information, see Error object.
|
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 with the application generator, 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 behavior
}
};
For details about the options
object, refer to Middleware configuration properties.
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 Application generator
it creates a default middleware.json
file that looks as follows:
{
"initial:before": {
"loopback#favicon": {}
},
"initial": {
"compression": {},
"cors": {
"params": {
"origin": true,
"credentials": true,
"maxAge": 86400
}
},
"helmet#xssFilter": {},
"helmet#frameguard": {
"params": [
"deny"
]
},
"helmet#hsts": {
"params": {
"maxAge": 0,
"includeSubdomains": true
}
},
"helmet#hidePoweredBy": {},
"helmet#ieNoOpen": {},
"helmet#noSniff": {},
"helmet#noCache": {
"enabled": false
}
},
"session": {},
"auth": {},
"parse": {},
"routes": {
"loopback#rest": {
"paths": [
"${restApiRoot}"
]
}
},
"files": {},
"final": {
"loopback#urlNotFound": {}
},
"final:after": {
"strong-error-handler": {}
}
}
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:
phase[:sub-phase] : {
middlewarePath : {
[ enabled: [true | false] ]
[, name: nameString ]
[, params : paramSpec ]
[, methods: methodSpec ]
[ paths : routeSpec ]
}
};
Where:
- 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
. - name: optional middleware name. See Middleware configuration properties below.
- middlewarePath: path to the middleware function. See Path to middleware function below.
- paramSpec: value of the middleware parameters, typically a JSON object. See Middleware configuration properties below.
- methodSpec: HTTP methods, such as ‘GET’, ‘POST’, and ‘PUT’. If not present, applies to all methods.
- routeSpec: REST endpoint(s) that trigger the middleware.
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 |
---|---|---|---|
name | String | An optional name for the entry. It can be used to identify an entry within the same phase/path for the purpose of merging | N/A |
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 params value is that object.
To specify a project-relative path (for example, to a directory containing static assets), start the string with the prefix $! . Such values are interpreted as paths relative to the file middleware.json .
See examples below.
|
N/A |
methods | String[ ] | Specifies the HTTP methods, such as 'GET', 'POST', and 'PUT'. If not present, it will apply to all methods. | N/A |
paths | String[ ] | Specifies the REST endpoint(s) that trigger 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 |
optional | Boolean | Specify whether the middleware is optional. Optional middleware do not throw, even if they are not installed or cannot be resolved in the file system. | N/A |
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"
}
}
//...
Example or an entry for static middleware to serve content from the multiple
directories in the project’s root:
//...
"files": {
"loopback#static": [{
"name": "x",
"paths": ["/x"],
"params": "$!../client/x"
},
{
"name": "y",
"paths": ["/y"],
"params": "$!../client/y"
}]
}
//...
Using variables in values
For any middleware configuration property, you can specify a variable in the value using the following syntax:
${ <var> }
Where <var>
is a property of the app
object. These properties include:
- Application-wide properties such as those defined in
config.json
. - Express app object properties.
For example, the following middleware.json
configuration will load LoopBack’s built-in rest middleware (loopback.rest)
during the routes phase at the path resolved by app.get('restApiRoot')
, which defaults to /api
.
{
"routes": {
"loopback#rest": {
"paths": ["${restApiRoot}"]
}
}
}
The following example loads hypothetical middleware named environmental
during the routes phase at the return value of app.get(env)
, typically either /development
or /production
.
{
"routes": {
"environmental": {
"paths": "${env}"
}
}
}
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 app = loopback();
app.middleware('routes:before', morgan('dev'));
app.middleware('routes', loopback.rest());
Using the Express API
Important:
When you register middleware with the Express API, it is always executed at the beginning of the routes
phase.
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();
});
As usual, 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 ) {
//...
});
Important: The above middleware is triggered by all routes that begin with “/greet”, so “/greet/you”, “greet/me/and/you” will all trigger it..
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.
Default middleware
LoopBack (via strong-remoting) registers two error-handling middleware by default:
urlNotFound
: A catch-all handler converting 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
: An instance of thestrong-error-handler
.
The presence of these middleware is controlled by configuration in config.json
.
Note: Scaffolded apps have strong-remoting’s errorHandler
disabled in favor of middleware.json config for strong-error-handler
.
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.
Important:
Always register pre-processing middleware in a phase before routes
, for example 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, 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.
For example:
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 function 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:server/middleware.json
{ "initial": { "./middleware/tracker": {} } }
-
Start the application:
$ node .
- 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
Routing middleware
For routes serving JSON, best practice is to create a new model and implement the routes as remote methods.
For routes serving non-JSON responses, best practice is to define them the standard “Express way” in server.js
or a boot script.
For more information, see Routing and Routing (Express documentation).
Note:
If you add middleware on the route
or route:after
phase, it will not execute after the route is matched.
Instead, it will be ignored because the route was already matched.
Error-handling middleware
Use error-handling middleware to deal with request errors. The strong-error-handler
middleware is scaffolded by default and sufficient for many projects, but additional custom error-handling middleware may be registered as necessary.
While you are free to register any number of error-handling middleware, be sure to register them in the “final” phase.
Example of a custom error-handling middleware:
module.exports = function() {
return function logError(err, req, res, next) {
console.log('ERR', req.url, err);
};
};
To register this middleware:
- 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.
- Load http://localhost:3000/url-does-not-exist in your browser.
Note: This middleware prints errors much like the strong-error-handler
middleware. The strong-error-handler
can be configured to not print errors by setting its log
parameter to false
.