Chinese Documentation : Defining middleware

See also:

See also:

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:

  1. Specify the middleware function:
    1. If using an existing function or package, add the code to your application or install the package. 
    2. If you are creating a new middleware function, write it.  See Defining a middleware handler function.
  2. Register the middleware:

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:

  1. initial - The first point at which middleware can run.
  2. session - Prepare the session object.
  3. auth - Handle authentication and authorization.
  4. parse - Parse the request body.
  5. routes - HTTP routes implementing your application logic.  Middleware registered via the Express API app.useapp.routeapp.get (and other HTTP verbs) runs at the beginning of this phase.  Use this phase also for sub-apps like loopback/server/middleware/rest or loopback-explorer.

  6. files - Serve static assets (requests are hitting the file system here).

  7. 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:

  1. initial:before 
  2. initial
  3. 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 IDCode accessorExternal package
loopback#faviconloopback.favicon()serve-favicon
loopback#restloopback.rest()N/A
loopback#staticloopback.static()serve-static
loopback#statusloopback.status()N/A
loopback#tokenloopback.token()N/A
loopback#urlNotFoundloopback.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:

function myMiddlewareFunc([err,] req, res, next) { ... };

NameTypeOptional?Description
errObjectRequired for error-handling middleware.

Use only for error-handling middleware.

Error object, usually an instance or Error; for more information, see Error object.

reqObjectNo

The Express request object.

resObjectNo

The Express response object.

nextFunctionNoCall 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:

Regular middleware
return function myMiddleware(req, res, next) {
    // ...
}

Here is a constructor (factory) for this function; use this form when registering it in middleware.json:

Regular middleware
module.exports = function() {
  return function myMiddleware(req, res, next) {
    // ...
  }
}

An example a middleware function with four arguments, called only when an error was encountered.

Error handler middleware
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:

server/middleware.json
{
  "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:

phase[:sub-phase] : {
middlewarePath : {
[ enabled: [true | false], ]
[ params : paramSpec, ]
[ 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 or after.
  • 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 to require('loopback').favicon.

  • A file in module's server/middleware directory, for example require('loopback/server/middleware/favicon')

  • A file in modulesmiddleware directory, for example require('loopback/middleware/favicon')

Middleware configuration properties

 You can specify the following properties in each middleware section.  They are all optional:

PropertyTypeDescriptionDefault
enabledBoolean

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
paramsObject 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
pathStringSpecifies 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":

server/middleware.json
{
  ...
  "parse": {},
  "log": { ... },
  "routes": {}
  ...
}

Environment-specific configuration

You can further customize configuration through middleware.local.jsmiddleware.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:

server/server.js
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

Icon

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:

  1. 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.
  2. The middleware handler function (or just "middleware function").  See Defining a new middleware handler function.

For example:

server/server.js
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":

/server/server.js
app.use('/greet', function(req, res, next ) { 
  ... 
})
Icon

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:

server/server.js or server/boot/scripts.js
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:

server/server.js
...
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:

server/middleware.json
...
  "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.

Icon
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.
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):
  1. Add the code above to /server/middleware/tracker.js.
  2. Edit (or create) the file /server/middleware.json and register the new middleware in the "initial" phase:

    {
      "initial": {
        "./middleware/tracker": {}
      }
    }
  3. Start the application with slc run.
  4. 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

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 as express.errorHandler.

Example of a custom error processing middleware:

module.exports = function() { return function logError(err, req, res, next) { console.log('ERR', req.url, err); }; };
 
  1. Add the code above to /server/middleware/log-error.js.

  2. Edit /server/middleware.json and register the new middleware in the "final" phase: 

    { "final": { "./middleware/log-error": {} } }
  3. Start the application with slc run.

  4. Load http://localhost:3000/url-does-not-exist in your browser.