Understanding Middleware in Express.js
In the world of Express, middleware is the most important concept. Or, as the documentation puts it:
"An Express application is essentially a series of middleware function calls."
But first, what is middleware? As the name suggests, it's a thing that sits in the middle of something. Specifically, it sits between incoming requests from and outgoing responses to the client.
To manage incoming requests on the server, Express documentation offers two types of middleware: application level and router level.
To begin, I'll briefly outline how to send requests to the server. Next, we'll look at each of the middleware types in turn.
Sending Requests
The middleware on the server is triggered when a request is received. If you're developing locally, you could send a GET
request by using a curl
command like this:
curl -X GET http://localhost:3000/posts
From the frontend, you could use the fetch
API which would look like something like this:
// Async function to get posts const getPosts = async () => { try { const response = await fetch('/posts') const posts = await response.json() return posts } catch (error) { console.error('Error:', error) } } // Call the function const posts = await getPosts()
Now, let's look at the two types of middleware.
Application level Middleware
Middleware that runs on the application level can be split into two categories:
-
Method agnostic middleware using
app.use(...)
runs for every incoming request, regardless of HTTP method. This type of middleware commonly:- Modifies requests by parsing JSON bodies, adding headers, or handling logging
- Can stop the request-response cycle early (for example, if authentication fails)
- Must call
next()
to pass control to subsequent middleware or route handlers
-
Method specific middleware like
app.get(...)
andapp.post(...)
only runs for requests matching that HTTP method. These handlers:- Process the request by interacting with databases, transforming data, or executing business logic
- Usually end the cycle by sending a response with
res.send()
orres.json()
- Can optionally call
next()
to continue the middleware chain
Here's an example that includes both method agnostic and method specific types of middleware:
const express = require('express'); const app = express(); // Middleware that runs for every request app.use((req, res, next) => { console.log('Logging request method:', req.method); next(); }); // Route handler for '/' app.get('/', (req, res) => { res.send('Welcome to the homepage!'); }); // Route handler for '/about' app.get('/about', (req, res) => { res.send('About page'); }); app.listen(3005);
Let's compare what happens when you visit /
and /about
:
/ | /about |
---|---|
• Middleware is triggered and logs the HTTP method req.method | • Middleware is triggered and logs the HTTP method req.method |
• next() is called, passing control to the next middleware or route handler | • next() is called, passing control to the next middleware or route handler |
• app.get('/', ...) is executed, and the welcome message is sent back | • The route handler for /about is executed, and the response About page is sent back |
For the sake of completeness, here's an example for each of the most important HTTP methods:
// GET Request app.get('/posts', (req, res) => res.send('Get all posts')) // POST Request app.post('/posts', (req, res) => res.send('Create new post')) // PUT Request app.put('/posts/:id', (req, res) => res.send('Update a post')) // DELETE Request app.delete('/posts/:id', (req, res) => res.send('Delete a post'))
Router level Middleware
The router-level middleware will only run for routes that are mounted under a specific path. To understand this, consider the example below:
const userRouter = express.Router(); // Mount the router under the specific path '/users' app.use('/users', userRouter); // This route will be at /users/profile userRouter.get('/profile', (req, res) => { res.send('User profile page'); });
The /profile
route will only be mounted under the /users
path, so
it will be accessible at /users/profile
.
To summarize: Application level middleware applies globally, while router level middleware applies relative to its mounted route(s).