Naming conventions for Express middleware
Express middleware should have names that explain what they do, not how they do it. Use active verbs: requireAuth, validateBody, logRequest. Avoid vague names like checkStuff or middleware1.
For reusable middleware, use factory functions that return the middleware: validateBody(schema), rateLimit({ max: 100 }). This allows per-route configuration without code duplication.
Organize middleware in folders by type: /middleware/auth/, /middleware/validation/. Global middleware (helmet, cors) goes in app bootstrap; route-specific ones apply in routers. Order matters: auth before validation, validation before handlers.
Common Express middleware patterns
The most common pattern is three-argument middleware (req, res, next). For async/await, wrap in asyncHandler or use libraries like express-async-errors that automate catching.
Error handling middleware has four arguments: (err, req, res, next). Must be at the end of the chain. Typed errors: create custom classes extending Error with statusCode, code, isOperational properties.
For validation, integrate Zod or Joi. Example: validateBody(userSchema) returns middleware that parses req.body and calls next(error) on failure. Use Zod's transform to sanitize and cast types (dates, emails).
Rate limiting and API security
Implement rate limiting with express-rate-limit. Basic config: max: 100 requests per IP every windowMs: 15 * 60 * 1000 (15min). For public APIs, use aggressive limits (10 req/min); for authenticated, more permissive (100 req/min).
Combine rate limit with slow-down: instead of blocking, progressively delay responses (from 0ms to 1000ms). Useful for login/signup endpoints vulnerable to brute force.
Other security layers: helmet for headers (CSP, HSTS), express-validator to sanitize inputs, hpp against parameter pollution. In production, use API Gateway (AWS API Gateway, Kong) for WAF, geoblocking, and DDoS protection before reaching your app.
Testing and debugging middleware
To test middleware, use supertest with real HTTP requests: request(app).get('/api/users').expect(200). Mock req, res, next with node-mocks-http for isolated middleware unit tests.
Use DEBUG=express:* to see Express internal logs. For distributed tracing, add correlation IDs in early middleware and propagate them in X-Correlation-ID headers to downstream services.
Common mistakes: forgetting to call next() (request hangs), calling next() after sending response (cannot set headers error), modifying req/res without custom TypeScript types. Use @types/express + module augmentation for strict typing of custom properties like req.user.