Router and routes in separate files
When every route lives in src/app.js, the file grows quickly. express.Router is a mini application: you describe a group of routes in a separate file and then mount it on the main application under a common address.
A routes file
Create the file src/routes/posts.js:
// src/routes/posts.js
import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('List of posts');
});
router.get('/:id', (req, res) => {
res.send(`Post ${req.params.id}`);
});
export default router;A router has the same methods as the application: get, post, delete and others. Addresses here are relative to where the router is mounted.
Mounting it
In src/app.js import the router and mount it with app.use():
// src/app.js
import express from 'express';
import postsRouter from './routes/posts.js';
const app = express();
app.use('/posts', postsRouter);
export default app;Now /posts calls the router's '/' route, and /posts/2 calls '/:id'. If the section moves to /articles, you change one line in app.js.
Controllers
To keep the routes file a short list of addresses, the handlers themselves are moved into a controller. Create src/controllers/postsController.js:
// src/controllers/postsController.js
import { posts } from '../data/posts.js';
export function index(req, res) {
res.send(posts.map((post) => post.title).join(', '));
}
export function show(req, res) {
const post = posts.find((item) => item.id === Number(req.params.id));
if (!post) {
return res.status(404).send('Post not found');
}
res.send(post.title);
}And replace the functions in the router with references to them:
// src/routes/posts.js
import { Router } from 'express';
import * as postsController from '../controllers/postsController.js';
const router = Router();
router.get('/', postsController.index);
router.get('/:id', postsController.show);
export default router;The pages /posts and /posts/1 work as before, but now one look at routes/posts.js shows every address of the section.
Parent parameters
If a router is mounted at an address with a parameter, for example '/posts/:postId/comments', it does not see postId by default. The mergeParams option passes the parent's parameters through:
// src/routes/comments.js
import { Router } from 'express';
const router = Router({ mergeParams: true });
router.get('/', (req, res) => {
res.send(`Comments on post ${req.params.postId}`);
});
export default router;In app.js it is mounted like this: app.use('/posts/:postId/comments', commentsRouter).
Common mistake
Do not repeat the address inside the router. If you write router.get('/posts/:id') and mount the router with app.use('/posts', ...), the route is reachable only at /posts/posts/2. The common part of the address is given once — in app.use().