Express Cheatsheet

CORS and Security

Use this Express reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

CORS — Cross-Origin Resource Sharing

CORS headers tell browsers whether cross-origin requests are allowed. Without them, same-origin policy blocks frontend JavaScript from calling your API.

cors Package (Recommended)

npm install cors
const cors = require('cors');

// Allow all origins (development only — never in production)
app.use(cors());

// Specific origin
app.use(cors({ origin: 'https://app.example.com' }));

// Multiple origins
const allowed = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowed.includes(origin)) return callback(null, true);
    callback(new Error('Not allowed by CORS'));
  },
}));

// Full options
app.use(cors({
  origin:           'https://app.example.com',
  methods:          ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders:   ['Content-Type', 'Authorization'],
  exposedHeaders:   ['X-Total-Count', 'X-Request-Id'],
  credentials:      true,          // allow cookies / auth headers
  maxAge:           86400,         // preflight cache (seconds)
  preflightContinue: false,        // pass OPTIONS to next handler if true
  optionsSuccessStatus: 204,       // some legacy browsers choke on 204
}));

Enabling Credentials

If the client sends cookies or Authorization headers, both sides must opt in:

// Server
app.use(cors({ origin: 'https://app.example.com', credentials: true }));
// Client (fetch)
fetch('https://api.example.com/data', { credentials: 'include' });

credentials: true is incompatible with origin: '*' — you must specify an exact origin.

Per-Route CORS

const corsAll     = cors();
const corsRestrict = cors({ origin: 'https://partner.com' });

app.get('/public',  corsAll,      handler);
app.get('/partner', corsRestrict, handler);

Manual CORS Headers (without package)

app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin',  'https://app.example.com');
  res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.set('Access-Control-Allow-Credentials', 'true');

  if (req.method === 'OPTIONS') return res.sendStatus(204); // handle preflight
  next();
});

CORS Header Reference

HeaderDirectionPurpose
Access-Control-Allow-OriginResponseAllowed origin(s)
Access-Control-Allow-MethodsResponse (preflight)Allowed methods
Access-Control-Allow-HeadersResponse (preflight)Allowed request headers
Access-Control-Expose-HeadersResponseHeaders visible to JS
Access-Control-Allow-CredentialsResponseAllow cookies/auth
Access-Control-Max-AgeResponse (preflight)Preflight cache duration
OriginRequestOrigin of the request

Helmet — Security Headers

npm install helmet
const helmet = require('helmet');

// Sensible defaults — use this first, then customize
app.use(helmet());

What helmet() Sets by Default (helmet v7+)

HeaderDefault Value
Content-Security-PolicyStrict policy blocking inline scripts, etc.
Cross-Origin-Opener-Policysame-origin
Cross-Origin-Resource-Policysame-origin
Origin-Agent-Cluster?1
Referrer-Policyno-referrer
Strict-Transport-Securitymax-age=31536000; includeSubDomains (180 days before helmet v8)
X-Content-Type-Optionsnosniff
X-DNS-Prefetch-Controloff
X-Download-Optionsnoopen
X-Frame-OptionsSAMEORIGIN
X-Permitted-Cross-Domain-Policiesnone
X-XSS-Protection0 (disables the buggy legacy filter — CSP is the real defense)

helmet() also removes X-Powered-By. Since helmet v7 (2023), Cross-Origin-Embedder-Policy is not set by default (it broke embedding third-party resources); opt in with helmet({ crossOriginEmbedderPolicy: true }).

Customizing Helmet

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc:  ["'self'", 'https://cdn.example.com'],
      styleSrc:   ["'self'", "'unsafe-inline'"],
      imgSrc:     ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com'],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  frameguard: { action: 'deny' },       // block all framing
  referrerPolicy: { policy: 'same-origin' },
}));

// Disable a specific protection
app.use(helmet({ contentSecurityPolicy: false }));

Individual Helmet Middleware

app.use(helmet.contentSecurityPolicy({ directives: { /* ... */ } }));
app.use(helmet.hsts({ maxAge: 31536000 }));
app.use(helmet.noSniff());
app.use(helmet.frameguard({ action: 'sameorigin' }));
app.use(helmet.xssFilter());  // only sets X-XSS-Protection: 0 (disables the legacy filter)
app.use(helmet.hidePoweredBy());  // same as app.disable('x-powered-by')

Rate Limiting

npm install express-rate-limit
const rateLimit = require('express-rate-limit');

// Global limiter
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  limit:    100,             // requests per window per IP ('max' is the legacy pre-v7 alias)
  standardHeaders: true,     // Return rate limit info in `RateLimit-*` headers
  legacyHeaders:   false,    // Disable `X-RateLimit-*` headers
  message: { error: 'Too many requests, please try again later.' },
});
app.use('/api', limiter);

// Tighter limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 60 * 1000,  // 1 minute
  limit: 5,
  message: { error: 'Too many login attempts.' },
  skipSuccessfulRequests: true, // don't count 2xx responses
});
app.post('/api/auth/login', authLimiter, loginHandler);

Hiding Express Fingerprint

app.disable('x-powered-by');
// Or with helmet:
app.use(helmet.hidePoweredBy());

Input Sanitization

npm install express-mongo-sanitize  # NoSQL injection (Express 4 only — see note)
npm install xss                     # XSS in string values
const mongoSanitize = require('express-mongo-sanitize');
const xss           = require('xss');

// Strip $ and . from keys (prevents NoSQL injection) — Express 4
app.use(mongoSanitize());

// Sanitize user strings before rendering
const safe = xss(req.body.comment);

Express 5 incompatibility: express-mongo-sanitize throws at request time on Express 5 because req.query is now a read-only getter it can't reassign. On v5, sanitize req.body yourself (strip $/.-prefixed keys) or validate shapes with a schema library (zod, joi) instead of mutating the request.

HTTP Parameter Pollution

npm install hpp
const hpp = require('hpp');
app.use(hpp()); // keeps last value of duplicate query params, whitelist options available

Security Checklist

ConcernSolution
CORS misconfigurationWhitelist exact origins; never * with credentials
Missing security headershelmet()
Brute-force authexpress-rate-limit on /auth/*
Large payloads (DoS)express.json({ limit: '10kb' })
XSS via reflected inputSanitize with xss; set CSP
NoSQL injectionexpress-mongo-sanitize
SQL injectionParameterized queries / ORM
Path traversalpath.basename(), resolve + check prefix
Clickjackinghelmet.frameguard({ action: 'deny' })
MIME sniffinghelmet.noSniff()
Information leakageDisable x-powered-by, generic error messages in prod
HTTPS enforcementhelmet.hsts(), redirect HTTP → HTTPS
Cookie securityhttpOnly: true, secure: true, sameSite: 'strict'