ExpressJS is an unopinionated, minimalist web framework for NodeJS. Among many others, middleware is an important concept to build any ExpressJS applications. Its significance is clear from the following paragraph in the official ExpressJS tutorial,
Express is a routing and middleware web framework that has minimal functionality of its own: An Express application is essentially a series of middleware function calls.
As part of my own learning, we are going to look at a few examples in this post in the hope to understand how middleware functions
- execute any code
- make changes to the request and the response objects
- end the request-response cycle
- call the next middleware function in the stack
Note that I borrow many examples from this awesome book Web Development with Node & Express by @EthanRBrown and you could find more interesting stuff from the book.
The absolute basics
Most of the middleware functions take three parameters req
(a request object), res
(a response object) and next
(the next middleware function), although some have the additional err
parameter for error handling. And these functions are executed in a pipeline, i.e. ordered, fashion. Middleware functions can be inserted into the pipeline by calling app.use()
and passed by calling next()
.
The usual route handler app.VERB
is a special type of middleware that handles specific HTTP verbs, e.g. GET
, POST
, etc and it requires a path as its first parameter.
1 | var app = require('express') |
In the above example, the request terminates with the first route handler. Suppose we rewrite the first route handler with a next()
function, the request will be passed onto the next handler. Note that, if a middleware doesn’t call next()
, it would terminate a request and it should always send something to the client; otherwise, the client would hang and eventually timeout. On the other side, when there is a next()
, nothing should be sent to the client at the moment.
1 | var app = require('express') |
Similarly, a middleware function can be thought as a route handler that handles all HTTP request and, thus, it doesn’t need a path parameter. We could replace some of the above route handlers with middleware functions, but the results would change accordingly.
1 | var app = require('express') |
Admittedly, this is not a particularly interesting example, but note how the results change because middleware doesn’t have a specified path. Also, this is not a typical use of middleware functions. Instead, there is a commom pratice to have a “catch-all” middleware function as the very last one that returns a status code 404 (Not Found).
1 | var app = require('express') |
The not-so-basic basics
The pipeline logic of route handlers and middleware functions should be pretty clear in the above examples, but things may get a bit tricky in the real world when there are many route handlers and middleware functions nested together. The logic stays the same, but it would need extra care to make sure users can access the right content.
To make my point, I am borrowing an example from @EthanRBrown‘s book as a simplified version of the real world. You should be able to walk through it with the described logic as long as you look closely.
1 | var app = require('express') |
Notice that, if a request goes to \b
, the client would see a 404
, but, if a request goes to \c
instead, it would get a 500
, depending on whether an err
is passed to the catch-all middleware.
Summary
Hope you get a good grasp of how middleware works with ExpressJS. With a large, active community, there are many useful middleware functions being developed and most of the times you don’t need to reinvent the wheel. Although, unfortunately, there currently isn’t an index for all the middleware available, one thing you could try is to search npm
for “Express”, “Connect” and “Middleware”.