Based on Planner and Memory Manager analysis: New Skills (8): - nodejs-express-patterns: App structure, routing, middleware - nodejs-security-owasp: OWASP Top 10 security practices - nodejs-testing-jest: Unit/integration tests, mocking - nodejs-auth-jwt: JWT authentication, OAuth, sessions - nodejs-error-handling: Error classes, middleware, async handlers - nodejs-middleware-patterns: Auth, validation, rate limiting - nodejs-db-patterns: SQLite, PostgreSQL, MongoDB patterns - nodejs-npm-management: package.json, scripts, dependencies New Rules: - nodejs.md: Code style, security, best practices Updated: - backend-developer.md: Added skills reference table Milestone: #48 NodeJS Development Coverage Related: Planner & Memory Manager analysis results
7.7 KiB
7.7 KiB
NodeJS Middleware Patterns
Comprehensive middleware patterns for Express.js applications.
Middleware Types
1. Application-level Middleware
// Global middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(helmet());
app.use(cors());
// Conditional middleware
app.use((req, res, next) => {
if (req.path.startsWith('/api')) {
return apiLimiter(req, res, next);
}
next();
});
2. Router-level Middleware
const router = require('express').Router();
// Router-specific middleware
router.use(authenticate);
router.use(validateRequest);
// Route-specific middleware
router.post('/', validateUser, controller.create);
router.put('/:id', validateUser, authenticate, controller.update);
3. Error-handling Middleware
// Must have 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.statusCode || 500).json({
status: 'error',
message: err.message
});
});
4. Built-in Middleware
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
Common Patterns
Authentication Middleware
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Invalid token' });
}
}
Authorization Middleware
function authorize(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Forbidden' });
}
next();
};
}
// Usage
router.delete('/:id', authenticate, authorize('admin'), controller.delete);
Rate Limiting Middleware
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { message: 'Too many requests' }
});
app.use('/api', limiter);
Validation Middleware
const { validationResult, body } = require('express-validator');
function validate(validations) {
return async (req, res, next) => {
await Promise.all(validations.map(v => v.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) return next();
res.status(400).json({
status: 'fail',
errors: errors.array().map(err => ({
field: err.path,
message: err.msg
}))
});
};
}
// Usage
router.post('/users',
validate([
body('email').isEmail(),
body('password').isLength({ min: 8 })
]),
controller.create
);
Request Logging Middleware
const pino = require('pino');
const logger = pino();
function requestLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration,
user: req.user?.id
});
});
next();
}
Context Middleware
// Request-scoped context
const asyncLocalStorage = new (require('async_hooks').AsyncLocalStorage)();
function contextMiddleware(req, res, next) {
const context = {
requestId: require('uuid').v4(),
userId: req.user?.id,
startTime: Date.now()
};
asyncLocalStorage.run(context, next);
}
// Access context anywhere in the request lifecycle
function getContext() {
return asyncLocalStorage.getStore();
}
Error Handler Middleware
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
return res.status(statusCode).json({
status,
message: err.message,
stack: err.stack
});
}
if (err.isOperational) {
return res.status(statusCode).json({
status,
message: err.message
});
}
res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
}
Third-party Middleware
Security (Helmet)
const helmet = require('helmet');
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"]
}
}));
Compression
const compression = require('compression');
app.use(compression({
threshold: 1024, // Only compress >1KB
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
}
}));
CORS
const cors = require('cors');
app.use(cors({
origin: process.env.CORS_ORIGIN?.split(',') || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
Morgan (Logging)
const morgan = require('morgan');
app.use(morgan('dev')); // Development
app.use(morgan('combined')); // Production
Custom Middleware Factory
// Custom middleware factory
function createMiddleware(options) {
return (req, res, next) => {
// Pre-processing
if (options.preCondition && !options.preCondition(req)) {
return res.status(400).json({ message: 'Precondition failed' });
}
// Modify request
req.custom = options.transform?.(req) || req.custom;
// Post-processing (on response)
res.on('finish', () => {
options.postProcess?.(req, res);
});
next();
};
}
// Usage
app.use(createMiddleware({
preCondition: (req) => req.headers['x-api-key'],
transform: (req) => ({ ip: req.ip }),
postProcess: (req, res) => console.log(`Request completed: ${req.path}`)
}));
Async Middleware Wrapper
function asyncHandler(fn) {
return (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
}
// Wrap middleware
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
res.json({ user });
}));
Middleware Composition
// Compose multiple middleware
function compose(...middlewares) {
return (req, res, next) => {
let index = -1;
function dispatch(i) {
if (i <= index) return next(new Error('next() called multiple times'));
index = i;
const middleware = middlewares[i];
if (!middleware) return next();
return middleware(req, res, (err) => {
if (err) return next(err);
return dispatch(i + 1);
});
}
return dispatch(0);
};
}
// Usage
app.use(compose(
authenticate,
authorize('user'),
validateRequest
));
Best Practices
- Order matters - Put error handlers last
- Use router middleware - For route groups
- Use async handlers - Wrap async functions
- Keep it simple - One responsibility per middleware
- Use third-party - Don't reinvent the wheel
- Document dependencies - Comment required fields
Middleware Order
// Correct order
app.use(helmet()); // Security headers
app.use(cors()); // CORS
app.use(compression()); // Compression
app.use(express.json()); // Body parsing
app.use(express.urlencoded()); // URL encoding
app.use(morgan('dev')); // Logging
app.use('/api', apiLimiter); // Rate limiting
app.use('/api', routes); // Routes
app.use(notFoundHandler); // 404 handler
app.use(errorHandler); // Error handler