When you build an API with Express.js, routes are the doors through which requests enter your application, and routers are the architectural wings that organize those doors into clean, manageable sections. Whether you're building a small service or a sprawling backend, mastering routes and routers is essential for clarity, scalability, and maintainability.
This article dives into what routes are, how routers work internally, why modular routing matters, and how to structure a real project using Express routers.
What Are Routes in Express.js?
A route defines how your application responds to a specific HTTP request on a given path.
Each route specifies:
- a URL path
- an HTTP method
- one or more handler functions
Example:
app.get('/users', (req, res) => {
res.send('List of users');
});
Here:
-
/usersis the route path -
GETis the method - The callback function is the route handler
Think of routes as instructions that say:
“Whenever a request comes to this path, using this method, execute this code.”
Route Methods
Express supports all standard HTTP verbs:
app.get('/items', handler);
app.post('/items', handler);
app.put('/items/:id', handler);
app.patch('/items/:id', handler);
app.delete('/items/:id', handler);
There’s also:
app.all('*', handler); // handles any method
This is useful for catch-all handlers (e.g., global fallbacks).
Route Handlers
A route can have multiple handler functions, creating a mini middleware chain:
app.get('/profile',
checkAuth,
checkStatus,
(req, res) => {
res.json({ user: req.user });
}
);
Express executes them in order until a handler sends a response.
Introducing the Express Router
Using app directly for all routes works only for small projects.
Once an application grows, everything becomes tangled.
Enter the Router:
const router = express.Router();
A router is a mini Express application that you attach to the main app. It has:
- its own middleware
- its own routes
- its own configuration
The router helps you break your API into modules.
Why Use Routers?
Routers give you:
1. Better Structure
Group related endpoints together.
2. Clean Separation of Concerns
Authentication routes are not mixed with product routes.
3. Scalability
Routers can be nested and expanded easily.
4. Reusable Middleware
Attach middleware to only one section of your API.
Creating a Router
Example: A router for user-related endpoints.
users.routes.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('All users');
});
router.post('/', (req, res) => {
res.send('Create user');
});
router.get('/:id', (req, res) => {
res.send(`User with ID ${req.params.id}`);
});
module.exports = router;
Attach it to your main app:
app.js
const usersRouter = require('./routes/users.routes');
app.use('/users', usersRouter);
Now:
-
GET /usersmaps to router.get('/') -
POST /usersmaps to router.post('/') -
GET /users/:idmaps to router.get('/:id')
Route-Level Middleware Using Routers
You can attach middleware only to a specific router:
router.use(checkAuth);
Or only to a specific route:
router.get('/:id', checkRole('admin'), handler);
This avoids cluttering your entire app with global middleware.
Nested Routers
Routers can be nested to build clean hierarchies.
Example:
/api
/v1
/users
/products
/orders
In code:
const apiRouter = express.Router();
const v1Router = express.Router();
v1Router.use('/users', usersRouter);
v1Router.use('/products', productsRouter);
v1Router.use('/orders', ordersRouter);
apiRouter.use('/v1', v1Router);
app.use('/api', apiRouter);
This is the structure used by most large production APIs.
Route Parameters
Express makes it easy to capture dynamic URL values:
router.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});
Route parameters behave like variables within the URL.
Understanding How Express Stores Routes Internally
Express keeps an internal stack in:
app._router.stack
router.stack
Each layer looks like:
{
route: {
path: '/users',
stack: [handlers...],
methods: { get: true }
},
handle: fn
}
Routers store their own stacks and the main app chains them together.
When a request arrives, Express walks through this stack in order, matching the path and method, and executing handlers.
Real Project Folder Structure Example
A clean Express project might look like this:
project/
app.js
routes/
users.routes.js
products.routes.js
orders.routes.js
controllers/
users.controller.js
products.controller.js
orders.controller.js
middlewares/
auth.js
rateLimit.js
validate.js
Your app.js becomes a simple, readable entry point:
app.use('/users', require('./routes/users.routes'));
app.use('/products', require('./routes/products.routes'));
app.use('/orders', require('./routes/orders.routes'));
This modularization is the hallmark of well-structured Express apps.
Final Thoughts
Routes and routers in Express.js give you the tools to design APIs that are clean, scalable, and organized. While routes define how your app responds to requests, routers allow you to separate concerns and maintain order as your application grows.
Once you adopt router-based organization, your Express apps become easier to understand, maintain, and extend. For large projects, routers aren’t just helpful—they’re essential.
Top comments (0)