Understanding Routing in Express
Routing refers to how an application responds to client requests to specific endpoints (URIs) combined with specific HTTP methods (GET, POST, PUT, DELETE, etc.). Express provides a powerful and flexible routing system that makes it easy to organize your application's endpoints and handle different types of requests.
Basic Route Structure
A route definition has the following structure:
app.METHOD(PATH, HANDLER)
Where:
app is an instance of Express
METHOD is an HTTP request method (get, post, put, delete, etc.) in lowercase
PATH is the path/endpoint on the server
HANDLER is the function executed when the route is matched
HTTP Route Methods
Express supports all standard HTTP methods. Here are the most commonly used ones:
GET - Retrieve Data
GET requests are used to retrieve data from the server:
// Get all users
app.get('/users', (req, res) => {
res.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
});
// Get a specific user
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: 'John' });
});
POST - Create Data
POST requests are used to create new resources:
app.post('/users', (req, res) => {
const { name, email } = req.body;
// In real app, save to database
res.status(201).json({
id: 3,
name,
email,
message: 'User created successfully'
});
});
PUT - Update Data (Complete Replacement)
PUT requests replace the entire resource:
app.put('/users/:id', (req, res) => {
const userId = req.params.id;
const { name, email, age } = req.body;
// In real app, update in database
res.json({
id: userId,
name,
email,
age,
message: 'User updated completely'
});
});
PATCH - Update Data (Partial Update)
PATCH requests update only specific fields:
app.patch('/users/:id', (req, res) => {
const userId = req.params.id;
const updates = req.body; // Only fields to update
res.json({
id: userId,
...updates,
message: 'User updated partially'
});
});
DELETE - Remove Data
DELETE requests remove resources:
app.delete('/users/:id', (req, res) => {
const userId = req.params.id;
// In real app, delete from database
res.json({
message: `User ${userId} deleted successfully`
});
});
All Methods
Handle all HTTP methods for a route:
app.all('/secret', (req, res) => {
console.log(`Accessing secret section via ${req.method}`);
res.send('Secret area');
});
Note: The order in which you define routes matters. Express matches routes in the order they are defined. Put more specific routes before generic ones.
Route Parameters
Route parameters are named URL segments used to capture values at specific positions in the URL. The captured values are stored in the req.params object.
Single Parameter
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});
// Example: /users/123 → User ID: 123
Multiple Parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({
user: userId,
post: postId,
message: `Getting post ${postId} from user ${userId}`
});
});
// Example: /users/5/posts/42
// Result: { user: '5', post: '42', message: ... }
Parameter Patterns
You can use regular expressions to validate parameters:
// Only match numeric IDs
app.get('/products/:id(\\d+)', (req, res) => {
res.send(`Product ID: ${req.params.id}`);
});
// Match IDs with exactly 5 digits
app.get('/orders/:orderId(\\d{5})', (req, res) => {
res.send(`Order ID: ${req.params.orderId}`);
});
Optional Parameters
// The ? makes the parameter optional
app.get('/users/:id?/profile', (req, res) => {
if (req.params.id) {
res.send(`Profile for user ${req.params.id}`);
} else {
res.send('Profile for current user');
}
});
// Matches both:
// /users/123/profile
// /users/profile
Tip: Always validate and sanitize route parameters before using them, especially when querying databases to prevent injection attacks.
Query String Parameters
Query strings are key-value pairs that appear after the ? in a URL. They are accessed via req.query.
Single Query Parameter
app.get('/search', (req, res) => {
const searchTerm = req.query.q;
res.json({
query: searchTerm,
results: [`Result 1 for ${searchTerm}`, `Result 2 for ${searchTerm}`]
});
});
// Example: /search?q=express
// Result: { query: 'express', results: [...] }
Multiple Query Parameters
app.get('/products', (req, res) => {
const { category, minPrice, maxPrice, sort } = req.query;
res.json({
category: category || 'all',
priceRange: {
min: minPrice || 0,
max: maxPrice || Infinity
},
sortBy: sort || 'relevance'
});
});
// Example: /products?category=electronics&minPrice=100&maxPrice=500&sort=price
Default Values and Type Conversion
app.get('/items', (req, res) => {
// Query parameters are always strings
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const sortOrder = req.query.sort || 'asc';
res.json({
page,
limit,
sortOrder,
message: `Page ${page} with ${limit} items per page`
});
});
Note: Query parameters are always received as strings. You need to convert them to numbers or booleans if needed.
Router Module
For larger applications, it's better to organize routes into separate modules using express.Router(). This creates modular, mountable route handlers.
Creating a Router
Create a file routes/users.js:
const express = require('express');
const router = express.Router();
// All routes here are relative to the mount point
// GET /users
router.get('/', (req, res) => {
res.json({ message: 'Get all users' });
});
// GET /users/:id
router.get('/:id', (req, res) => {
res.json({ message: `Get user ${req.params.id}` });
});
// POST /users
router.post('/', (req, res) => {
res.status(201).json({ message: 'User created' });
});
// PUT /users/:id
router.put('/:id', (req, res) => {
res.json({ message: `Update user ${req.params.id}` });
});
// DELETE /users/:id
router.delete('/:id', (req, res) => {
res.json({ message: `Delete user ${req.params.id}` });
});
module.exports = router;
Using the Router in Main App
In your main app.js:
const express = require('express');
const app = express();
const usersRouter = require('./routes/users');
app.use(express.json());
// Mount the router at /users
app.use('/users', usersRouter);
// Now all routes in usersRouter are prefixed with /users
// GET /users → router.get('/')
// GET /users/123 → router.get('/:id')
// POST /users → router.post('/')
app.listen(3000);
Multiple Routers
You can create multiple routers for different resources:
// routes/users.js
const usersRouter = require('./routes/users');
// routes/posts.js
const postsRouter = require('./routes/posts');
// routes/comments.js
const commentsRouter = require('./routes/comments');
// app.js
app.use('/api/users', usersRouter);
app.use('/api/posts', postsRouter);
app.use('/api/comments', commentsRouter);
Nested Routers
You can nest routers to create hierarchical route structures:
// routes/users.js
const express = require('express');
const router = express.Router();
const postsRouter = require('./posts');
router.get('/', (req, res) => {
res.json({ message: 'All users' });
});
// Nest posts router under users
router.use('/:userId/posts', postsRouter);
module.exports = router;
// routes/posts.js
const express = require('express');
const router = express.Router({ mergeParams: true }); // Important!
router.get('/', (req, res) => {
// Access parent route parameter
const userId = req.params.userId;
res.json({ message: `Posts for user ${userId}` });
});
router.get('/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ message: `Post ${postId} from user ${userId}` });
});
module.exports = router;
// app.js
app.use('/users', usersRouter);
// Results in routes:
// GET /users → All users
// GET /users/5/posts → Posts for user 5
// GET /users/5/posts/42 → Post 42 from user 5
Warning: When using nested routers, set mergeParams: true in the child router to access parent route parameters. Without this, req.params.userId would be undefined in the posts router.
Route Grouping and Chaining
You can chain route handlers for the same path:
Method Chaining
router.route('/users/:id')
.get((req, res) => {
res.json({ message: 'Get user' });
})
.put((req, res) => {
res.json({ message: 'Update user' });
})
.delete((req, res) => {
res.json({ message: 'Delete user' });
});
Multiple Handlers for One Route
You can pass multiple callback functions as handlers:
// Middleware functions
const validateUser = (req, res, next) => {
if (!req.body.name) {
return res.status(400).json({ error: 'Name is required' });
}
next();
};
const checkAuth = (req, res, next) => {
// Check authentication
const isAuthenticated = true; // Simplified
if (!isAuthenticated) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};
// Apply multiple handlers
router.post('/users', checkAuth, validateUser, (req, res) => {
res.status(201).json({ message: 'User created' });
});
Array of Handlers
const handlers = [
checkAuth,
validateUser,
(req, res) => {
res.json({ message: 'All checks passed' });
}
];
router.post('/users', handlers);
Path Patterns
Express supports various patterns for route paths:
String Patterns
// Matches any path starting with /ab
app.get('/ab*cd', (req, res) => {
res.send('Matched: abcd, abxcd, ab123cd, etc.');
});
// The + means one or more
app.get('/ab+cd', (req, res) => {
res.send('Matches: abcd, abbcd, abbbcd, etc.');
});
// The ? means optional
app.get('/ab?cd', (req, res) => {
res.send('Matches: acd, abcd');
});
Regular Expression Patterns
// Match any route containing 'fly'
app.get(/.*fly$/, (req, res) => {
res.send('Matches: butterfly, dragonfly, etc.');
});
// Match exactly 3 digits
app.get(/^\/\d{3}$/, (req, res) => {
res.send('Matches: /123, /456, etc.');
});
Organizing Routes Best Practices
Recommended Folder Structure
project/
├── app.js
├── routes/
│ ├── index.js // Main router file
│ ├── users.js
│ ├── posts.js
│ └── auth.js
├── controllers/
│ ├── usersController.js
│ ├── postsController.js
│ └── authController.js
└── middleware/
├── auth.js
└── validation.js
Separating Controllers
Keep route logic in separate controller files:
// controllers/usersController.js
exports.getAllUsers = (req, res) => {
res.json({ message: 'Get all users' });
};
exports.getUserById = (req, res) => {
res.json({ message: `Get user ${req.params.id}` });
};
exports.createUser = (req, res) => {
res.status(201).json({ message: 'User created' });
};
exports.updateUser = (req, res) => {
res.json({ message: 'User updated' });
};
exports.deleteUser = (req, res) => {
res.json({ message: 'User deleted' });
};
// routes/users.js
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/usersController');
router.get('/', usersController.getAllUsers);
router.get('/:id', usersController.getUserById);
router.post('/', usersController.createUser);
router.put('/:id', usersController.updateUser);
router.delete('/:id', usersController.deleteUser);
module.exports = router;
Tip: Use descriptive route names and follow RESTful conventions for API routes. For example: GET /users, POST /users, GET /users/:id, PUT /users/:id, DELETE /users/:id
RESTful Routing Conventions
RESTful APIs follow standard conventions for route naming:
// Resource: users
GET /users // Get all users
GET /users/:id // Get specific user
POST /users // Create new user
PUT /users/:id // Update entire user
PATCH /users/:id // Update partial user
DELETE /users/:id // Delete user
// Nested resources
GET /users/:userId/posts // Get posts by user
GET /users/:userId/posts/:postId // Get specific post by user
POST /users/:userId/posts // Create post for user
Exercise: Build a complete RESTful API for a blog:
- Create a project structure with separate route and controller files
- Implement routes for three resources: users, posts, and comments
- Users routes:
- GET /api/users - Get all users
- GET /api/users/:id - Get user by ID
- POST /api/users - Create user
- PUT /api/users/:id - Update user
- DELETE /api/users/:id - Delete user
- Posts routes (similar structure to users)
- Nested route: GET /api/users/:userId/posts - Get all posts by user
- Implement query parameters for pagination: /api/posts?page=1&limit=10
- Create validation middleware to check required fields
- Test all routes using Postman or Thunder Client
- Add a 404 handler for undefined routes