Moving from local development to production requires process management, environment configuration, and monitoring.

Production Checklist

  • Set NODE_ENV=production
  • Use environment variables for all secrets
  • Enable HTTPS
  • Set up logging and monitoring
  • Configure health check endpoints
  • Use a process manager

Health Check Endpoint

  app.get('/health', (req, res) => {
    res.json({ status: 'ok', uptime: process.uptime() });
});
  

PM2 Process Manager

  npm install -g pm2

pm2 start src/index.js --name my-api
pm2 logs my-api
pm2 restart my-api
pm2 stop my-api

# Auto-restart on server reboot
pm2 startup
pm2 save
  

ecosystem.config.js:

  module.exports = {
    apps: [{
        name: 'my-api',
        script: 'src/index.js',
        instances: 'max',
        exec_mode: 'cluster',
        env: {
            NODE_ENV: 'production',
            PORT: 3000
        }
    }]
};
  
  pm2 start ecosystem.config.js
  

Docker

Dockerfile:

  FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
  
  docker build -t my-api .
docker run -p 3000:3000 --env-file .env my-api
  

Cloud Platforms

Platform Best for
Railway Quick deploys, hobby projects
Render Free tier, auto-deploy from Git
Fly.io Global edge deployment
AWS ECS/Lambda Enterprise scale
Vercel Serverless functions

Environment on Cloud

Set variables in the platform dashboard — never commit .env to git.

Graceful Shutdown

  const server = app.listen(PORT);

process.on('SIGTERM', () => {
    console.log('SIGTERM received, shutting down gracefully');
    server.close(() => {
        mongoose.connection.close(false, () => process.exit(0));
    });
});
  

Logging

Use structured logging in production:

  npm install winston
  
  import winston from 'winston';

const logger = winston.createLogger({
    level: 'info',
    format: 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() }));
}
  

Deploy early and often — start with a simple platform like Render or Railway, then scale as needed.