Third-party Middleware (Morgan, CORS, Helmet) · learncode.live

Why Third-Party Middleware?

Express’s ecosystem includes hundreds of ready-made middleware packages that solve common problems - logging, security, compression, rate limiting, and more. Instead of reinventing the wheel, you npm install them and plug them in.

npm install morgan cors helmet compression express-rate-limit

Morgan - HTTP Request Logger

Morgan logs every HTTP request with customizable formats.

Installation

npm install morgan

Basic Usage

const morgan = require('morgan');
app.use(morgan('dev'));

Output Formats

FormatExample
'dev'GET /users 200 5.123 ms
'short'127.0.0.1 - GET /users HTTP/1.1 200 24 -
'combined'127.0.0.1 - alice [10/Jun/2026:12:00:00 +0000] "GET /users HTTP/1.1" 200 24 "-" "Mozilla/5.0"
'common'127.0.0.1 - alice [10/Jun/2026:12:00:00 +0000] "GET /users HTTP/1.1" 200 24
'tiny'GET /users 200 24 - 5.123 ms

Custom Format String

app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));
TokenDescription
:methodHTTP method
:urlRequest URL
:statusResponse status code
:response-timeResponse time in ms
:date[iso]ISO timestamp
:res[header]Response header value
:req[header]Request header value
:remote-addrClient IP
:http-versionHTTP version
:user-agentUser agent string
:referrerReferrer header

Conditional Logging

// Skip logging for static files
app.use(morgan('dev', {
  skip: (req) => req.url.startsWith('/static')
}));

// Only log errors
app.use(morgan('dev', {
  skip: (req, res) => res.statusCode < 400
}));

// Write to file instead of stdout
const fs = require('fs');
const path = require('path');
const accessLogStream = fs.createWriteStream(
  path.join(__dirname, 'access.log'),
  { flags: 'a' }
);
app.use(morgan('combined', { stream: accessLogStream }));

Full Example

const morgan = require('morgan');

if (process.env.NODE_ENV === 'production') {
  app.use(morgan('combined', { stream: accessLogStream }));
} else {
  app.use(morgan('dev'));
}

CORS - Cross-Origin Resource Sharing

CORS enables your API to be called from browsers running on different domains.

Why CORS Matters

Without CORS:                          With CORS:
                                       │
  Browser                              │  Browser
    │                                  │    │
    │── GET api.example.com/data ─────→│    │── GET api.example.com/data ─────→│
    │←── 200 (no Access-Control-*) ────│    │←── 200 Access-Control-Allow-Origin: * ──│
    │                                  │    │    ↑
    │  ❌ Blocked by browser           │    │  ✅ Allowed

Installation

npm install cors

Basic Usage

const cors = require('cors');

// Allow all origins (not for production)
app.use(cors());

Configuration Options

app.use(cors({
  origin: 'https://myapp.com',       // Allow single origin
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Total-Count'],  // Headers exposed to browser
  credentials: true,                   // Allow cookies/auth headers
  maxAge: 86400,                       // Cache preflight for 24h
  preflightContinue: false
}));

Multiple Origins

const allowedOrigins = ['https://app1.com', 'https://app2.com'];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

Per-Route CORS

// Public API - open to all
app.get('/api/public', cors(), (req, res) => {
  res.json({ data: 'public' });
});

// Private API - restricted
const corsOptions = {
  origin: 'https://admin.myapp.com',
  credentials: true
};
app.get('/api/private', cors(corsOptions), (req, res) => {
  res.json({ data: 'secret' });
});

Helmet - Security Headers

Helmet sets various HTTP security headers to protect your app from common web vulnerabilities.

Installation

npm install helmet

Basic Usage

const helmet = require('helmet');
app.use(helmet()); // Sets 15+ security headers

What Helmet Does

HeaderPurpose
Content-Security-PolicyPrevents XSS and data injection
X-Content-Type-OptionsPrevents MIME-type sniffing
X-Frame-OptionsPrevents clickjacking
X-XSS-ProtectionEnables browser XSS filter
Strict-Transport-SecurityEnforces HTTPS
Referrer-PolicyControls Referrer header
Permissions-PolicyRestricts browser APIs

Custom Configuration

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "https://images.example.com"]
    }
  },
  frameguard: { action: 'deny' },
  referrerPolicy: { policy: 'same-origin' }
}));

Disabling Specific Headers

app.use(helmet({
  contentSecurityPolicy: false,    // Disable if using inline scripts
  crossOriginEmbedderPolicy: false // Disable for cross-origin assets
}));

Compression - Gzip Responses

Compression reduces response size by gzipping it.

Installation

npm install compression

Usage

const compression = require('compression');
app.use(compression());

// With options
app.use(compression({
  level: 6,           // Compression level 0-9 (default: -1)
  threshold: 1024,    // Minimum size in bytes to compress (default: 0)
  filter: (req, res) => {
    if (req.headers['x-no-compression']) return false;
    return compression.filter(req, res);
  }
}));

express-rate-limit - Rate Limiting

express-rate-limit protects your app from brute-force attacks and DOS.

Installation

npm install express-rate-limit

Usage

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

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,                    // Max 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later' }
});

// Apply to all routes
app.use(limiter);

// Apply only to /api/auth
app.use('/api/auth', limiter);

Putting It All Together

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

const app = express();

// 1. Security headers (should be first)
app.use(helmet());

// 2. Compression
app.use(compression());

// 3. CORS
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*',
  credentials: true
}));

// 4. Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 5. Logging
if (process.env.NODE_ENV !== 'test') {
  app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
}

// 6. Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: 'Too many requests' }
});
app.use('/api', limiter);

// 7. Routes
app.get('/', (req, res) => res.send('Hello World'));
app.use('/api/users', require('./routes/users'));

// 8. Error handler
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: 'Internal server error' });
});

app.listen(3000);
PackagePurposeWeekly Downloads
morganHTTP request logging20M+
corsCross-Origin Resource Sharing50M+
helmetSecurity headers15M+
compressionGzip/Brotli response compression10M+
express-rate-limitRate limiting5M+
cookie-parserParse Cookie header15M+
express-sessionSession management10M+
passportAuthentication strategies10M+
multerFile upload handling5M+
express-validatorInput validation5M+

Key Takeaways

  • Morgan logs HTTP requests - use 'dev' in development, 'combined' in production
  • CORS enables cross-origin requests - configure origin tightly in production
  • Helmet adds 15+ security headers - it’s a must for production apps
  • Compression reduces bandwidth - add it early in the middleware chain
  • Rate limiting prevents abuse - apply specifically to auth endpoints
  • Always order middleware: security → parsing → logging → routes → error handler
Courses