Mastering Express Router: The Complete Guide to Route Handling in Node.js
(Node Series Part 9)
In this section, I have covered how to work with the Router in Express.js
Routing in Express.js
Routing is one of the core features of Express.js, determining how an application responds to client requests for specific endpoints (URIs) and HTTP methods (GET, POST, etc.).
Basically helps us avoid writing the same end point multiple times and consolidate the API calls for that endpoint as a single module and handle it better.
The general syntax for routes in Express is:
app.METHOD(PATH, HANDLER)
// app is an instance of express
// METHOD is an HTTP request method (get, post, put, delete, etc.)
// PATH is a path on the server
// HANDLER is the function executed when the route is matchedHow Routing Works
Request Matching: When a request comes in, Express matches:
- The HTTP method (GET, POST, etc.)
- The URL path
Route Execution: The first matching route is executed. Routes are matched in the order they are defined.
Handler Execution: The route handler function receives:
req(request object) - contains request datares(response object) - used to send a responsenext(optional) - passes control to the next middleware
Route Paths
Route paths can be:
- Strings: Exact matches (
'/about') - String patterns: Wildcards (
'/ab*cd'matches 'abcd', 'abxcd', etc.) - Regular expressions: (
/\/user\/([0-9]+)/) - Route parameters: (
'/users/:userId')
Route Handlers
Handlers can be:
- A single function
//single function
app.get('/greet', (req, res) => {
res.send('Hello there!');
});- Multiple functions,we need to call the next method to call the next handler.
//Multiple Callbacks
// Middleware function
const logVisit = (req, res, next) => {
console.log('Someone visited!');
next(); // Pass to next handler
};
// Route with two handlers
app.get('/about',
logVisit,
(req, res) => {
console.log('About Us page');
res.send('About Us page');
}
);What it does: Chains multiple functions that execute sequentially
When to use: When you need middleware (like auth checks, logging, data validation)
Flow: Request → Middleware → Handler → Response
Get Shubham Gupta’s stories in your inbox
Join Medium for free to get updates from this writer.
Key point: Must call next() to continue to next handler
- Combinations of functions and arrays
// Two middleware functions
const detectBrowser = (req, res, next) => {
console.log('Browser:', req.headers['user-agent']);
next();
};
const checkMobile = (req, res, next) => {
req.isMobile = /mobile/i.test(req.headers['user-agent']);
next();
};
// Route using array
app.get('/products',
[detectBrowser, checkMobile],
(req, res) => {
if (req.isMobile) {
res.send('Mobile products page');
} else {
res.send('Desktop products page');
}
}
);Express Router
Express Router is a mini Express application that provides route organization and modularity. It helps you:
- Split routes into multiple files
- Organize by features or resource types
- Apply middleware to specific route groups
Basic Router Setup
Create a file user inside the routes folder
// routes/users.js
import express from 'express';
const router = express.Router();
// Define routes
router.get('/', (req, res) => {
res.send('User list');
});
router.get('/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
export default router;inside express.js
import express from "express";
import cookieParser from "cookie-parser";
import userRouter from './routes/users.js';
const app = express();
app.use(express.json());
app.use(cookieParser());
//mounting router
app.use('/users', userRouter);
app.listen(3000, serverListener);
function serverListener(err) {
if (err) {
console.log("error", err);
}else{
console.log("Server Started");
}
}GET /users HTTP/1.1
Host: localhost:3000
###
GET /users/12 HTTP/1.1
Host: localhost:3000Key Router Features
Below are some router features
Route Parameters: Capture dynamic values from the URL path
router.get('/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
router.get('/:id/:name', (req, res) => {
res.send(`User ID: ${req.params.id} User Name:${req.params.name}`);
});GET /users/12/shubham HTTP/1.1
Host: localhost:3000Router-Level Middleware: Middleware that runs only for routes in this router
router.use((req, res, next) => {
console.log(req.url);
next();
});import express from 'express';
const router = express.Router();
router.use((req, res, next) => {
console.log(req.url);
next();
});
router.get('/', (req, res) => {
console.log('User list');
res.send('User list');
});
router.get('/:id', (req, res) => {
console.log(`User ID: ${req.params.id}`);
res.send(`User ID: ${req.params.id}`);
});
router.post('/create', (req, res) => {
console.log(`Create User`);
res.send(`Create User`);
});
export default router;HTTP Method Handlers:Support for all HTTP methods (GET, POST, PUT, etc.)
//users.js
import express from 'express';
const router = express.Router();
router.get('/', (req, res) => {
console.log('User list');
res.send('User list');
});
router.get('/:id', (req, res) => {
console.log(`User ID: ${req.params.id}`);
res.send(`User ID: ${req.params.id}`);
});
router.get('/:id/:name', (req, res) => {
console.log(`User ID: ${req.params.id} User Name:${req.params.name}`);
res.send(`User ID: ${req.params.id} User Name:${req.params.name}`);
});
router.post('/create', (req, res) => {
console.log(`Create User`);
res.send(`Create User`);
});
router.delete('/:id', (req, res) => {
console.log(`Delete User ${req.params.id}`);
res.send(`Delete User`);
});
export default router;GET /users HTTP/1.1
Host: localhost:3000
###
GET /users/12 HTTP/1.1
Host: localhost:3000
###
GET /users/12/shubham HTTP/1.1
Host: localhost:3000
###
POST /users/create HTTP/1.1
Host: localhost:3000
###
DELETE /users/12 HTTP/1.1 / HTTP/1.1
Host: localhost:3000Route Chaining: Group same-path routes by method for cleaner code We use route method which has base path to it and then methods are changed to it.
router.route(PATH)
.METHOD(handler)
.METHOD(handler)
// ...additional methodsHere is an example, we will convert the below routes into chaining
import express from 'express';
const router = express.Router();
router.use((req, res, next) => {
console.log(req.url);
next();
});
router.get('/', (req, res) => {
console.log('User list');
res.send('User list');
});
router.get('/:id', (req, res) => {
console.log(`User ID: ${req.params.id} `);
res.send(`User ID: ${req.params.id}`);
});
router.post('/create', (req, res) => {
console.log(`Create User`);
res.send(`Create User`);
});
router.delete('/:id', (req, res) => {
console.log(`Delete User ${req.params.id}`);
res.send(`Delete User`);
});
export default router;Here are the chained routes
import express from 'express';
const router = express.Router();
// Middleware for all routes
router.use((req, res, next) => {
console.log('Request URL:', req.url);
next();
});
// Base route
router.route('/')
.get((req, res) => {
console.log('User list');
res.send('User list');
});
// ID-based routes
router.route('/:id')
.get((req, res) => {
console.log(`Get User ID: ${req.params.id}`);
res.send(`User ID: ${req.params.id}`);
})
.delete((req, res) => {
console.log(`Delete User ${req.params.id}`);
res.send(`Delete User ${req.params.id}`);
});
// Create route
router.route('/create')
.post((req, res) => {
console.log('Create User');
res.send('Create User');
});
export default router;Multiple Middleware Stack: Sequence multiple handlers for a single route
const validate = (req, res, next) => { /* validation logic */ };
const sanitize = (req, res, next) => { /* data cleaning */ };
router.post('/submit',
validate,
sanitize,
(req, res) => { /* final handler */ }
);router.all(): this method is a special routing function that matches all HTTP methods (GET, POST, PUT, DELETE, etc.) for a specified path.
It’s commonly used for:
- Global middleware for specific paths
- Catch-all handlers
- Path-based preprocessing
Important Notes
all()Handlers execute before method-specific handlers- Always call
next()Unless you're intentionally terminating the request - Can be combined with other routing methods
- Useful for creating “gatekeeper” middleware for route groups
router.all(path, callback)Example
router.all('/products', (req, res, next) => {
console.log('This runs for ALL methods first');
next();
});
router.get('/products', (req, res) => {
res.send('GET products');
});
router.post('/products', (req, res) => {
res.send('POST products');
});Router Params vs. Static Routes Conflict
Issue: Express matches routes in order, so a static route (e.g., /getall) must be defined before a dynamic parameterized route (e.g., /:id), or the dynamic route will "steal" requests meant for the static route.
- Route matching depends on BOTH the path AND the HTTP method
- Conflicts only occur for identical methods
- Static routes should precede dynamic ones for the same method
// DYNAMIC (matches ANY path, including "/create")
router.get('/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
// STATIC (will NEVER be reached)
router.get('/getall', (req, res) => {
res.send('User List');
});###
GET /users/getall HTTP/1.1
Host: localhost:3000Always define static routes before parameterized routes in your router to avoid conflicts when methods are the same
router.get('/getall', (req, res) => {
res.send('User List');
});
router.get('/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});Controllers
Now, as you can see above, our route file seems crowded as all the code for the method will be there, it will be difficult to modify, maintain, and debug, so we introduce controllers.
Controllers in Express.js are functions that handle the business logic for specific routes. They help keep your code organized, modular, and maintainable by separating route definitions from the actual logic.
A controller is a function that:
- Receives
req(request) andres(response) objects. - Processes the request (e.g., database queries, validations).
- Sends back a response (e.g., JSON, HTML, error messages).
Basic Controller Structure
A controller is typically structured as an object or module with methods for different actions.
Here is an example of how to use controllers, create a separate folder for the controllers, and I have added a file user.js, which will have a function for each method
// this is our route file
import express from 'express';
const router = express.Router();
router.use((req, res, next) => {
console.log(req.url);
next();
});
router.get('/getall', (req, res) => {
res.send('User List');
});
router.post('/create', (req, res) => {
console.log(`Create User`);
res.send(`Create User`);
});
router.delete('/:id', (req, res) => {
console.log(`Delete User ${req.params.id}`);
res.send(`Delete User`);
});
export default router;// this is user.js controller file
export function getHandller(req,res) {
res.send('User List');
}
export function postHandller(req, res) {
console.log(`Create User`);
res.send(`Create User`);
}
export function deleteHandller(req, res) {
console.log(`Delete User ${req.params.id}`);
res.send(`Delete User`);
}Now our route file will become like this.
import express from 'express';
import * as controller from "../controllers/user"
const router = express.Router();
router.use((req, res, next) => {
console.log(req.url);
next();
});
router.get('/getall', controller.getHandller);
router.post('/create',controller.postHandller);
router.delete('/:id', controller.deleteHandller);
export default router;Best Practices
- Keep controllers focused (one responsibility per method).
- Use async/await for database operations.
- Handle errors properly (avoid crashing the server).
- Use dependency injection for better testing.
- Avoid business logic in routes (delegate to controllers).
If you’d like to be in the know, subscribe, clap, like, and share. Cheers!







