Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/expressjs/express/llms.txt

Use this file to discover all available pages before exploring further.

Deploying Express applications to production requires careful attention to performance, security, and reliability. This guide covers essential best practices.

Environment Configuration

1

Set NODE_ENV to production

export NODE_ENV=production
This enables:
  • View template caching
  • Less verbose error messages
  • Performance optimizations in many packages
2

Use environment variables

require('dotenv').config();

const config = {
  port: process.env.PORT || 3000,
  dbUrl: process.env.DATABASE_URL,
  sessionSecret: process.env.SESSION_SECRET
};
3

Validate configuration on startup

const requiredEnvVars = [
  'DATABASE_URL',
  'SESSION_SECRET',
  'API_KEY'
];

requiredEnvVars.forEach(varName => {
  if (!process.env[varName]) {
    console.error(`Missing required environment variable: ${varName}`);
    process.exit(1);
  }
});

Performance Optimization

Enable Compression

Compress response bodies to reduce bandwidth:
npm install compression
const compression = require('compression');
const express = require('express');
const app = express();

app.use(compression());

Use a Reverse Proxy

Use nginx or Apache as a reverse proxy to handle static files, SSL termination, load balancing, and caching.
upstream app {
  server 127.0.0.1:3000;
  server 127.0.0.1:3001;
  server 127.0.0.1:3002;
}

server {
  listen 80;
  server_name example.com;

  # Redirect HTTP to HTTPS
  return 301 https://$server_name$request_uri;
}

server {
  listen 443 ssl http2;
  server_name example.com;

  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;

  # Serve static files directly
  location /static/ {
    alias /path/to/static/;
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # Proxy to Express app
  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Enable Caching

const express = require('express');
const app = express();

// Cache static files
app.use(express.static('public', {
  maxAge: '1y',
  etag: true,
  lastModified: true
}));

// Cache views in production
if (app.get('env') === 'production') {
  app.enable('view cache');
}

Process Management

Use a Process Manager

Use PM2 to manage your Node.js processes, handle crashes, and enable zero-downtime deployments.
npm install -g pm2

# Start app with PM2
pm2 start app.js -i max

# Start with ecosystem file
pm2 start ecosystem.config.js
ecosystem.config.js
module.exports = {
  apps: [{
    name: 'express-app',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
  }]
};

Graceful Shutdown

const express = require('express');
const app = express();

const server = app.listen(3000);

// Handle shutdown signals
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

function gracefulShutdown() {
  console.log('Received shutdown signal, closing server...');
  
  server.close(() => {
    console.log('HTTP server closed');
    
    // Close database connections
    mongoose.connection.close(false, () => {
      console.log('MongoDB connection closed');
      process.exit(0);
    });
  });
  
  // Force shutdown after 10 seconds
  setTimeout(() => {
    console.error('Forcing shutdown');
    process.exit(1);
  }, 10000);
}

Logging

Don’t use console.log in production. Use a proper logging library.
npm install winston morgan
const winston = require('winston');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

Error Handling

const logger = require('./logger');

// 404 handler
app.use((req, res, next) => {
  res.status(404).json({ error: 'Not found' });
});

// Error handler
app.use((err, req, res, next) => {
  // Log error
  logger.error({
    message: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method
  });
  
  // Don't leak error details in production
  const statusCode = err.status || 500;
  const message = process.env.NODE_ENV === 'production'
    ? 'Internal server error'
    : err.message;
  
  res.status(statusCode).json({ error: message });
});

Database Connection Pooling

const mongoose = require('mongoose');

mongoose.connect(process.env.DATABASE_URL, {
  maxPoolSize: 10,
  minPoolSize: 5,
  socketTimeoutMS: 45000,
  serverSelectionTimeoutMS: 5000
});

Health Checks

app.get('/health', async (req, res) => {
  const healthcheck = {
    uptime: process.uptime(),
    message: 'OK',
    timestamp: Date.now()
  };
  
  try {
    // Check database connection
    await mongoose.connection.db.admin().ping();
    healthcheck.database = 'connected';
    
    res.status(200).json(healthcheck);
  } catch (error) {
    healthcheck.database = 'disconnected';
    healthcheck.message = error.message;
    res.status(503).json(healthcheck);
  }
});

Security Headers

const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet());
app.disable('x-powered-by');

// Enable CORS for specific origins
const cors = require('cors');
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true
}));

Request Size Limits

const express = require('express');
const app = express();

// Limit request body size
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

Monitoring

Use APM tools like:
  • New Relic
  • Datadog
  • AppDynamics
  • Elastic APM
npm install newrelic
// Must be first require
require('newrelic');
const express = require('express');
const client = require('prom-client');

// Create metrics
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code']
});

// Track metrics
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration.labels(req.method, req.route?.path || req.path, res.statusCode).observe(duration);
  });
  next();
});

// Expose metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', client.register.contentType);
  res.end(await client.register.metrics());
});

Production Checklist

1

Environment

  • Set NODE_ENV=production
  • Use environment variables for config
  • Validate required env vars on startup
2

Performance

  • Enable compression
  • Use reverse proxy (nginx/Apache)
  • Enable view caching
  • Configure static file caching
  • Use connection pooling
3

Reliability

  • Use process manager (PM2)
  • Implement graceful shutdown
  • Add health check endpoints
  • Set up monitoring and alerts
4

Security

  • Use HTTPS
  • Set security headers (Helmet)
  • Implement rate limiting
  • Validate and sanitize input
  • Keep dependencies updated
5

Logging

  • Use proper logging library
  • Log errors with context
  • Set up log aggregation
  • Don’t log sensitive data

Build docs developers (and LLMs) love