In our last post, we set up our first basic Express server. Now it’s time to dive deeper and understand the two most fundamental building blocks of any Express application: Middleware and Routing. Mastering these ExpressJS core concepts is essential for building powerful, organized, and scalable web applications.
Think of these concepts as the nervous system of your app—they control the flow of requests and direct them to the right place to be processed. Let’s break them down.
Understanding Middleware in ExpressJS
So, what is middleware? The simplest analogy is an assembly line. When a request comes into your server, it passes through a series of middleware functions, one by one. Each function can inspect the request, modify it, end the response cycle, or pass control to the next function in the line.
Every middleware function has access to the request object (`req`), the response object (`res`), and the `next()` function. Calling `next()` is crucial—it passes control to the next middleware in the stack. If you don’t call `next()` or send a response, the request will be left hanging!
Application-level Middleware
This is the most common type of middleware. It’s bound to the entire application instance using `app.use()` or `app.METHOD()`. A great example is a simple logger that prints details for every incoming request.
// A simple logger middleware
app.use((req, res, next) => {
console.log(`Request Received: ${req.method} ${req.url}`);
next(); // Pass control to the next handler
});
Router-level Middleware
Router-level middleware works in the same way as application-level middleware, but it’s bound to an instance of `express.Router()`. This is incredibly useful for creating modular code, for example, by applying specific middleware only to a group of routes (like an `/api/` section).
Built-in Middleware
Express comes with a few powerful built-in middleware functions that you’ll use in almost every project. The two most important are:
- `express.json()`: This middleware parses incoming requests with JSON payloads. It’s what allows you to access `req.body` when a client sends JSON data.
- `express.urlencoded()`: This parses incoming requests with URL-encoded payloads, typically from HTML forms.
You would use them in your main application file like this:
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
Error-handling Middleware
This is a special type of middleware that has four arguments instead of three: `(err, req, res, next)`. Express recognizes this signature and will only call this function when an error occurs in the preceding middleware stack. It’s your safety net for catching and handling server errors gracefully.
Read Me: What is ExpressJS? A Beginner’s Guide to Get Started
Read Me: Effortless Guide to Setting Up a React Development Environment on Windows 11
Routing in ExpressJS
ExpressJS routing determines how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, etc.). Each route can have one or more handler functions, which are executed when the route is matched.
Basic Route Methods
The basic structure of a route is `app.METHOD(PATH, HANDLER)`. Here are the most common methods:
- `app.get()`: Handles GET requests, typically used for retrieving data.
- `app.post()`: Handles POST requests, used for creating new data.
- `app.put()`: Handles PUT requests, used for updating existing data.
- `app.delete()`: Handles DELETE requests, used for deleting data.
// GET route to fetch all users
app.get('/users', (req, res) => { res.send('List of all users'); });
// POST route to create a new user
app.post('/users', (req, res) => { res.send('New user created'); });
Route Parameters & Query Parameters
Often, you need to capture dynamic values from the URL. Express provides two ways to do this:
Route Parameters (`req.params`): These are named URL segments used to capture values. They are defined with a colon (`:`).
// URL: /users/123
app.get('/users/:userId', (req, res) => {
res.send(`User profile for user ID: ${req.params.userId}`); // Outputs: User profile for user ID: 123
});
Query Parameters (`req.query`): These are key-value pairs appended to the end of a URL after a `?`. They are commonly used for filtering, sorting, or pagination.
// URL: /search?q=express&sort=asc
app.get('/search', (req, res) => {
const query = req.query.q;
const sortBy = req.query.sort;
res.send(`Searching for "${query}" and sorting by "${sortBy}"`);
});
Using `express.Router()` for Modular Routes
As your application grows, defining all your routes in a single file becomes messy. `express.Router` is a mini-Express application that allows you to group route handlers and middleware into separate files.
1. Create a router file (e.g., `routes/users.js`):
const express = require('express');
const router = express.Router();
// All routes here are automatically prefixed with /users
router.get('/', (req, res) => { res.send('Get all users'); });
router.get('/:id', (req, res) => { res.send('Get user by ID'); });
module.exports = router;
2. Mount the router in your main app file (e.g., `index.js`):
const userRoutes = require('./routes/users');
// Mount the user router on the /users path
app.use('/users', userRoutes);
Now, a request to `/users` in your main app will be handled by the router in `users.js`.
Conclusion
Middleware and routing are the heart and soul of Express. Middleware gives you control over the request-response cycle, while routing provides the structure for your application’s endpoints. By understanding and using these concepts effectively, especially `express.Router`, you can build clean, organized, and powerful backend services.
Next, we’ll explore how to connect our Express app to a database to persist data. [Read our guide on connecting Express to MongoDB here].
Frequently Asked Questions (FAQ)
Can I use multiple middleware functions for a single route?
Absolutely. You can chain as many middleware functions as you need. Just pass them as arguments before your final route handler, and ensure each one calls `next()` to pass control to the next in the chain.
What is the difference between `app.use()` and `app.get()`?
`app.use(‘/path’, …)` will match any HTTP method (GET, POST, etc.) for that path and its sub-paths. `app.get(‘/path’, …)` is more specific and will only match GET requests to that exact path.
How do I send a JSON response from a route?
Instead of `res.send()`, use the `res.json()` method. It automatically sets the correct `Content-Type` header to `application/json`. For example: `res.json({ user: ‘John Doe’ })`. Learn more about response methods in the official Express routing documentation.
