Process HTTP Requests in Express
19 min read·Jan 1, 2025
In Express, incoming HTTP requests can be intercepted before they reach a route's request listener using a special type of function called a middleware.
These middlewares are at the core of Express' design and are used for a variety of actions, such as logging requests, verifying request headers, parsing request payloads, and so on.
Define middleware functions
Just like a request listener, a middleware has access to the request and response objects, as well as an additional parameter named next.
function middleware(req, res, next) {
//
}
Modify the request-response lifecycle
Beyond executing internal logic, a middleware can terminate the request-response lifecycle by sending an immediate response to the client using one of the methods of the response object, such as send() or sendStatus():
function middleware(req, res, next) {
// Execute internal logic
res.sendStatus(200);
}
Or it can forward the request to the next middleware (or request listener) in the call stack, by invoking its next() argument, which is actually a function — as without it, the request will be left hanging until it times out:
function middleware(req, res, next) {
// Execute internal logic
next();
}
Note: Any modifications made to the
requestandresponseobjects will be preserved and passed on to each component of the middleware stack, which is useful if you want to forward custom properties such as IDs or connection tokens.
Use middleware functions
In Express, middleware functions can be used at three different levels:
- The application-level.
- The route-level.
- The router-level.
Application-level middlewares
An application-level middleware is a function used to intercept all requests or groups of requests as soon as they reach the server and before they are routed.
The most common use cases for this type of middleware include request logging, headers checking, and error handling.
These middlewares are bound to the application object through its use() method:
server.use([PATH,] MIDDLEWARE [, MIDDLEWARE]);
Where:
serveris an Express instance created using the top-levelexpress()function.PATHis an optional mount path, a path pattern, a regular expression, or an array of combinations of these three.MIDDLEWAREis a single middleware function or a series of middleware functions.
Example
Let's consider this server, whose application-level middleware will log every incoming HTTP request into a file named server.log:
const express = require('express');
const { appendFileSync } = require('node:fs');
const server = express();
server.use((req, res, next) => {
try {
const log = `${new Date().getTime()} ${req.ip} ${req.method} ${req.path}\n`;
appendFileSync('server.log', log);
} catch(error) {
console.error(error.message);
} finally {
next();
}
});
server.all('*', (req, res) => {
res.sendStatus(200);
});
server.listen(3000);
When executed, it will:
- Catch every single incoming HTTP request reaching the server.
- Log the request's timestamp (
new Date().getTime()), the client's IP address (req.ip), the request's verb (req.method), and the request's relative URL (req.path) into a file namedserver.log. - Forward the request to the next route using the
next()handler. - Catch every single incoming HTTP request using the
app.all()method combined with the*path. - Respond to the client with a HTTP
200 OKusing theres.sendStatus()method.
When sending the following HTTP requests to the server:
$ curl 127.0.0.1:3000/
OK
$ curl 127.0.0.1:3000/foo/bar
OK
$ curl -X POST 127.0.0.1:3000/bar
OK
The server will write these logs into the server.log file:
$ cat server.log
1732108282051 ::ffff:127.0.0.1 GET /
1732108297337 ::ffff:127.0.0.1 GET /foo/bar
1732108304042 ::ffff:127.0.0.1 POST /bar
Route-level middlewares
A route-level middleware is a function used to intercept all requests before they reach the request listener of a specified route.
These middlewares are declared in the route method, between the path string and the request listener:
server.METHOD(PATH, MIDDLEWARE[, MIDDLEWARE], HANDLER);
Where:
METHODis a function representing an HTTP method.PATHis a string of characters representing a URN.MIDDLEWAREis a single middleware function or a series of middleware functions.HANDLERis a request listener function.
Example
Let's consider this server, whose route-level middleware will check if the client accepts a response in the HTML format:
const express = require('express');
const server = express();
server.get('/',
(req, res, next) => {
const mediaTypes = /(text\/html|text\/\*|\*\/\*)/;
if (!mediaTypes.test(req.headers?.accept)) {
return res.sendStatus(406);
}
next();
},
(req, res) => {
res
.set('Content-Type', 'text/html')
.send('<!DOCTYPE html><html><head><title>Home</title></head><body><h1>Welcome to Learn Backend</h1></body></html>');
}
);
server.listen(3000);
When executed, it will:
- Check if the
AcceptHTTP request header is defined and includes the media types'text/html','text/*', or'*/*'indicating that the client accepts a response in the HTML format, any text format, or any format at all. - Break the request-response lifecycle by responding with an HTTP
406 Not Acceptableindicating that the server could not produce a response matching the list of acceptable formats, if it evaluates tofalse. - Forward the request to the route's controller using the
next()handler, if it evaluates totrue. - Respond with an HTTP
200 OKcontaining an HTML string, including theContent-Typeheader indicating that the response's body is in the HTML format.
When sending this HTTP request to the server, it should respond with an HTTP 200 OK:
$ curl -i -H 'Accept: */*' 127.0.0.1:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 106
ETag: W/"6a-mXUkrHMj/FAM+Xf2eKe6Tg7Ubo0"
Date: Wed, 20 Nov 2024 12:00:27 GMT
Connection: keep-alive
Keep-Alive: timeout=5
<!DOCTYPE html><html><head><title>Home</title></head><body><h1>Welcome to Learn Backend</h1></body></html>
Note: You can also try it in your browser at http://127.0.0.1:3000.
When sending this HTTP request to the server, it should respond with an HTTP 406 Not Acceptable:
$ curl -i -H 'Accept: application/json' 127.0.0.1:3000
HTTP/1.1 406 Not Acceptable
X-Powered-By: Express
Content-Type: text/plain; charset=utf-8
Content-Length: 14
ETag: W/"e-dqk0oyZuM+x+D21Lq3ZqYJ94/o4"
Date: Wed, 20 Nov 2024 12:01:01 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Not Acceptable
Router-level middlewares
A router-level middleware is a function used to intercept all requests as soon as they reach the server and before they are forwarded to a specified router.
Just like application-level middlewares, these middlewares are bound to the application object through its use() method, between the optional path string and the router:
server.use([PATH,] MIDDLEWARE [, MIDDLEWARE], ROUTER);
Where:
serveris an Express instance created using the top-levelexpress()function.PATHis an optional mount path, a path pattern, a regular expression, or an array of combinations of these three.MIDDLEWAREis a single middleware function or a series of middleware functions.ROUTERis a router instance created using theexpress.Router()function.
Example
Let's consider this server, whose router-level middleware will check if the client has exceeded its access limit:
const express = require('express');
const server = express();
const router = express.Router();
let clients = {};
router.get('/forecast', (req, res) => res.send('sunny'));
router.get('/airquality', (req, res) => res.send('good'));
server.use('/weather', (req, res, next) => {
if (!clients[req.ip]) {
clients[req.ip] = 1;
} else {
clients[req.ip] += 1;
if (clients[req.ip] > 2) {
return res.sendStatus(429);
}
}
next();
}, router);
server.listen(3000);
When executed, it will:
- Catch every single incoming HTTP request reaching the server on the
/weatherpath. - Check if the client has already accessed the router's
GET /weather/forecastandGET /weather/airqualityendpoints more than 2 times based on its IP address. - Break the request-response lifecycle by responding with an HTTP
429 Too Many Requestsindicating that the client has sent too many requests. - Forward the request to the appropriate router's endpoint using the
next()handler. - Respond in both cases with an HTTP
200 OKcontaining a string.
When sending these HTTP requests to the server, it should respond with an HTTP 200 OK:
$ curl 127.0.0.1:3000/weather/forecast
sunny
$ curl 127.0.0.1:3000/weather/airquality
good
When sending this HTTP request to the server, it should respond with an HTTP 429 Too Many Requests:
$ curl 127.0.0.1:3000/weather/forecast
Too Many Requests
Summary
Here's a summary of what you've learned in this lesson:
- Middleware functions are used to intercept incoming HTTP requests.
- Middleware functions have access to the request object, the response object, and a
next()handler. - Middleware functions can break the request-response lifecycle by sending an immediate response to the client using methods such as
send(),sendStatus(), etc. - Middleware functions can forward requests to the next component in the call stack using the
next()handler. - Application-level middlewares are attached to the application using the following syntax:
app.use([PATH,] MIDDLEWARE [, MIDDLEWARE]). - Route-level middlewares are attached to routes using the following syntax:
app.METHOD(PATH, MIDDLEWARE[, MIDDLEWARE], HANDLER). - Router-level middlewares are attached to routers using the following syntax:
app.use([PATH,] MIDDLEWARE [, MIDDLEWARE], ROUTER).
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery