If you’ve used Ruby on Rails, Sinatra, Express.js or any other modern web framework, you’ve likely encountered the term Middleware. In this short post I’ll explain what middleware is, and how to write your own. Most of my explanation will be in the context of a Node.js Express web server.
What problem does middleware solve? Why would I use it?
Any time you type a url into your browser (the client) address bar, you are requesting information from a web server. This web server then sends a text response back to the your browser. The browser then interprets and converts that text response into text content, styling, images or even video.
Say you’re running a web application on a web server with Node.js and Express. In this application, let’s say you certain pages that require that you log in.
When the web server receives a request for data, Express gives you a request
object with information about the user and the data they are requesting. You can see their IP address. What language their browser is set to. What url they are requesting, and what parameters they have passed along. Express also gives you access to a response
object that you can modify before the web server respond to the user. These objects are usually shortened to req
, res
.
Middleware functions are the perfect place to modify the req
and res
objects with relevant information. For instance, after a user has logged in, you could fetch their user details from a database, and then store those details in res.user
.
In short, middleware gives you access to req
and res
in the apps request-> response cycle.
What does a middleware function look like?
var app = express();
// We're telling Express to call this middleware function every time a request is received on
// any route for the express server.
app.use(function (req, res, next) {
console.log("Time:", Date.now());
next();
});
A middleware function example from the Express docs: https://expressjs.com/en/guide/using-middleware.html
There are several important things to point out here:
- Middleware functions usually have 3 standard params
req
,res
, andnext
. The first two are objects, the last is a function that will call the next middleware function, if there is one. - Usually there is a middleware chain, meaning a chain of functions that are called one after the other, with the last function sending the response back to the browser. So we get the request from the browser, make any modifications and data additions, and then send a response back.
- You must call
next()
(unless it’s the last function in the chain) or the request will just hang and eventually timeout. In the browser this will manifest as a really long spinner before a message of “connection timed out” or similar. - Any changes you make to
req
orres
will be available in the next middleware function. req
andres
are unique for each request. Meaning that a user from USA result in a differentreq
object than a user from a European country.
How do I use existing middleware?
If you’re using Express, you’re likely already using middleware. There are many Express middleware function available.
Say for instance that we wanted to record how long each request takes to be sent back to the browser. We’d add the existing middleware function for that:
var express = require("express");
var responseTime = require("response-time");
var app = express();
// calculates the time it takes each request to respond back to the browser.
// We could use this middleware function to log it to the console, or send it
// to a database or 3rd party API service for graphing.
app.use(
responseTime(function (req, res, time) {
console.log("time", time);
next();
}),
);
app.get("/", function (req, res) {
res.send("hello, world!");
});
Example of using the response-time middleware: href="https://expressjs.com/en/resources/middleware/response-time.html
How do I write my own middleware for certain routes?
This is where middleware is the most useful in my opinion.
var express = require('express');
var app = express();
// if logged in, continue, else redirect to login page
function isAuth(req, res, next) {
if (res.user && res.user.isLoggedIn) {
return next()
}
res.redirect('/login');
}
// we've got a basic route here where a user types ie: '/user/john82' into the browser.
// we've applied the 'isAuth' middleware function in the middleware chain right before
// the response is sent back to the user
app.get('/user/:id', isAuth, function(req, res, next) {
res.send('Hello: ' + res.user.name);
})
Authentication middleware redirects a user back to a login page if they are not logged in
When we run the above code, and request the /user/someName
route, the server will check to see if the user is already logged in. If they are not, the server tells the browser to redirect to the /login
route.
In conclusion
Middleware functions are a really great way to run code on each request, or on each request for a certain route, and to take action on request or response data. Middleware is a crucial piece of any modern web server, and is incredibly useful.