Middleware in Express
When I was learning Express, the idea of middleware always confused me. It still sorta does. So I’m going to write an article about it.
- As a way to further my own understanding.
- To help others.
Big Picture
First off, here’s the big picture:
In this scenario, the request starts off in the browser and ends up at C. A and B are middleware. They transform the request.
- A transforms the request from
POST / {num: 1}
toPOST / {num: 2}
. - B transforms the request from
POST / {num: 2}
toPOST / {num: 3}
.
In this context, middleware is just some code that transforms the incoming request.
Express
To understand how middleware works in Express, there’s a few things you have to understand:
- You could have multiple routes that match an incoming request.
- Routes are executed from top to bottom.
- If a route matches an incoming request, subsequent routes that match the incoming request won’t be hit if you don’t call
next()
.
Let me explain.
app.get('/', function(req, res) {
console.log('one');
});app.get('/', function(req, res) {
console.log('two');
});
Here’s what will happen if there’s an incoming GET /
request:
- It will match the first route.
- The code in the first route will be executed.
- “The buck stops here”. Even though the second route matches the incoming request.
So how would we get “two” to be logged as well?
app.get('/', function(req, res, next) {
console.log('one');
next();
});app.get('/', function(req, res) {
console.log('two');
});
Now…
GET /
outputs
one
two
Calling next()
allows “the buck to not stop here”.
Consider this:
app.get('/', function(req, res, next) {
console.log('one');
next();
});app.get('/foo', function(req, res) {
console.log('two');
});
What do you think would be the output after a GET /
request?
Answer:
one
“two” wouldn’t be logged because the second route doesn’t match the request. next()
just triggers the next matching route to run its callback.
Consider a different scenario:
app.get('/', function(req, res, next) {
console.log('one');
next();
});app.get('/', function(req, res) {
console.log('two');
});app.get('/', function(req, res) {
console.log('three');
});
What do you think the output will be?
Answer:
one
two
Why not one, two, three? Because next()
wasn’t called in the second route’s callback. next()
only “passes the buck” to the next matching route. Not to all matching routes.
So far we didn’t actually manipulate the request. Let’s see how to do that.
app.get('/', function(req, res, next) {
req.foo = 'bar';
next();
});app.get('/', function(req, res) {
console.log(req.foo);
});- - -GET /bar
The key point is that the request is an object, and it could be manipulated.
You could also pass multiple callbacks to app.VERB()
. Like this:
app.get(
'/',
function(req, res, next) {
console.log('one');
next();
},
function(req, res, next) {
console.log('two');
next();
},
function(req, res) {
console.log('three');
}
);- - -GET /one
two
three
app.use()
It works like this:
app.use('/', cb);
/*
"matches everything that starts with /"
matches /
matches /foo
matches /foo/bar
*/app.get('/', cb);
/*
"only matches /"
matches /
doesn't match /foo
doesn't match /foo/bar
*/
So app.use()
could be used as middleware.
app.use('/', function(req, res, next) {
console.log('one');
next();
});app.get('/', function(req, res) {
console.log('two');
});- - -GET /one
two
If the callback is passed as the first argument to app.use()
, it matches all routes.
app.use(function(req, res, next) {
console.log('one');
});app.get('/sdljfsdjlf/sdfsj/dsfsdfs', function(req, res) {
console.log('two');
});- - -GET /sdljfsdjlf/sdfsj/dsfsdfsone
two
But remember that order matters.
app.get('/', function(req, res) {
console.log('two');
});app.use(function(req, res, next) {
console.log('one');
next();
});- - -GET /two
Also check out these two Stack Overflow questions: