Course:Node.js & Express/
Lesson

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 cors
import 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 });
});
Good to know
CORS only applies to browsers. If you're testing your API with curl or Postman, CORS headers won't affect anything. The browser is the one enforcing these rules.
02

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 helmet
import 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:

HeaderWhat it protects against
Content-Security-PolicyXSS attacks by controlling what resources can load
X-Frame-OptionsClickjacking by preventing your site from being framed
X-Content-Type-OptionsMIME sniffing attacks
Strict-Transport-SecurityForces HTTPS connections
Referrer-PolicyControls 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.

03

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 morgan
import 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 - 45

Pro tip
In production, you probably want to log to files or a service like Winston or Pino instead of just console. Morgan is great for quick setup, but dedicated logging libraries offer more features like log rotation and different log levels.
04

Compression: 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 compression
import 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.

05

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-limit
import 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

06

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...
07

Quick reference: essential middlewares

MiddlewarePurposeInstall command
CORSCross-origin requestsnpm install cors
HelmetSecurity headersnpm install helmet
MorganHTTP loggingnpm install morgan
CompressionGzip responsesnpm install compression
express-rate-limitRequest throttlingnpm 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.