NodeJSThird-Party Middleware

Third-Party Middleware

Beyond the built-ins, the real power of Express is its ecosystem: thousands of npm packages that plug straight into the pipeline as app.use(something()). A handful are so universal that nearly every production app installs them — security headers, CORS, logging, compression, rate limiting, sessions. This page is a tour of those essentials, what each one does, and the order to register them in.

The production starter pack

Package

Purpose

helmet

Sets security-related HTTP headers

cors

Enables cross-origin requests (covered here)

morgan

HTTP request logging

compression

gzip/deflate responses

express-rate-limit

Throttle requests per IP

cookie-parser

Parse the Cookie header → req.cookies

express-session

Server-side sessions

multer

Parse multipart/form-data (file uploads)

`app.use(pkg())` — call the factory, pass the result
Almost every third-party middleware is a **factory**: you import it, *call it* (often with options) to get a middleware function, then pass that to `app.use`. So it's `app.use(helmet())`, not `app.use(helmet)` — the parentheses produce the configured middleware. Forgetting them is a common beginner bug that silently does nothing or throws.
helmet — security headers

JS
import helmet from 'helmet'

// Sensible secure defaults in one line:
app.use(helmet())

// Or tune individual protections:
app.use(helmet({
  contentSecurityPolicy: {
    directives: { 'img-src': ["'self'", 'data:', 'https:'] },
  },
}))
helmet sets a dozen headers you'd otherwise forget
`helmet()` applies a bundle of defensive headers: `Content-Security-Policy`, `X-Content-Type-Options: nosniff`, `Strict-Transport-Security`, `X-Frame-Options`, and more. Each mitigates a class of attack (XSS, clickjacking, MIME sniffing, protocol downgrade). You could set them by hand with `res.set`, but helmet keeps them correct and current. Register it **early** so every response is covered.
morgan — request logging

JS
import morgan from 'morgan'

// Concise colored output for development:
app.use(morgan('dev'))

// Apache-style combined logs for production:
app.use(morgan('combined'))
GET /api/users 200 4.512 ms - 1043
POST /api/users 201 12.880 ms - 87
GET /favicon.ico 404 0.991 ms - 150
Pick a format per environment
`morgan('dev')` is compact and color-codes status by severity — ideal locally. `morgan('combined')` emits the standard Apache combined log line (IP, timestamp, user-agent) that log aggregators understand — use it in production. You can also pass a `stream` option to pipe logs to a file or a logging service instead of stdout.
compression — smaller responses

JS
import compression from 'compression'

// gzip/brotli text responses over a size threshold:
app.use(compression())
Compression belongs early — but often the proxy should do it
`compression()` must run **before** the route handlers whose responses it compresses (it wraps `res.write`/`res.end`). It pays off for text (JSON, HTML, CSS) but wastes CPU on already-compressed assets (images, video) — it skips those by content type. In production behind nginx or a CDN, the proxy frequently handles compression instead; adding it in Node too is redundant. Compress in exactly one layer.
express-rate-limit — throttling

JS
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,   // 15-minute window
  max: 100,                    // 100 requests per IP per window
  standardHeaders: true,       // send RateLimit-* headers
  legacyHeaders: false,
  message: { error: 'Too many requests, slow down.' },
})

// Apply globally, or just to sensitive routes:
app.use('/api', limiter)
app.use('/auth/login', rateLimit({ windowMs: 60_000, max: 5 }))
The default in-memory store doesn't work across multiple instances
`express-rate-limit` keeps counts in process memory by default. If you run **multiple instances** (cluster, PM2, containers, autoscaling), each has its own counter — so the real limit is `max × instances`, and a client can dodge it by hitting different instances. For multi-instance deployments, configure a shared store (Redis via `rate-limit-redis`). Also set `app.set('trust proxy', ...)` correctly behind a load balancer, or every request looks like it comes from the proxy's IP.
Where each one goes — the order

app.js — third-party middleware in order

JS
import express from 'express'
import helmet from 'helmet'
import cors from 'cors'
import compression from 'compression'
import morgan from 'morgan'
import rateLimit from 'express-rate-limit'
import cookieParser from 'cookie-parser'

const app = express()

app.use(helmet())                                   // 1. security headers
app.use(cors({ origin: 'https://app.example.com' })) // 2. cross-origin policy
app.use(compression())                              // 3. compress responses
app.use(morgan('combined'))                         // 4. log requests
app.use(rateLimit({ windowMs: 60_000, max: 100 }))  // 5. throttle
app.use(express.json())                             // 6. parse bodies
app.use(cookieParser())                             // 7. parse cookies

// 8. your routes
app.use('/api', apiRouter)
The ordering logic, not a magic recipe
The sequence follows from [what each needs](/nodejs/application-middleware): security headers and CORS come first so they apply to *everything* (including errors); logging early so it records every request; rate limiting before the expensive body parsing it's meant to protect; parsers before routes that read `req.body`/`req.cookies`; routes last. You don't have to memorize a fixed list — reason about what each middleware depends on.
Vetting a package before you install it
  • Maintenance — recent commits, open-issue responsiveness, an Express 5-compatible version.

  • Downloads & dependents — widely-used packages are battle-tested and quickly patched.

  • Dependency weight — a middleware that pulls in 40 transitive deps enlarges your attack surface.

  • Does Express already do it? — body parsing and static serving are built in; no package needed.

Every middleware you add runs on every matching request — and is code you trust
Third-party middleware executes inside your request pipeline with full access to `req`/`res`. A compromised or abandoned package is a direct supply-chain risk. Prefer well-maintained, widely-depended-on packages; pin versions; run `npm audit`; and don't add a dependency for something you can do in five lines of [custom middleware](/nodejs/custom-middleware).
Next
When no package fits, write your own: [Writing Custom Middleware](/nodejs/custom-middleware).