Express Router: Grouping and Mounting Explained
express.Router lets you group routes by feature. Here is how to use it well.
Express Router: Grouping and Mounting Explained
express.Router is a mini-app you can mount under a path. It is how you keep your Express code organized as it grows.
Why Use express.Router
Putting every route in app.js works for tiny apps. As features grow, app.js becomes a mess. express.Router lets you split routes by feature (auth, users, feed, chat) into separate files.
Creating a Router
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
router.patch('/:id', updateUser);
router.delete('/:id', deleteUser);
module.exports = router;
Mounting the Router
// app.js
app.use('/users', require('./routes/users'));
Now /users hits router.get('/', ...), /users/:id hits router.get('/:id', ...), and so on. The router's paths are relative to the mount path.
Per-Router Middlewares
You can apply middlewares to all routes in a router:
// routes/admin.js
const router = express.Router();
router.use(authenticate);
router.use(requireAdmin);
router.get('/stats', getStats);
Every route in admin.js now requires auth and admin. No need to repeat per route.
Nested Routers
Routers can be mounted inside routers:
// routes/users.js
const postsRouter = require('./posts');
router.use('/:userId/posts', postsRouter);
Now /users/:userId/posts/... hits postsRouter. Useful for nested resources.
Separation of Concerns
Routes declare endpoints. Controllers handle logic. The router file imports controllers and binds them:
// routes/users.js
const { listUsers, createUser, getUser } = require('../controllers/userController');
router.get('/', listUsers);
The router file stays thin and declarative.
The Takeaway
express.Router lets you group routes by feature in separate files, mount them under a path in app.js, apply per-router middlewares, and nest routers for nested resources. Use it from day one to keep app.js clean.
For grouping routes by feature in separate files. You create a router in routes/users.js, add routes to it, and mount it in app.js with app.use('/users', usersRouter). This keeps app.js clean as features grow.
Use app.use('/path', router). The router's paths are then relative to the mount path. For example, router.get('/:id') mounted at /users responds to /users/:id.
Yes. Use router.use(middleware) at the top of the router file. Every route in the router now runs that middleware. Useful for auth and role checks across a feature.
Yes. You can mount one router inside another: router.use('/:userId/posts', postsRouter). Now /users/:userId/posts/... hits postsRouter. Useful for nested resources.
In controllers or services, not in routes. Routes should be thin: they declare the endpoint and bind it to a controller. router.get('/', listUsers) is good. Putting logic in the router makes the file hard to maintain.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

