Node and Express - Middleware
Learn everything about Express in this Node and Express tutorial series. This article goes in depth on Express middleware including application, global, and router level middleware.
Table of Contents 📖
- What is Middleware?
- Adding another Middleware Function
- Middleware Function Signature
- Adding Even more Middleware
- Not Calling Next
- Code After Next is Still Executed
- Multiple HTTP Responses to One Request
- Global Middleware
- How to Write Global Middleware
- Application Level Middleware
- Router Level Middleware
- Middleware Order Matters
- Summary
What is Middleware?
Middleware is something that runs between the time the Express server gets a request and responds to the request. Thus, the callback function on every route we make in Express is middleware.
app.get('/', (request, response) => {
response.send('This is the home page!');
});
Above, when our Express application receives a GET request to "/" the callback function runs and then responds to the request.
Adding another Middleware Function
We can have multiple middleware functions that all run one after the other. So we can register as many middleware functions to a route as we want.
app.get('/', [
(request, response) => {
response.send('This is middleware number 1!');
},
(request, response) => {
response.send('This is middleware number 2!');
}
]);
To use multiple middleware on a route we need to pass an array as the second argument and then pass it multiple middleware functions. But how do we access the second middleware function in our route?
Middleware Function Signature
Middleware takes a third parameter in its function signature which by convention is called next.
app.get('/', [
(request, response, next) => {
response.send('This is middleware number 1!');
},
(request, response, next) => {
response.send('This is middleware number 2!');
}
]);
This third parameter, next, is a function that when called passes the HTTP request to the next middleware function.
app.get('/', [
(request, response, next) => {
next();
},
(request, response, next) => {
response.send('This is middleware number 2!');
}
]);
Now if we access localhost:1234/ we will get the response "This is middleware number 2!".
Adding Even more Middleware
We can add as many middleware functions as we want to a route.
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
next();
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
Now, "This is middleware number 4!" gets sent to the client and the middleware number will be logged to the console.
Not Calling Next
If we don't call the next function, then the middleware chain will be broken and the next middleware function in line will not be called. So if we got rid of next() in our second middleware function, only "middleware 1" would be printed to the console and a response would never be sent.
Code After Next is Still Executed
Something that is important to know about middleware is that code written after next() will still be run.
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
next();
console.log('middleware 2 after next()');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
Even though we called next() our program still came back around and ran the log statement in our second middleware function. If we want to ensure that the code after next isn't run we can call return next().
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
return next();
console.log('middleware 2 after next()');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
So it isn't uncommon to see return next() to ensure that the execution of the middleware function stops. The return keyword stops the execution of a function and returns a value if provided.
Multiple HTTP Responses to One Request
Something that could go wrong when calling next() without return is providing two responses to the client's request.
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
next();
response.send('This is middleware number 2!');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
We can see that the final middleware sends a response back to the client with the message "This is middleware number 4!". However, after this response has been sent our program moves back to our second middleware and sends another response. This error is because HTTP requires only one response per request. So here, when the client sends a GET request to localhost:1234/, our Express program should only respond once.
Global Middleware
So far we have been adding middleware functions to a route. However, we can also have global middleware functions in our Express programs. Global middleware functions will be run whenever a HTTP request is sent to any route. For example, we could have a global middleware that prints out the current time so we know the exact time each request was sent to our Express application.
How to Write Global Middleware
Global middleware is added using the method app.use(). App.use() takes a middleware function, or a function with the signature function(request, response, next).
app.use(function(request, response, next) {
console.log('This is global middleware!');
next();
});
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
return next();
response.send('This is middleware number 2!');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
"This is global middleware!" will now be printed to our console whenever a request is sent to any route in our application. So if we travel to localhost:1234/users we can see "This is global middleware!" printed to the console, same if we go to localhost:1234/posts, and so on.
Application Level Middleware
All the middleware functions we have worked with so far are application level middleware functions. Application middleware functions are functions placed inside app.use() and app.method() (app.get, app.post, etc.).
Router Level Middleware
Router level middleware works the same as application-level middleware but it is bound to an instance of express.Router(). In a previous article, we created two other index.js files, one inside /users and the other inside /posts. Both of these index files exported a Router object. We then required them and set them equal to the variables users and posts in our main index.js file.
const users = require('./routes/users');
const posts = require('./routes/posts');
app.use('/users', users);
app.use('/posts', posts);
What this code does is bind each middleware function we made inside users/index.js to the route /users and each middleware function we made inside /posts/index.js to the route /posts.
const express = require('express');
const router = express.Router();
router.get('/', (request, response) => {
response.send('This is all the users!')
});
router.get('/:username', (request, response) => {
if (request.query) {
Object.entries(request.query).forEach((object) => {
let [key, value] = object;
console.log(`${key} : ${value}`);
})
}
response.send(`This is ${request.params.username}'s profile!`);
});
router.get('/:username/:favChannel', (request, response) => {
response.send(`${request.params.username}'s favorite YouTube channel is ${request.params.favChannel}!`);
});
module.exports = router;
const express = require('express');
const router = express.Router();
router.get('/', (request, response) => {
response.send('This is the home page of the posts.')
});
router.get('/most-popular', (request, response) => {
response.send('These are the most popular posts.');
});
module.exports = router;
Now, every time a request is sent to localhost:1234/users the middleware attached to the express router in users/index.js will be called and whenever a request is sent to localhost:1234/posts the middleware attached to the express router in posts/index.js will be called.
Middleware Order Matters
The order that we place our middleware functions matters. For example, if we want our global middleware to run before each route we need to put it at the top of the page.
app.use(function(request, response, next) {
console.log('This is global middleware!');
next();
});
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
return next();
response.send('This is middleware number 2!');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
}
]);
If we want our global middleware to run after each route we need to put it at the bottom of the page. Also, we will have to call next() in our 4th middleware function if we want to reach the global middleware at the bottom of our page.
app.get('/', [
(request, response, next) => {
console.log('middleware 1');
next();
},
(request, response, next) => {
console.log('middleware 2');
return next();
response.send('This is middleware number 2!');
},
(request, response, next) => {
console.log('middleware 3');
next();
},
(request, response, next) => {
response.send('This is middleware number 4!');
next();
}
]);
app.use(function(request, response, next) {
console.log('This is global middleware!');
next();
});
Summary
But there we have it! In the next article we will be going over templates with Express. So until then thanks for checking out this article and please consider supporting me by clicking on the donation button at the top of the page so I can keep making this sort of content!