Advanced Routing in Express
Routing is the core of any web application. Express provides a powerful and flexible routing system that allows you to create clean, maintainable API endpoints.
Learning Objectives
- Understand Express Router and modular routing
- Learn route parameters and query strings
- Master middleware types and patterns
- Build reusable middleware functions
Express Router
The Express Router is a mini Express application capable of handling routes and middleware. It helps organize your routes into separate modules.
Creating a Router
javascript// routes/products.js const express = require('express'); const router = express.Router(); // All routes here are relative to /api/products router.get('/', (req, res) => { res.json({ message: 'Get all products' }); }); router.get('/:id', (req, res) => { res.json({ message: `Get product ${req.params.id}` }); }); router.post('/', (req, res) => { res.status(201).json({ message: 'Create product' }); }); module.exports = router;
Mounting Routers
javascript// app.js const express = require('express'); const productsRouter = require('./routes/products'); const usersRouter = require('./routes/users'); const ordersRouter = require('./routes/orders'); const app = express(); // Mount routers with prefixes app.use('/api/products', productsRouter); app.use('/api/users', usersRouter); app.use('/api/orders', ordersRouter); module.exports = app;
Route Parameters
Basic Route Parameters
javascript// Single parameter app.get('/users/:id', (req, res) => { const userId = req.params.id; res.json({ userId }); }); // Example: GET /users/123 // req.params = { id: '123' }
Query Strings
Query strings are used for filtering, pagination, and search:
javascript// GET /api/products?category=electronics&sort=price&order=desc&page=1&limit=10 app.get('/api/products', (req, res) => { const { category, sort = 'createdAt', order = 'asc', page = 1, limit = 10 } = req.query; console.log({ category, // 'electronics' sort, // 'price' order, // 'desc' page, // '1' (string!) limit // '10' (string!) }); // Convert to numbers const pageNum = parseInt(page, 10); const limitNum = parseInt(limit, 10); const skip = (pageNum - 1) * limitNum; // Build query... res.json({ data: [], pagination: { page: pageNum, limit: limitNum, total: 100 } }); });
Important
Query string values are always strings! Remember to parse numbers with parseInt() or parseFloat().
Middleware Deep Dive
Middleware functions are the heart of Express. They execute in the order they're defined and can:
- Execute code
- Modify request and response objects
- End the request-response cycle
- Call the next middleware
Types of Middleware
Creating Custom Middleware
Authentication Middleware
javascript// middleware/auth.js const jwt = require('jsonwebtoken'); const authenticate = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); } }; // Role-based authorization const authorize = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Not authorized to access this resource' }); } next(); }; }; module.exports = { authenticate, authorize };
Request Validation Middleware
javascript// middleware/validate.js const validateBody = (schema) => { return (req, res, next) => { const { error, value } = schema.validate(req.body); if (error) { return res.status(400).json({ error: 'Validation error', details: error.details.map(d => d.message) }); } req.body = value; // Use validated/sanitized data next(); }; }; // Usage with Joi const Joi = require('joi'); const createUserSchema = Joi.object({ name: Joi.string().min(2).max(50).required(), email: Joi.string().email().required(), password: Joi.string().min(8).required() }); router.post('/users', validateBody(createUserSchema), usersController.create );
Rate Limiting Middleware
javascript// middleware/rateLimit.js const rateLimit = (options = {}) => { const { windowMs = 60000, // 1 minute max = 100, // requests per window message = 'Too many requests' } = options; const requests = new Map(); return (req, res, next) => { const key = req.ip; const now = Date.now(); // Clean old entries const windowStart = now - windowMs; if (!requests.has(key)) { requests.set(key, []); } const userRequests = requests.get(key) .filter(time => time > windowStart); if (userRequests.length >= max) { return res.status(429).json({ error: message }); } userRequests.push(now); requests.set(key, userRequests); next(); }; }; // Usage app.use('/api', rateLimit({ max: 100, windowMs: 60000 }));
Middleware Execution Order
Middleware Chain
javascriptapp.use(helmet()); // 1. Security headers app.use(cors()); // 2. CORS app.use(morgan('dev')); // 3. Logging app.use(express.json()); // 4. Parse body app.use(rateLimit()); // 5. Rate limiting // 6. Routes (with their own middleware) app.use('/api', authenticate, apiRoutes); app.use('/public', publicRoutes); // 7. 404 Handler app.use((req, res) => { res.status(404).json({ error: 'Not found' }); }); // 8. Error Handler (always last!) app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); });
Route Chaining
Express allows you to chain route handlers:
javascript// Method 1: Multiple callbacks app.get('/users/:id', validateParams, authenticate, authorize('admin'), async (req, res) => { // All middleware passed! res.json(user); } ); // Method 2: router.route() for same path, different methods router.route('/users') .get(getUsers) .post(validateBody(userSchema), createUser); router.route('/users/:id') .get(getUser) .patch(validateBody(updateSchema), updateUser) .delete(authorize('admin'), deleteUser);
Best Practices
✅ Routing & Middleware Best Practices
- Keep routes organized — Group related routes in separate files
- Use async/await with try-catch — Or use an async handler wrapper
- Validate input early — Use validation middleware before handlers
- Keep middleware focused — One middleware, one responsibility
- Order matters — Error handlers must be last
- Use meaningful HTTP status codes — 200, 201, 400, 401, 403, 404, 500
Next Steps
Continue Learning
Now that you understand routing and middleware, let's connect to a database and build real CRUD operations!