Understanding Middleware in Express.js
Express.js is a backend framework built for Node.js that simplifies building server side applications. This post covers Middleware, the core concept of 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?
Middleware refers to functions that manage incoming requests from and outgoing responses to the client.There are two types of middleware in Express: application level and router level.
Below, 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 functions that run on the application level?Also frequently referred to as the global level are bound to an instance of the app
object. They 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 function.
-
Method specific middleware like
app.get(...)
andapp.post(...)
only runs for requests matching that HTTP method. These functions:- 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(); }); // Middleware that handles requests to '/' app.get('/', (req, res) => { res.send('Welcome to the homepage!'); }); // Middleware that handles requests to '/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 function | • next() is called, passing control to the next middleware function |
• app.get('/', ...) is executed, and the welcome message is sent back | • The middleware for /about is executed, and the response About page is sent back |
For the sake of completeness, here's an example of a middleware function 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'); });
Router-level middleware is not global because it's isolated to specific request paths, unlike application-level middleware which can affect all routes.
In the example, the /profile
route is mounted under the /users
path, so
it will be accessible at /users/profile
.
Summary
Express.js middleware functions handle requests and responses between client and server. There are two main types:
Application-level middleware:
- Bound to the app object
- Can be method-agnostic
app.use()
for all requests - Can be method-specific
app.get()
,app.post()
, etc. - Often used for logging, parsing, authentication
Router-level middleware:
- Bound to
express.Router
instances - Only runs for routes mounted under specific paths
- Enables modular organization of related routes
Middleware functions can modify request and response objects and must call next()
to continue the request flow.