beginner
backend

Routing and Middleware

Master Express.js routing patterns and middleware architecture for building modular APIs

express fundamentals
20 min
Routing and Middleware

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 } }); });

Middleware Deep Dive

Middleware functions are the heart of Express. They execute in the order they're defined and can:

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

javascript
app.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

  1. Keep routes organized — Group related routes in separate files
  2. Use async/await with try-catch — Or use an async handler wrapper
  3. Validate input early — Use validation middleware before handlers
  4. Keep middleware focused — One middleware, one responsibility
  5. Order matters — Error handlers must be last
  6. 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!

Next Lesson:Database Integration with MongoDB/PostgreSQL