Express Router & Project Organization
📖 Concept
Express Router is a mini-application that handles routing and middleware for a specific path prefix. It enables modular, scalable project organization by splitting routes into separate files.
Why use Routers?
- Separation of concerns — each resource gets its own route file
- Middleware scoping — apply middleware only to specific route groups
- Team collaboration — different developers work on different route modules
- Testing — test route modules independently
Router pattern:
// routes/users.js
const router = express.Router();
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
export default router;
// app.js
app.use('/api/users', userRouter); // Prefix all routes with /api/users
Router-level middleware:
// Apply auth to all routes in this router
router.use(authenticate);
// Or to specific routes
router.get('/', publicHandler);
router.post('/', authenticate, protectedHandler);
route() chaining:
router.route('/users/:id')
.get(getUser)
.put(updateUser)
.delete(deleteUser);
🏠 Real-world analogy: If Express is a shopping mall, each Router is a store. The mall routes customers (requests) to the right store (router) based on the entrance they use (path prefix). Each store manages its own layout (routes) and security (middleware) independently.
💻 Code Example
1// Express Router — Modular Project Organization23const express = require("express");45// === routes/userRoutes.js ===6function createUserRouter(userService) {7 const router = express.Router();89 // GET /api/users10 router.get("/", async (req, res, next) => {11 try {12 const { page = 1, limit = 10, search } = req.query;13 const users = await userService.findAll({ page, limit, search });14 res.json(users);15 } catch (err) {16 next(err);17 }18 });1920 // GET /api/users/:id21 router.get("/:id", async (req, res, next) => {22 try {23 const user = await userService.findById(req.params.id);24 if (!user) {25 return res.status(404).json({ error: "User not found" });26 }27 res.json({ data: user });28 } catch (err) {29 next(err);30 }31 });3233 // POST /api/users34 router.post("/", async (req, res, next) => {35 try {36 const user = await userService.create(req.body);37 res.status(201).json({ data: user });38 } catch (err) {39 next(err);40 }41 });4243 // PUT /api/users/:id44 router.put("/:id", async (req, res, next) => {45 try {46 const user = await userService.update(req.params.id, req.body);47 if (!user) {48 return res.status(404).json({ error: "User not found" });49 }50 res.json({ data: user });51 } catch (err) {52 next(err);53 }54 });5556 // DELETE /api/users/:id57 router.delete("/:id", async (req, res, next) => {58 try {59 await userService.delete(req.params.id);60 res.status(204).end();61 } catch (err) {62 next(err);63 }64 });6566 return router;67}6869// === routes/authRoutes.js ===70function createAuthRouter(authService) {71 const router = express.Router();7273 router.post("/register", async (req, res, next) => {74 try {75 const { name, email, password } = req.body;76 const user = await authService.register({ name, email, password });77 res.status(201).json({ data: user });78 } catch (err) {79 next(err);80 }81 });8283 router.post("/login", async (req, res, next) => {84 try {85 const { email, password } = req.body;86 const token = await authService.login(email, password);87 res.json({ token });88 } catch (err) {89 next(err);90 }91 });9293 return router;94}9596// === routes/index.js — Route aggregator ===97function createRoutes(services) {98 const router = express.Router();99100 router.use("/auth", createAuthRouter(services.auth));101 router.use("/users", createUserRouter(services.user));102103 return router;104}105106// === app.js — Application setup ===107function createApp(services) {108 const app = express();109110 // Global middleware111 app.use(express.json());112113 // Health check114 app.get("/health", (req, res) => {115 res.json({ status: "ok", uptime: process.uptime() });116 });117118 // API routes119 app.use("/api/v1", createRoutes(services));120121 // 404 handler122 app.use((req, res) => {123 res.status(404).json({ error: `${req.method} ${req.path} not found` });124 });125126 // Error handler127 app.use((err, req, res, next) => {128 console.error(err.stack);129 res.status(err.statusCode || 500).json({130 error: err.message || "Internal server error",131 });132 });133134 return app;135}136137// === server.js — Entry point ===138// Mock services139const services = {140 user: {141 findAll: async () => [{ id: 1, name: "Alice" }],142 findById: async (id) => ({ id, name: "Alice" }),143 create: async (data) => ({ id: Date.now(), ...data }),144 update: async (id, data) => ({ id, ...data }),145 delete: async (id) => true,146 },147 auth: {148 register: async (data) => ({ id: 1, ...data }),149 login: async () => "mock-jwt-token",150 },151};152153const app = createApp(services);154app.listen(3000, () => console.log("Server on http://localhost:3000"));
🏋️ Practice Exercise
Exercises:
- Split an Express app into separate router modules:
authRoutes.js,userRoutes.js,postRoutes.js - Create a route aggregator (
routes/index.js) that mounts all routers with proper prefixes - Implement API versioning using routers:
/api/v1/and/api/v2/with different handlers - Use
router.route()chaining for a resource with GET, PUT, DELETE on the same path - Add router-level middleware that only applies to admin routes (
/api/admin/*) - Build a factory function pattern where routers receive dependencies (services, config) as parameters
⚠️ Common Mistakes
Defining overlapping paths between parent and child routers — this causes confusing double-matching of routes
Not passing errors to
next(err)in async route handlers — use try/catch in every async handler and callnext(err)in the catch blockCreating circular dependencies between route files — use dependency injection (factory functions) instead of requiring between route files
Hardcoding API prefixes in route files — let the parent
app.use('/api/v1', router)handle the prefix; routes should use relative pathsNot testing routers in isolation — factory functions that accept services enable unit testing without a running database
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Express Router & Project Organization. Login to unlock this feature.