You could write every middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. yourself, but why reinvent the wheel? The Express ecosystem is filled with battle-tested middlewares that solve common problems. Think of them as the power tools in your development toolbox, they save you hours of work and handle edge cases you haven't even thought of yet.
CORSWhat is cors?Cross-Origin Resource Sharing - a browser security rule that blocks web pages from making requests to a different domain unless that domain explicitly allows it.: handling cross-originWhat is origin?The combination of protocol, domain, and port that defines a security boundary in the browser, like https://example.com:443. requests
Here's a scenario that confuses every beginner: your React app runs on localhost:5173, your Express APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. on localhost:3000. They look like they're on the same machine, but the browser sees them as different origins. By default, it blocks requests between them. That's where CORS comes in.
CORS (Cross-Origin Resource Sharing) is a browser security feature. Your server must explicitly tell the browser: "It's okay, I trust that website."
npm install corsimport cors from 'cors';
// Allow ALL origins (great for development, terrible for production)
app.use(cors());
// Production configuration - whitelist specific origins
app.use(cors({
origin: ['https://my-site.com', 'https://app.my-site.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true // Allow cookies to be sent
}));
// Apply CORS to specific routes only
app.get('/public', cors(), (req, res) => {
res.json({ public: true });
});HelmetWhat is helmet?An Express middleware package that sets recommended HTTP security headers (CSP, HSTS, X-Frame-Options) in one line.: security headers made easy
Security headers are HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. responses that tell browsers how to behave. Should this page be allowed in an iframe? Can scripts run inline? Helmet sets sensible defaults for all of these, protecting you from common attacks like XSSWhat is xss?Cross-Site Scripting - an attack where malicious JavaScript is injected into a web page and runs in other users' browsers, stealing data or hijacking sessions. and clickjackingWhat is clickjacking?An attack where a malicious site embeds your page in a hidden iframe and tricks users into clicking elements on it unknowingly..
npm install helmetimport helmet from 'helmet';
// Default configuration - recommended for most apps
app.use(helmet());
// Custom configuration for specific needs
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:']
}
},
crossOriginEmbedderPolicy: false
}));Helmet adds these security headers automatically:
| Header | What it protects against |
|---|---|
Content-Security-Policy | XSS attacks by controlling what resources can load |
X-Frame-Options | Clickjacking by preventing your site from being framed |
X-Content-Type-Options | MIME sniffing attacks |
Strict-Transport-Security | Forces HTTPS connections |
Referrer-Policy | Controls what info is sent in the Referer header |
Think of Helmet as your security guard, it doesn't make your app bulletproof, but it stops the most common attacks automatically.
Morgan: HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. request logging
When something breaks in production, you need to know what happened. Morgan logs every HTTP request with useful details: method, URL, status codeWhat is status code?A three-digit number in an HTTP response that tells the client what happened: 200 means success, 404 means not found, 500 means the server broke., response time.
npm install morganimport morgan from 'morgan';
// Predefined formats
app.use(morgan('dev')); // Concise, colored output for development
app.use(morgan('combined')); // Full Apache-style logs
app.use(morgan('tiny')); // Minimal: method, url, status
app.use(morgan('short')); // Short version with response time
// Custom format
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
// Log to file instead of console
import { createWriteStream } from 'fs';
const accessLogStream = createWriteStream('./access.log', { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));The dev format output looks like this:
GET /api/users 200 45.123 ms - 342
POST /api/users 201 23.456 ms - 89
GET /api/users/123 404 12.789 ms - 45Compression: faster responses
Modern web apps send a lot of JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it.. Compression middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. automatically gzips your responses, reducing transfer size by 70-80%.
npm install compressionimport compression from 'compression';
// Compress all responses
app.use(compression());
// Custom filter - skip compression for certain requests
app.use(compression({
filter: (req, res) => {
// Don't compress if client sends x-no-compression header
if (req.headers['x-no-compression']) {
return false;
}
// Use compression's default filter for everything else
return compression.filter(req, res);
},
level: 6 // Compression level 1-9 (higher = smaller but slower)
}));Your users on slow connections will thank you. A 500KB JSON response becomes ~100KB after compression.
Rate limitingWhat is rate limiting?Restricting how many requests a client can make within a time window. Prevents brute-force attacks and protects your API from being overwhelmed.: preventing abuse
Without rate limiting, a single malicious user (or a buggy script) can overwhelm your server with requests. Rate limiting caps how many requests an IP can make in a time window.
npm install express-rate-limitimport rateLimit from 'express-rate-limit';
// General API rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window per IP
message: 'Too many requests from this IP, please try again later',
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false // Disable X-RateLimit headers
});
app.use(limiter);
// Stricter limit for authentication routes
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 login attempts per 15 minutes
skipSuccessfulRequests: true // Don't count successful logins
});
app.use('/auth/login', authLimiter);This protects you from:
- Brute force attacks on login endpoints
- Accidental infinite loops in client code
- Malicious users trying to cost you money on metered services
Complete production setup
Here's a complete security and utility middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. stack for production:
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import morgan from 'morgan';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
const app = express();
// 1. Security headers (always first)
app.use(helmet());
// 2. CORS configuration
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true
}));
// 3. Request logging
app.use(morgan('dev'));
// 4. Response compression
app.use(compression());
// 5. Rate limiting
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}));
// 6. Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Your routes here...Quick reference: essential middlewares
| Middleware | Purpose | Install command |
|---|---|---|
| CORS | Cross-origin requests | npm install cors |
| Helmet | Security headers | npm install helmet |
| Morgan | HTTP logging | npm install morgan |
| Compression | Gzip responses | npm install compression |
| express-rate-limit | Request throttling | npm install express-rate-limit |
These five middlewares form the foundation of any production Express app. Install them once at the start of your project, and you'll have solved 80% of common security and performance concerns automatically.