> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 10. Deployment & Best Practices

> Learn how to prepare your Node.js application for production and deploy it.

# Deployment & Best Practices

You've built your Node.js application, and it works perfectly on your machine. But **running in production is a completely different challenge**. Production environments face real-world constraints: thousands of concurrent users, security threats, server crashes, and the need for zero-downtime updates.

The gap between "it works on my machine" and "it works in production" is where most Node.js projects fail. A senior engineer would tell you that writing the application is only about 30% of the work--the other 70% is making it reliable, observable, secure, and deployable. This chapter covers that 70%.

## Development vs. Production

Understanding the difference is crucial:

| Aspect             | Development           | Production                     |
| ------------------ | --------------------- | ------------------------------ |
| **Error messages** | Detailed stack traces | Generic user-friendly messages |
| **Performance**    | Not optimized         | Maximized, gzip compression    |
| **Security**       | Relaxed (localhost)   | Hardened, HTTPS required       |
| **Logging**        | Console output        | Structured logs, monitoring    |
| **Restarts**       | Manual                | Automatic recovery on crash    |
| **Configuration**  | Hardcoded values OK   | Environment variables only     |

## Why Production Best Practices Matter

### Real-World Failures

Ignoring production best practices leads to:

* **Security breaches**: Hardcoded API keys leaked in public repositories
* **Downtime**: Single process crashes take down entire application
* **Data loss**: No proper error handling or logging
* **Poor performance**: Uncompressed responses, inefficient code

<Warning>
  A single exposed API key or database password can compromise your entire application and user data. Never commit secrets to version control.
</Warning>

Let's go through essential practices to make your application production-ready.

## Environment Variables

Never hardcode sensitive information (API keys, database passwords, ports) in your code. Use environment variables.

1. Install `dotenv`:
   ```bash theme={null}
   npm install dotenv
   ```

2. Create a `.env` file:
   ```
   PORT=5000
   DB_URI=mongodb://localhost:27017/myapp
   SECRET_KEY=mysecretkey
   ```

3. Use it in your code:
   ```javascript theme={null}
   // Load .env file FIRST, before any other imports that might need env vars.
   // This must be the very first line in your entry file.
   require('dotenv').config();

   const PORT = process.env.PORT || 3000;
   console.log(process.env.DB_URI);
   ```

4. **Important**: Add `.env` to your `.gitignore` file so it's not committed to version control.

## Production Best Practices

1. **Use Gzip Compression**: Use the `compression` middleware to decrease the size of the response body.
   ```javascript theme={null}
   const compression = require('compression');
   app.use(compression());
   ```

2. **Security Headers**: Use `helmet` to set various HTTP headers for security.
   ```javascript theme={null}
   const helmet = require('helmet');
   app.use(helmet());
   ```

3. **Logging**: Use a logger like `morgan` or `winston` instead of `console.log`.

4. **Error Handling**: Implement a global error handling middleware.

5. **Clustering**: Use Node.js Cluster module or a process manager like PM2 to take advantage of multi-core systems.

## Deployment Options

### 1. Heroku (PaaS)

Heroku is a popular platform for deploying Node.js apps.

1. Create a `Procfile` in the root:
   ```
   web: node server.js
   ```
2. Install Heroku CLI and login.
3. `heroku create`
4. `git push heroku main`

### 2. Vercel / Netlify (Serverless)

Great for static sites and serverless functions. Next.js apps deploy seamlessly on Vercel.

### 3. VPS (DigitalOcean, AWS EC2, Linode)

For full control, you can rent a Virtual Private Server.

* Set up Linux (Ubuntu).
* Install Node.js and Nginx (as a reverse proxy).
* Use **PM2** to keep your app running.

## Using PM2

PM2 is a production process manager for Node.js. It solves two critical problems: (1) your Node.js process crashes and nobody restarts it, leaving users with a dead server, and (2) your single-threaded Node process only uses one CPU core on a multi-core machine. PM2 handles automatic restarts on crash and can run your app in cluster mode across all available cores.

```bash theme={null}
npm install pm2 -g
pm2 start server.js

# Cluster mode -- run one process per CPU core for maximum throughput
pm2 start server.js -i max

# Generate a startup script so PM2 restarts your app after server reboot
pm2 startup
pm2 save
```

Commands:

* `pm2 list`: List running processes.
* `pm2 stop <id>`: Stop a process.
* `pm2 restart <id>`: Restart a process.
* `pm2 logs`: View logs.
* `pm2 monit`: Real-time monitoring dashboard.

## Summary

* Use **Environment Variables** for configuration--never hardcode secrets, database URLs, or API keys
* Follow security best practices (Helmet for headers, Compression for performance)
* Use a process manager like **PM2** for VPS deployments--it handles crash recovery and clustering
* Platforms like **Heroku**, **Vercel**, and **Railway** offer easy deployment for getting started

## Docker Deployment

### Basic Dockerfile

```dockerfile theme={null}
# Dockerfile
FROM node:20-alpine

# Create app directory
WORKDIR /usr/src/app

# Copy package files FIRST -- this layer is cached as long as dependencies
# do not change, so rebuilds after code-only changes are much faster.
COPY package*.json ./

# Use npm ci (not npm install) for reproducible builds from lockfile.
# --only=production skips devDependencies, reducing image size.
RUN npm ci --only=production

# Copy source code AFTER installing deps to maximize Docker layer caching
COPY . .

# EXPOSE documents the port but does not publish it -- you still need
# -p 3000:3000 when running the container
EXPOSE 3000

# Use array syntax (exec form) so Node.js receives SIGTERM directly
# for graceful shutdown. String form runs via /bin/sh which swallows signals.
CMD ["node", "server.js"]
```

### Optimized Multi-Stage Build

```dockerfile theme={null}
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build  # If you have a build step

# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodeapp -u 1001

# Copy only production dependencies
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Copy built app
COPY --from=builder /app/dist ./dist

# Switch to non-root user
USER nodeapp

EXPOSE 3000
CMD ["node", "dist/server.js"]
```

### Docker Compose for Development

```yaml theme={null}
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=mongodb://mongo:27017/myapp
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      - mongo
      - redis

  mongo:
    image: mongo:6
    volumes:
      - mongo-data:/data/db
    ports:
      - "27017:27017"

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

volumes:
  mongo-data:
```

```bash theme={null}
# Run with Docker Compose
docker-compose up -d
docker-compose logs -f app
docker-compose down
```

## AWS Deployment Options

| Service               | Best For                  | Complexity |
| --------------------- | ------------------------- | ---------- |
| **Elastic Beanstalk** | Quick deployment, managed | Low        |
| **ECS/Fargate**       | Docker containers         | Medium     |
| **EC2**               | Full control              | High       |
| **Lambda**            | Serverless, APIs          | Low        |
| **App Runner**        | Containers, simple        | Low        |

### AWS Elastic Beanstalk

```bash theme={null}
# Install EB CLI
pip install awsebcli

# Initialize and deploy
eb init my-node-app --platform node.js
eb create production
eb deploy

# Set environment variables
eb setenv NODE_ENV=production DATABASE_URL=...
```

## Health Checks

Always implement health endpoints:

```javascript theme={null}
// Basic health check
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

// Comprehensive health check
app.get('/health/detailed', async (req, res) => {
  const health = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: 'healthy',
    checks: {}
  };

  // Database check
  try {
    await mongoose.connection.db.admin().ping();
    health.checks.database = { status: 'healthy' };
  } catch (error) {
    health.status = 'unhealthy';
    health.checks.database = { status: 'unhealthy', error: error.message };
  }

  // Redis check
  try {
    await redis.ping();
    health.checks.redis = { status: 'healthy' };
  } catch (error) {
    health.status = 'unhealthy';
    health.checks.redis = { status: 'unhealthy', error: error.message };
  }

  // Memory check
  const memUsage = process.memoryUsage();
  health.checks.memory = {
    heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`
  };

  res.status(health.status === 'healthy' ? 200 : 503).json(health);
});
```

## Graceful Shutdown

A graceful shutdown means your server stops accepting new connections but finishes processing all in-flight requests before exiting. Without graceful shutdown, deploying a new version kills active requests mid-response--users see errors, database transactions get interrupted, and files get partially written. Every production Node.js server should implement this pattern.

```javascript theme={null}
const server = app.listen(PORT);

const gracefulShutdown = async (signal) => {
  console.log(`Received ${signal}. Starting graceful shutdown...`);

  // Stop accepting new connections
  server.close(async () => {
    console.log('HTTP server closed');

    try {
      // Close database connections
      await mongoose.connection.close();
      console.log('MongoDB connection closed');

      // Close Redis
      await redis.quit();
      console.log('Redis connection closed');

      console.log('Graceful shutdown completed');
      process.exit(0);
    } catch (error) {
      console.error('Error during shutdown:', error);
      process.exit(1);
    }
  });

  // Force shutdown after 30 seconds
  setTimeout(() => {
    console.error('Forced shutdown after timeout');
    process.exit(1);
  }, 30000);
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
```

## CI/CD with GitHub Actions

```yaml theme={null}
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to production
        run: |
          # Add deployment steps
          echo "Deploying..."
```
