Commit Logs

Basic Middleware in ExpressJS

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
2
3
4
5
6
7
8
9
10
var app = require('express')

app.get('/', function(req, res)) {
console.log('/: route terminated');
res.send('homepage');
});

app.get('/', function(req, res)) {
console.log('/: never called');
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var app = require('express')

app.get('/', function(req, res, next)) {
console.log('/: route not terminated');
next();
});

app.get('/', function(req, res, next)) {
console.log('/: always called');
res.send('homepage');
});

app.get('/', function(req, res, next)) {
console.log('/: never called');
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var app = require('express')

app.get('/', function(req, res, next)) {
console.log('/: route not terminated');
next();
});

app.use(function(req, res, next)) {
console.log('always called');
res.send('could be any pages');
});

app.use(function(req, res, next)) {
console.log('never called');
});

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
2
3
4
5
6
7
8
9
10
11
var app = require('express')

app.get('/', function(req, res, next)) {
console.log('/: route terminated');
res.send('homepage');
});

app.use(function(req, res)) {
console.log('route not handled');
res.send('404 - Not Found');
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
var app = require('express')

app.use(function(req, res, next){
console.log('\n\nALLWAYS');
next();
});

app.get('/a', function(req, res){
console.log('/a: route terminated');
res.send('a');
});

app.get('/a', function(req, res){
console.log('/a: never called');
});

app.get('/b', function(req, res, next){
console.log('/b: route not terminated');
next();
});

app.use(function(req, res, next){
console.log('SOMETIMES');
next();
});

app.get('/b', function(req, res, next){
console.log('/b (part 2): error thrown' );
throw new Error('b failed');
});

app.use('/b', function(err, req, res, next){
console.log('/b error detected and passed on');
next(err);
});

app.get('/c', function(err, req){
console.log('/c: error thrown');
throw new Error('c failed');
});

app.use('/c', function(err, req, res, next){
console.log('/c: error deteccted but not passed on');
next();
});

app.use(function(err, req, res, next){
console.log('unhandled error detected: ' + err.message);
res.send('500 - server error');
});

app.use(function(req, res){
console.log('route not handled');
res.send('404 - not found');
});

app.listen(3000, function(){
console.log('listening on 3000');
});

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