When your backend server starts small, having all your routes inside a single app.js file feels simple and convenient.
But as your application grows, more pages, more API endpoints, more middleware. It becomes harder to maintain. This is exactly where modular routing in Express.js shines.
Day 26 was about how modular routing works, why it matters, and how it keeps your project clean and scalable.
🚩 The Problem: One Massive app.js
If you don’t break routes into separate files, your main server file might look like this:
// app.js
app.get('/users', ...)
app.post('/users', ...)
app.get('/products', ...)
app.post('/products', ...)
// and eventually it becomes unmanageable...
This gets messy fast—especially once middleware, controllers, authentication, or validation get added.
✅ The Solution: Modular Routing
Modular routing lets you split related routes into separate files using the Express Router object.
It keeps your project organized by grouping endpoints by feature (users, products, posts, auth, etc.).
🗂 Example: Modular Routing Done Right
/routes/users.js
const router = require('express').Router();
// GET /users
router.get('/', (req, res) => {
res.send("Get all users");
});
// POST /users
router.post('/', (req, res) => {
res.send("Create a user");
});
module.exports = router;
/routes/products.js
const router = require('express').Router();
// GET /products
router.get('/', (req, res) => {
res.send("Get all products");
});
// POST /products
router.post('/', (req, res) => {
res.send("Create a product");
});
module.exports = router;
Clean app.js
const express = require('express');
const app = express();
const usersRouter = require('./routes/users');
const productsRouter = require('./routes/products');
app.use('/users', usersRouter);
app.use('/products', productsRouter);
app.listen(3000, () => console.log("Server running on port 3000"));
Your app.js finally stays clean and easy to read.
🔍 How Modular Routing Works in Express.js
Below are the core concepts that make modular routing so powerful.
1. The Express Router Object
express.Router() creates isolated mini-apps that you attach to your main app.
Each router supports its own routes, middleware, and logic.
Think of routers as small “sub-apps” that plug into your main Express instance.
2. Route Prefixing
When you use:
app.use('/users', usersRouter);
Every route inside the users router automatically inherits /users as a prefix.
So inside users.js, this:
router.get('/');
Becomes:
GET /users
This is incredibly helpful for structuring RESTful APIs.
3. Splitting by Feature (Best Practice)
A common, scalable structure:
/routes
users.js
products.js
auth.js
orders.js
/controllers
usersController.js
productsController.js
app.js
Each file focuses on one feature or resource, which improves:
- readability
- maintainability
- teamwork
- testing
4. Route-Level Middleware
Routers can have their own middleware—like authentication, validation, or logging.
router.use((req, res, next) => {
console.log("Users route hit");
next();
});
Or middleware for a specific route:
router.get('/', authMiddleware, getUsers);
This keeps the logic separated and prevents your main app from being bloated.
5. Controllers (Basics)
For even cleaner projects, routes and logic are split:
Route file
router.get('/', usersController.getAllUsers);
Controller file
exports.getAllUsers = (req, res) => {
res.send("Get all users from controller");
};
This is the foundation of scalable Express apps and prepares you for MVC-style architecture.
🎯 Final Thoughts
Modular routing is one of the first major steps toward building maintainable backend applications. Breaking routes into separate files, organizing them by feature, and using controllers makes your code cleaner and easier to grow.
It is essential for backend applications to maintain structure, even as they scale to dozens or hundreds of routes.
If you're learning Express.js, mastering modular routing early will save you a lot of headaches later!
Happy coding!
Top comments (0)