Express.js Middleware for Production: Best Practices

Understanding Express.js Middleware

Middleware functions are the backbone of Express.js applications. They have access to the request object (req), response object (res), and the next middleware function in the application's request-response cycle.

This comprehensive guide covers everything from b...

🔗 https://www.roastdev.com/post/....expressjs-middleware

#news #tech #development

Favicon 
www.roastdev.com

Express.js Middleware for Production: Best Practices

Understanding Express.js Middleware

Middleware functions are the backbone of Express.js applications. They have access to the request object (req), response object (res), and the next middleware function in the application's request-response cycle.

This comprehensive guide covers everything from basic middleware concepts to production-ready patterns used by companies at scale.

Middleware Fundamentals

Basic Middleware Structure

// Simple middleware function
function myMiddleware(req, res, next) {
// Do something with req/res
console.log('Request received:', req.method, req.url);

// Pass control to next middleware
next();
}

// Use middleware
app.use(myMiddleware);

Types of Middleware


Application-level middleware: Bound to app instance
Router-level middleware: Bound to router instance
Error-handling middleware: Has 4 parameters (err, req, res, next)
Built-in middleware: express.json(), express.static()
Third-party middleware: helmet, cors, morgan


Essential Production Middleware

1. Security Middleware

const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

// Helmet - Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:']
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));

// CORS configuration
const corsOptions = {
origin: function (origin, callback) {
const whitelist = process.env.ALLOWED_ORIGINS.split(',');
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));

// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);

// Stricter rate limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true
});
app.use('/api/auth/', authLimiter);

2. Request Parsing Middleware

const express = require('express');
const compression = require('compression');

// Body parsers
app.use(express.json({
limit: '10mb',
verify: (req, res, buf) => {
req.rawBody = buf;
}
}));

app.use(express.urlencoded({
extended: true,
limit: '10mb'
}));

// Compression
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6
}));

3. Logging Middleware

const morgan = require('morgan');
const winston = require('winston');
const fs = require('fs');
const path = require('path');

// Winston logger setup
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});

if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}

// Morgan HTTP request logging
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'logs', 'access.log'),
{ flags: 'a' }
);

app.use(morgan('combined', { stream: accessLogStream }));

// Custom request logger
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('user-agent')
});
});

next();
});

4. Authentication Middleware

const jwt = require('jsonwebtoken');

// JWT authentication
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];

if (!token) {
return res.status(401).json({ error: 'No token provided' });
}

jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}

req.user = user;
next();
});
}

// Optional authentication
function optionalAuth(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];

if (token) {
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (!err) {
req.user = user;
}
});
}

next();
}

// Role-based authorization
function requireRole(...roles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}

if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}

next();
};
}

// Usage
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({ data: 'Protected data', user: req.user });
});

app.get('/api/admin', authenticateToken, requireRole('admin'), (req, res) => {
res.json({ data: 'Admin data' });
});

5. Validation Middleware

const { body, param, query, validationResult } = require('express-validator');

// Validation middleware factory
function validate(validations) {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));

const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array().map(err => ({
field: err.param,
message: err.msg
}))
});
}

next();
};
}

// Example validations
const userValidation = validate([
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 })
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('Password must contain uppercase, lowercase, and number'),
body('age').optional().isInt({ min: 18, max: 120 })
]);

const idValidation = validate([
param('id').isMongoId()
]);

// Usage
app.post('/api/users', userValidation, createUser);
app.get('/api/users/:id', idValidation, getUser);

6. Error Handling Middleware

// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}

// Async wrapper to catch errors
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}

// 404 handler
app.use((req, res, next) => {
next(new AppError(`Cannot find ${req.originalUrl}`, 404));
});

// Global error handler
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';

// Log error
logger.error({
message: err.message,
stack: err.stack,
url: req.url,
method: req.method
});

// Development error response
if (process.env.NODE_ENV === 'development') {
return res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
}

// Production error response
if (err.isOperational) {
return res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}

// Programming or unknown errors
return res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
});

// Usage
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);

if (!user) {
throw new AppError('User not found', 404);
}

res.json({ user });
}));

Advanced Middleware Patterns

1. Caching Middleware

const redis = require('redis');
const client = redis.createClient();

function cache(duration) {
return async (req, res, next) => {
if (req.method !== 'GET') {
return next();
}

const key = `cache:${req.originalUrl}`;

try {
const cachedResponse = await client.get(key);

if (cachedResponse) {
return res.json(JSON.parse(cachedResponse));
}

// Override res.json to cache response
const originalJson = res.json.bind(res);
res.json = (body) => {
client.setex(key, duration, JSON.stringify(body));
return originalJson(body);
};

next();
} catch (error) {
next(error);
}
};
}

// Usage
app.get('/api/products', cache(300), getProducts);

2. Request Context Middleware

const { v4: uuidv4 } = require('uuid');
const cls = require('cls-hooked');

const namespace = cls.createNamespace('request-context');

function requestContext(req, res, next) {
namespace.run(() => {
const requestId = uuidv4();
namespace.set('requestId', requestId);
namespace.set('userId', req.user?.id);

res.setHeader('X-Request-ID', requestId);

next();
});
}

// Helper to get context anywhere
function getRequestContext() {
return {
requestId: namespace.get('requestId'),
userId: namespace.get('userId')
};
}

app.use(requestContext);

3. API Versioning Middleware

function apiVersion(version) {
return (req, res, next) => {
req.apiVersion = version;
next();
};
}

// V1 routes
const v1Router = express.Router();
v1Router.use(apiVersion('v1'));
v1Router.get('/users', getUsersV1);

// V2 routes
const v2Router = express.Router();
v2Router.use(apiVersion('v2'));
v2Router.get('/users', getUsersV2);

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

4. Performance Monitoring

const prometheus = require('prom-client');

// Metrics
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});

const httpRequestTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});

function metricsMiddleware(req, res, next) {
const start = process.hrtime();

res.on('finish', () => {
const duration = process.hrtime(start);
const durationSeconds = duration[0] + duration[1] / 1e9;

const route = req.route?.path || req.path;

httpRequestDuration
.labels(req.method, route, res.statusCode)
.observe(durationSeconds);

httpRequestTotal
.labels(req.method, route, res.statusCode)
.inc();
});

next();
}

app.use(metricsMiddleware);

// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', prometheus.register.contentType);
res.end(await prometheus.register.metrics());
});

Middleware Execution Order

const express = require('express');
const app = express();

// 1. Security (first)
app.use(helmet());
app.use(cors(corsOptions));
app.use(limiter);

// 2. Request parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(compression());

// 3. Logging and monitoring
app.use(morgan('combined'));
app.use(metricsMiddleware);
app.use(requestContext);

// 4. Static files
app.use(express.static('public'));

// 5. Authentication (if needed globally)
app.use(optionalAuth);

// 6. Routes
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// 7. 404 handler
app.use(notFoundHandler);

// 8. Error handler (last)
app.use(errorHandler);

Testing Middleware

const request = require('supertest');
const express = require('express');

describe('Authentication Middleware', () => {
let app;

beforeEach(() => {
app = express();
app.use(express.json());
app.get('/protected', authenticateToken, (req, res) => {
res.json({ user: req.user });
});
});

test('should reject request without token', async () => {
const response = await request(app)
.get('/protected');

expect(response.status).toBe(401);
});

test('should accept valid token', async () => {
const token = generateTestToken({ id: 1, email: 'test@example.com' });

const response = await request(app)
.get('/protected')
.set('Authorization', `Bearer ${token}`);

expect(response.status).toBe(200);
expect(response.body.user).toBeDefined();
});
});

Performance Best Practices


Order matters: Put frequently used middleware first
Avoid heavy computation: Keep middleware fast
Use async/await properly: Prevent blocking
Cache when possible: Reduce redundant work
Monitor performance: Track middleware execution time


Production Checklist


✅ Security headers configured (helmet)
✅ Rate limiting implemented
✅ CORS properly configured
✅ Request validation in place
✅ Error handling comprehensive
✅ Logging configured
✅ Authentication/authorization working
✅ Monitoring and metrics enabled
✅ Compression enabled
✅ Tests written for middleware


Conclusion

Mastering Express.js middleware is essential for building production-ready Node.js applications. From security to performance monitoring, middleware provides the foundation for scalable, maintainable applications.

Start with the basics, add security layers, implement proper error handling, and monitor everything. Your production application will thank you.

Similar Posts

Similar

Django Whitespace Problems: Complete Fix Guide

Understanding Django Template Whitespace Issues

Django templates are powerful, but they can generate unexpected whitespace in your HTML output. This causes issues with inline elements, JavaScript parsing, and overall code cleanliness.

This guide covers every whitespace problem in Django templates ...

🔗 https://www.roastdev.com/post/....django-whitespace-pr

#news #tech #development

Favicon 
www.roastdev.com

Django Whitespace Problems: Complete Fix Guide

Understanding Django Template Whitespace Issues

Django templates are powerful, but they can generate unexpected whitespace in your HTML output. This causes issues with inline elements, JavaScript parsing, and overall code cleanliness.

This guide covers every whitespace problem in Django templates and provides practical solutions.

Common Whitespace Problems

Problem 1: Template Tags Creating Extra Lines

!-- Bad: Creates empty lines --
div
{% if user.is_authenticated %}
Hello {{ user.username }}
{% endif %}
/div

!-- Rendered output has extra blank lines --
div

Hello John

/div

Problem 2: Inline Elements Split

!-- Bad: Unwanted space between elements --
spanPrice:/span
{% if on_sale %}
span class="sale"$99/span
{% else %}
span$149/span
{% endif %}

!-- Renders: "Price: $99" with space before price --

Problem 3: JSON/JavaScript Issues

!-- Bad: Whitespace breaks JSON --
script
const data = {
{% for item in items %}
"{{ item.key }}": "{{ item.value }}",
{% endfor %}
};
/script

!-- Creates trailing comma and formatting issues --

Solutions

1. Strip Whitespace with Minus Sign

!-- Use {%- and -%} to strip whitespace --
div
{%- if user.is_authenticated -%}
Hello {{ user.username }}
{%- endif -%}
/div

!-- Clean output --
divHello John/div

!-- Strip on one side only --
spanText/span
{%- if condition %} More text{% endif %}

!-- Left side stripped, right side keeps whitespace --

2. Single-line Template Tags

!-- Keep everything inline --
div{% if user.is_authenticated %}Hello {{ user.username }}{% endif %}/div

!-- For longer content, use continuation --
div{%- if condition -%}
Your content here
{%- endif -%}/div

3. Custom Template Filter

# myapp/templatetags/whitespace_filters.py
from django import template
import re

register = template.Library()

@register.filter(name='remove_whitespace')
def remove_whitespace(value):
"""Remove extra whitespace from string"""
return re.sub(r'\s+', ' ', value).strip()

@register.filter(name='compress')
def compress_html(value):
"""Compress HTML by removing unnecessary whitespace"""
# Remove whitespace between tags
value = re.sub(r'>\s+\s+\s+
Similar

Discovered my guy’s been playing me like a damn fiddle

I tracked down the other chick he’s been messing with, expecting some psycho who’d flip out and scream, but nah, she was surprisingly chill. Turns out, this bastard’s been stringing her along too, and get this—there’s apparently a third idiot in his harem, just as clueless as us. This dude’s a straight-up gigolo, living large off our dime while spinning yarns about “working.” What a slick son of a bitch!

Anyway, this other girl’s a med student, and we hatched a savage plan to screw with our little player. We’re whipping up a fake medical report to make him think I’ve got AIDS—supposedly got tested six months back (we’ve been together for two years), and boom, results just dropped. Let’s see how this prick squirms!

#cheater #revenge #savage #drama

Similar

Savage Player on the Loose

I was out chilling with this chick, thinking I’d shoot my shot and ask her to be my girl. Turns out, she’s already got a dude on the side. And get this—sheordre—I’m not the only one she’s stringing along. She’s been playing the field with five other guys! Claims she’s no slut, just running a damn "boyfriend audition."
Seriously, shouldn’t she be auditioning for a porn gig instead with that kind of game?

#player #savage #datingdrama #wtf