Express.js Routing
Route methods, parameters, query strings, handlers, and Express Router
REST API
Route Params
Query Strings
Table of Contents
1. Introduction to Routing
Routing refers to determining how an application responds to a client request to a specific endpoint (URI) and HTTP method.
Basic Route Structure
Patternapp.METHOD(PATH, HANDLER)
Where:
- app — Express instance
- METHOD — HTTP method (GET, POST, PUT, DELETE, etc.)
- PATH — URL path (endpoint)
- HANDLER — Function executed when route is matched
Simple Example
JavaScriptconst express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Home Page');
});
app.listen(3000);
2. Route Methods
Express supports all HTTP methods commonly used in RESTful APIs.
Basic HTTP Methods
JavaScriptconst express = require('express');
const app = express();
app.get('/api/users', (req, res) => {
res.json({ message: 'Fetching all users' });
});
app.post('/api/users', (req, res) => {
res.json({ message: 'Creating a new user' });
});
app.put('/api/users/:id', (req, res) => {
res.json({ message: `Updating user ${req.params.id} completely` });
});
app.patch('/api/users/:id', (req, res) => {
res.json({ message: `Partially updating user ${req.params.id}` });
});
app.delete('/api/users/:id', (req, res) => {
res.json({ message: `Deleting user ${req.params.id}` });
});
Less Common Methods
JavaScriptapp.head('/api/users', (req, res) => {
res.status(200).end();
});
app.options('/api/users', (req, res) => {
res.set('Allow', 'GET, POST, PUT, DELETE, OPTIONS');
res.status(200).end();
});
app.all('/api/secret', (req, res) => {
res.json({
method: req.method,
message: 'This route accepts any HTTP method'
});
});
Multiple Methods Same Path
JavaScriptapp.get('/products', (req, res) => {
res.json({ products: [] });
});
app.post('/products', (req, res) => {
res.status(201).json({ message: 'Product created' });
});
app.put('/products', (req, res) => {
res.json({ message: 'Product updated' });
});
3. Route Paths
Route paths define the endpoints at which requests can be made.
String Paths
JavaScriptapp.get('/about', (req, res) => {
res.send('About page');
});
app.get('/users/profile/settings', (req, res) => {
res.send('User settings page');
});
app.get('/document.pdf', (req, res) => {
res.sendFile('document.pdf');
});
Pattern Paths (String Patterns)
JavaScriptapp.get('/ab?cd', (req, res) => res.send('Matches /acd or /abcd'));
app.get('/ab+cd', (req, res) => res.send('Matches /abcd, /abbcd, /abbbcd, etc.'));
app.get('/ab*cd', (req, res) => res.send('Matches /abXcd, /abXYZcd, /abanythingcd'));
app.get('/ab(cd)?e', (req, res) => res.send('Matches /abe or /abcde'));
Regular Expression Paths
JavaScriptapp.get(/a/, (req, res) => res.send('Path contains letter "a"'));
app.get(/.*fly$/, (req, res) => res.send('Path ends with "fly"'));
app.get(/^\/users\/\d+$/, (req, res) => res.send('Matches /users/123, not /users/abc'));
app.get(/\.(jpg|png|gif)$/, (req, res) => res.send('Image file request'));
Route Path Examples
| Path | Matches | Doesn't Match |
|---|---|---|
/users | /users | /users/123 |
/users/me | /users/me | /users/you |
/ab?cd | /acd, /abcd | /abbcd |
/ab+cd | /abcd, /abbcd | /acd |
/ab*cd | /abXcd, /abXYZcd | /acd |
/* | Any path | — |
/.*fly$/ | /butterfly, /dragonfly | /flyaway |
4. Route Parameters
Route parameters are named URL segments used to capture values at specific positions in the URL.
Basic Route Parameters
JavaScriptapp.get('/users/:userId', (req, res) => {
const userId = req.params.userId;
res.send(`User ID: ${userId}`);
});
app.get('/users/:userId/books/:bookId', (req, res) => {
const { userId, bookId } = req.params;
res.send(`User: ${userId}, Book: ${bookId}`);
});
Parameter Naming Conventions
JavaScriptapp.get('/products/:productId/reviews/:reviewId', (req, res) => {
const { productId, reviewId } = req.params;
res.json({ productId, reviewId });
});
app.get('/api/v1/:api-version/users/:user_id', (req, res) => {
console.log(req.params['api-version']);
console.log(req.params.user_id);
res.send('OK');
});
Parameter Validation Example
JavaScriptapp.get('/users/:id', (req, res) => {
const userId = req.params.id;
if (isNaN(userId)) {
return res.status(400).json({ error: 'User ID must be a number' });
}
const id = parseInt(userId);
const user = findUserById(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
Optional Parameters
JavaScriptapp.get('/users/:userId?', (req, res) => {
const userId = req.params.userId;
if (userId) {
res.send(`Viewing user: ${userId}`);
} else {
res.send('Viewing all users');
}
});
Regular Expression Parameters
JavaScriptapp.get('/users/:id(\\d+)', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
app.get('/files/:file(\\w+\\.\\w+)', (req, res) => {
res.send(`File: ${req.params.file}`);
});
5. Query Parameters
Query parameters are key-value pairs appended to the URL after a ? separator.
Basic Query Parameters
JavaScriptapp.get('/search', (req, res) => {
const queryParams = req.query;
res.json(queryParams);
});
// /search?q=express → { q: 'express' }
// /search?q=express&page=2 → { q: 'express', page: '2' }
Working with Query Parameters
JavaScriptapp.get('/api/users', (req, res) => {
const { page, limit, sort, filter } = req.query;
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const sortBy = sort || 'name';
res.json({
pagination: { page: pageNum, limit: limitNum, sort: sortBy },
filter: filter || 'none'
});
});
Parsing Different Data Types
JavaScriptapp.get('/api/filter', (req, res) => {
const price = parseFloat(req.query.price);
const quantity = parseInt(req.query.quantity);
const inStock = req.query.inStock === 'true';
const isActive = req.query.active === 'true';
const colors = req.query.colors ? req.query.colors.split(',') : [];
const fromDate = req.query.from ? new Date(req.query.from) : null;
res.json({ price, quantity, inStock, isActive, colors, fromDate });
});
Query Parameter Validation
JavaScriptapp.get('/api/validate-query', (req, res) => {
const errors = [];
if (!req.query.apiKey) errors.push('apiKey is required');
const limit = parseInt(req.query.limit);
if (req.query.limit && isNaN(limit)) errors.push('limit must be a number');
if (errors.length) return res.status(400).json({ errors });
res.json({ message: 'Valid query parameters', data: req.query });
});
6. Combining Params and Query Strings
JavaScriptapp.get('/api/users/:userId/posts', (req, res) => {
const { userId } = req.params;
const { page = 1, limit = 10, sort = 'createdAt', includeComments = false } = req.query;
res.json({
user: userId,
posts: {
pagination: { page, limit },
sort,
includeComments: includeComments === 'true'
}
});
});
// /api/users/123/posts?page=2&limit=5&sort=title&includeComments=true
Building URLs Programmatically
JavaScriptapp.get('/api/build-url', (req, res) => {
const baseUrl = `${req.protocol}://${req.get('host')}`;
const searchParams = new URLSearchParams({ q: 'express tutorial', page: 2, limit: 10 });
const searchUrl = `${baseUrl}/api/search?${searchParams.toString()}`;
res.json({ searchUrl, currentUrl: req.originalUrl });
});
7. Route Handlers
Multiple Callback Functions (Middleware)
JavaScriptconst validateUser = (req, res, next) => {
if (!req.params.id) return res.status(400).json({ error: 'User ID required' });
next();
};
const checkPermissions = (req, res, next) => {
req.hasPermission = true;
next();
};
app.get('/users/:id', validateUser, checkPermissions, (req, res) => {
res.json({ id: req.params.id, hasPermission: req.hasPermission });
});
Array of Callbacks
JavaScriptconst logRequest = (req, res, next) => { console.log(`${req.method} ${req.url}`); next(); };
const authenticate = (req, res, next) => {
if (!req.headers.authorization) return res.status(401).json({ error: 'Unauthorized' });
req.user = { id: 1, name: 'John' };
next();
};
app.get('/profile', [logRequest, authenticate, (req, res) => res.json({ user: req.user })]);
Async Route Handlers
JavaScriptapp.get('/async-data', async (req, res) => {
try {
const data = await fetchDataFromDatabase();
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
8. Response Methods
JavaScriptapp.get('/response-methods', (req, res) => {
// res.send(), res.json(), res.status(), res.redirect()
// res.render(), res.sendFile(), res.download(), res.end()
});
app.get('/chainable', (req, res) => {
res
.status(200)
.type('application/json')
.set('X-Custom-Header', 'Hello')
.json({ message: 'Chainable methods are awesome!' });
});
Advanced Response Configuration
JavaScriptapp.get('/advanced-response', (req, res) => {
res.set({ 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' });
res.cookie('user', 'john', { maxAge: 900000, httpOnly: true });
if (!res.headersSent) res.json({ message: 'Response sent' });
});
9. app.route() for Chaining
JavaScriptapp.route('/books')
.get((req, res) => res.json({ books: ['Book 1', 'Book 2'] }))
.post((req, res) => res.status(201).json({ message: 'Book created' }))
.put((req, res) => res.json({ message: 'Books updated' }))
.delete((req, res) => res.json({ message: 'All books deleted' }));
Complete CRUD Example
JavaScriptapp.route('/api/articles/:id?')
.get((req, res) => {
const { id } = req.params;
res.json(id ? { article: { id, title: `Article ${id}` } } : { articles: ['Article 1', 'Article 2'] });
})
.post((req, res) => res.status(201).json({ message: 'Article created' }))
.put((req, res) => res.json({ message: `Article ${req.params.id} updated` }))
.patch((req, res) => res.json({ message: `Article ${req.params.id} partially updated` }))
.delete((req, res) => res.json({ message: `Article ${req.params.id} deleted` }));
10. Express Router
Basic Router Setup (routes/users.js)
JavaScriptconst express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.json({ users: ['Alice', 'Bob', 'Charlie'] }));
router.get('/:id', (req, res) => res.json({ user: { id: req.params.id } }));
router.post('/', (req, res) => res.status(201).json({ message: 'User created' }));
module.exports = router;
Main App with Routers
JavaScriptconst userRoutes = require('./routes/users');
app.use('/users', userRoutes);
app.use('/api/v1', apiRoutes);
Router Parameters
JavaScriptrouter.param('companyId', (req, res, next, id) => {
req.company = { id, name: `Company ${id}` };
next();
});
router.get('/:companyId', (req, res) => res.json(req.company));
11. Practical Examples
Complete REST API Example (Todos)
JavaScriptconst express = require('express');
const app = express();
app.use(express.json());
let todos = [
{ id: 1, title: 'Learn Express', completed: false },
{ id: 2, title: 'Build an API', completed: false }
];
app.get('/api/todos', (req, res) => {
let result = [...todos];
if (req.query.completed !== undefined) {
const completed = req.query.completed === 'true';
result = result.filter(todo => todo.completed === completed);
}
if (req.query.search) {
const term = req.query.search.toLowerCase();
result = result.filter(todo => todo.title.toLowerCase().includes(term));
}
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const start = (page - 1) * limit;
res.json({
data: result.slice(start, start + limit),
pagination: { page, limit, total: result.length }
});
});
app.get('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === parseInt(req.params.id));
if (!todo) return res.status(404).json({ error: 'Todo not found' });
res.json(todo);
});
app.post('/api/todos', (req, res) => {
if (!req.body.title) return res.status(400).json({ error: 'Title is required' });
const newTodo = { id: todos.length + 1, title: req.body.title, completed: false };
todos.push(newTodo);
res.status(201).json(newTodo);
});
app.patch('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === parseInt(req.params.id));
if (!todo) return res.status(404).json({ error: 'Todo not found' });
Object.assign(todo, req.body);
res.json(todo);
});
app.delete('/api/todos/:id', (req, res) => {
const idx = todos.findIndex(t => t.id === parseInt(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Todo not found' });
todos.splice(idx, 1);
res.status(204).send();
});
app.listen(3000);
Blog API with Route Params and Query Params
JavaScriptapp.get('/api/posts', (req, res) => {
let posts = [...db.posts];
if (req.query.author) posts = posts.filter(p => p.authorId === parseInt(req.query.author));
if (req.query.published !== undefined) {
posts = posts.filter(p => p.published === (req.query.published === 'true'));
}
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const start = (page - 1) * limit;
res.json({ data: posts.slice(start, start + limit), pagination: { page, limit, total: posts.length } });
});
app.get('/api/posts/:postId', (req, res) => {
const post = db.posts.find(p => p.id === parseInt(req.params.postId));
if (!post) return res.status(404).json({ error: 'Post not found' });
post.views++;
if (req.query.include_comments === 'true') {
post.comments = db.comments.filter(c => c.postId === post.id);
}
res.json(post);
});
12. Best Practices
Routing Best Practices
JavaScript// GOOD: Descriptive route names
app.get('/api/users/:userId/orders/:orderId')
// GOOD: Plural for collections
app.get('/api/products')
// GOOD: Version your API
app.use('/api/v1', v1Routes)
Parameter Validation
JavaScriptconst validateId = (req, res, next) => {
const id = parseInt(req.params.id);
if (isNaN(id) || id <= 0) return res.status(400).json({ error: 'Invalid ID format' });
req.id = id;
next();
};
const validatePagination = (req, res, next) => {
const page = parseInt(req.query.page);
const limit = parseInt(req.query.limit);
if (req.query.limit && (isNaN(limit) || limit < 1 || limit > 100)) {
return res.status(400).json({ error: 'Limit must be between 1 and 100' });
}
req.pagination = { page: page || 1, limit: limit || 10 };
next();
};
Error Handling for Routes
JavaScriptapp.use((err, req, res, next) => {
const status = err.statusCode || 500;
res.status(status).json({
error: {
message: err.message || 'Internal Server Error',
status
}
});
});
Security Considerations
JavaScriptconst rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 });
app.use('/api/', apiLimiter);
app.get('/api/users/:id', (req, res) => {
if (!/^\d+$/.test(req.params.id)) {
return res.status(400).json({ error: 'ID must be numeric' });
}
});
Quick Reference Cheatsheet
| Topic | Syntax / Access |
|---|---|
| Route Parameters | req.params — /users/:userId |
| Query Parameters | req.query — /search?q=express&page=2 |
| GET / POST / PUT / PATCH / DELETE | app.get(), app.post(), etc. |
| Response | res.send(), res.json(), res.status() |
Route Paths'/users' // Exact match
'/users/:id' // Parameter
'/ab?cd' // Optional character
'/ab+cd' // One or more
'/ab*cd' // Wildcard
/.*fly$/ // Regex