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
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
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
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
Next Lesson
Database Integration with MongoDB/PostgreSQL