+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 209 of 355

๐Ÿš€ Process Management: PM2 Integration

Master process management: pm2 integration in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป
  • Node.js fundamentals ๐Ÿ–ฅ๏ธ

What you'll learn

  • Understand PM2 process management fundamentals ๐ŸŽฏ
  • Apply PM2 in real TypeScript projects ๐Ÿ—๏ธ
  • Debug common PM2 integration issues ๐Ÿ›
  • Write type-safe process management code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on PM2 process management! ๐ŸŽ‰ In this guide, weโ€™ll explore how to supercharge your TypeScript Node.js applications with PM2โ€™s powerful process management capabilities.

Youโ€™ll discover how PM2 can transform your TypeScript backend development experience. Whether youโ€™re building web servers ๐ŸŒ, microservices ๐Ÿ–ฅ๏ธ, or API gateways ๐Ÿ“š, understanding PM2 is essential for creating production-ready, scalable applications.

By the end of this tutorial, youโ€™ll feel confident deploying and managing TypeScript applications with PM2! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding PM2

๐Ÿค” What is PM2?

PM2 is like a superhero manager for your Node.js applications ๐Ÿฆธโ€โ™‚๏ธ. Think of it as a personal assistant that keeps your apps running smoothly, restarts them if they crash, and helps them scale to handle more users!

In TypeScript terms, PM2 is a production process manager that provides:

  • โœจ Zero-downtime deployments
  • ๐Ÿš€ Automatic application restarts
  • ๐Ÿ›ก๏ธ Built-in load balancing and clustering
  • ๐Ÿ“Š Real-time monitoring and logging
  • ๐Ÿ”„ Hot reloading for development

๐Ÿ’ก Why Use PM2 with TypeScript?

Hereโ€™s why developers love PM2 for TypeScript projects:

  1. Production Ready ๐Ÿญ: Handle crashes gracefully
  2. Performance โšก: Built-in clustering for multi-core utilization
  3. Developer Experience ๐Ÿ’ป: Hot reload during development
  4. Monitoring ๐Ÿ“Š: Real-time app metrics and logs
  5. DevOps Integration ๐Ÿ”ง: Easy CI/CD pipeline integration

Real-world example: Imagine building an e-commerce API ๐Ÿ›’. With PM2, if your checkout service crashes during Black Friday traffic, PM2 automatically restarts it in milliseconds - your customers never notice!

๐Ÿ”ง Basic Setup and Usage

๐Ÿ“ฆ Installation

Letโ€™s start with installing PM2 globally:

# ๐Ÿš€ Install PM2 globally
npm install -g pm2

# ๐Ÿ“ Verify installation
pm2 --version

๐Ÿ“ Simple TypeScript App

First, letโ€™s create a TypeScript application to manage:

// ๐Ÿ‘‹ app.ts - Our sample TypeScript server
import express from 'express';

const app = express();
const PORT = process.env.PORT || 3000;

// ๐ŸŽจ Middleware
app.use(express.json());

// ๐Ÿ  Routes
app.get('/', (req, res) => {
  res.json({ 
    message: 'Hello from TypeScript! ๐Ÿš€',
    timestamp: new Date().toISOString(),
    process: process.pid
  });
});

app.get('/health', (req, res) => {
  res.json({ 
    status: 'healthy ๐Ÿ’š',
    uptime: process.uptime(),
    memory: process.memoryUsage()
  });
});

// ๐ŸŽฏ Start server
app.listen(PORT, () => {
  console.log(`๐Ÿš€ Server running on port ${PORT} with PID ${process.pid}`);
});

export default app;

๐ŸŽฏ Package.json Setup

{
  "name": "typescript-pm2-app",
  "scripts": {
    "build": "tsc",
    "start": "node dist/app.js",
    "dev": "ts-node app.ts",
    "pm2:start": "pm2 start ecosystem.config.js",
    "pm2:stop": "pm2 stop all",
    "pm2:restart": "pm2 restart all",
    "pm2:logs": "pm2 logs"
  },
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.0",
    "@types/node": "^18.0.0",
    "typescript": "^4.9.0",
    "ts-node": "^10.9.0"
  }
}

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce API with PM2

Letโ€™s build a production-ready e-commerce API:

// ๐Ÿ›๏ธ ecommerce-api.ts
import express from 'express';
import { createServer } from 'http';

interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
  stock: number;
}

interface Order {
  id: string;
  products: Product[];
  total: number;
  status: 'pending' | 'processing' | 'shipped' | 'delivered';
  emoji: string;
}

class EcommerceAPI {
  private app = express();
  private server = createServer(this.app);
  private products: Product[] = [];
  private orders: Order[] = [];

  constructor() {
    this.setupMiddleware();
    this.setupRoutes();
    this.seedData();
  }

  // ๐Ÿ”ง Setup middleware
  private setupMiddleware(): void {
    this.app.use(express.json());
    
    // ๐Ÿ“Š Request logging
    this.app.use((req, res, next) => {
      console.log(`๐Ÿ“ ${new Date().toISOString()} - ${req.method} ${req.path} - PID: ${process.pid}`);
      next();
    });
  }

  // ๐Ÿ›’ Setup routes
  private setupRoutes(): void {
    // ๐Ÿ  Health check
    this.app.get('/health', (req, res) => {
      res.json({
        status: 'healthy ๐Ÿ’š',
        process: process.pid,
        uptime: process.uptime(),
        memory: process.memoryUsage()
      });
    });

    // ๐Ÿ“ฆ Get products
    this.app.get('/products', (req, res) => {
      res.json({
        success: true,
        products: this.products,
        message: 'Products fetched successfully! ๐Ÿ›๏ธ'
      });
    });

    // ๐Ÿ›’ Create order
    this.app.post('/orders', (req, res) => {
      const { productIds } = req.body;
      
      try {
        const orderProducts = this.products.filter(p => 
          productIds.includes(p.id) && p.stock > 0
        );
        
        if (orderProducts.length === 0) {
          return res.status(400).json({
            success: false,
            message: 'No available products found! ๐Ÿ˜…'
          });
        }

        const order: Order = {
          id: `order_${Date.now()}`,
          products: orderProducts,
          total: orderProducts.reduce((sum, p) => sum + p.price, 0),
          status: 'pending',
          emoji: '๐Ÿ“ฆ'
        };

        this.orders.push(order);
        
        // ๐Ÿ“‰ Update stock
        orderProducts.forEach(orderedProduct => {
          const product = this.products.find(p => p.id === orderedProduct.id);
          if (product) product.stock--;
        });

        res.json({
          success: true,
          order,
          message: 'Order created successfully! ๐ŸŽ‰'
        });
      } catch (error) {
        console.error('โŒ Order creation failed:', error);
        res.status(500).json({
          success: false,
          message: 'Failed to create order ๐Ÿ˜ฐ'
        });
      }
    });

    // ๐Ÿ“‹ Get orders
    this.app.get('/orders', (req, res) => {
      res.json({
        success: true,
        orders: this.orders,
        message: 'Orders fetched successfully! ๐Ÿ“ฆ'
      });
    });
  }

  // ๐ŸŒฑ Seed initial data
  private seedData(): void {
    this.products = [
      { id: '1', name: 'TypeScript Handbook', price: 29.99, emoji: '๐Ÿ“˜', stock: 10 },
      { id: '2', name: 'Premium Coffee', price: 12.99, emoji: 'โ˜•', stock: 50 },
      { id: '3', name: 'Wireless Headphones', price: 89.99, emoji: '๐ŸŽง', stock: 5 },
      { id: '4', name: 'Laptop Sticker Pack', price: 4.99, emoji: '๐Ÿ’ป', stock: 100 }
    ];
  }

  // ๐Ÿš€ Start server
  start(port: number = 3000): void {
    this.server.listen(port, () => {
      console.log(`๐Ÿš€ E-commerce API running on port ${port}`);
      console.log(`๐Ÿ“Š Process ID: ${process.pid}`);
      console.log(`โšก Environment: ${process.env.NODE_ENV || 'development'}`);
    });
  }

  // ๐Ÿ›‘ Graceful shutdown
  shutdown(): void {
    console.log('๐Ÿ›‘ Shutting down gracefully...');
    this.server.close(() => {
      console.log('โœ… Server closed successfully');
      process.exit(0);
    });
  }
}

// ๐ŸŽฏ Handle graceful shutdown
const api = new EcommerceAPI();

process.on('SIGTERM', () => api.shutdown());
process.on('SIGINT', () => api.shutdown());

// ๐Ÿš€ Start the API
api.start(parseInt(process.env.PORT || '3000'));

๐ŸŽฎ Example 2: PM2 Ecosystem Configuration

Create a comprehensive PM2 configuration:

// ๐Ÿ”ง ecosystem.config.js
module.exports = {
  apps: [
    {
      // ๐Ÿ›๏ธ E-commerce API
      name: 'ecommerce-api',
      script: './dist/ecommerce-api.js',
      instances: 'max', // ๐Ÿš€ Use all CPU cores
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3000
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 8080
      },
      // ๐Ÿ“Š Monitoring options
      log_file: './logs/combined.log',
      out_file: './logs/out.log',
      error_file: './logs/error.log',
      time: true,
      
      // ๐Ÿ”„ Restart options
      watch: false,
      ignore_watch: ['node_modules', 'logs'],
      max_memory_restart: '1G',
      
      // ๐Ÿ›ก๏ธ Advanced options
      kill_timeout: 5000,
      wait_ready: true,
      listen_timeout: 10000
    },
    {
      // ๐ŸŽฏ Background worker
      name: 'order-processor',
      script: './dist/worker.js',
      instances: 2,
      exec_mode: 'cluster',
      cron_restart: '0 0 * * *', // ๐Ÿ•› Restart daily at midnight
      env: {
        NODE_ENV: 'development',
        WORKER_TYPE: 'order-processor'
      },
      env_production: {
        NODE_ENV: 'production',
        WORKER_TYPE: 'order-processor'
      }
    }
  ],
  
  // ๐Ÿ“Š Deploy configuration
  deploy: {
    production: {
      user: 'deploy',
      host: 'your-server.com',
      ref: 'origin/main',
      repo: '[email protected]:your-username/your-repo.git',
      path: '/var/www/your-app',
      'post-deploy': 'npm install && npm run build && pm2 reload ecosystem.config.js --env production'
    }
  }
};

๐Ÿ”„ Example 3: TypeScript Process Monitor

// ๐Ÿ“Š process-monitor.ts
import pm2 from 'pm2';

interface ProcessInfo {
  name: string;
  pid: number;
  status: string;
  cpu: number;
  memory: number;
  uptime: number;
  emoji: string;
}

class PM2Monitor {
  private processes: ProcessInfo[] = [];

  // ๐Ÿ” Connect to PM2
  async connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      pm2.connect((err) => {
        if (err) {
          console.error('โŒ Failed to connect to PM2:', err);
          reject(err);
        } else {
          console.log('โœ… Connected to PM2 successfully! ๐ŸŽ‰');
          resolve();
        }
      });
    });
  }

  // ๐Ÿ“Š Get process list
  async getProcessList(): Promise<ProcessInfo[]> {
    return new Promise((resolve, reject) => {
      pm2.list((err, list) => {
        if (err) {
          reject(err);
          return;
        }

        this.processes = list.map(proc => ({
          name: proc.name || 'unknown',
          pid: proc.pid || 0,
          status: proc.pm2_env?.status || 'unknown',
          cpu: proc.cpu || 0,
          memory: (proc.memory || 0) / 1024 / 1024, // ๐Ÿ“Š Convert to MB
          uptime: Date.now() - (proc.pm2_env?.created_at || 0),
          emoji: this.getStatusEmoji(proc.pm2_env?.status || 'unknown')
        }));

        resolve(this.processes);
      });
    });
  }

  // ๐ŸŽจ Get status emoji
  private getStatusEmoji(status: string): string {
    const emojiMap: Record<string, string> = {
      'online': '๐ŸŸข',
      'stopped': '๐Ÿ”ด',
      'stopping': '๐ŸŸก',
      'launching': '๐ŸŸก',
      'errored': '๐Ÿ’ฅ',
      'one-launch-status': '๐Ÿ”ต'
    };
    return emojiMap[status] || 'โšช';
  }

  // ๐Ÿ“Š Display process stats
  displayStats(): void {
    console.log('\n๐Ÿ“Š PM2 Process Monitor Dashboard');
    console.log('================================');
    
    if (this.processes.length === 0) {
      console.log('๐Ÿ˜ด No processes running');
      return;
    }

    this.processes.forEach(proc => {
      console.log(`${proc.emoji} ${proc.name}`);
      console.log(`   PID: ${proc.pid}`);
      console.log(`   Status: ${proc.status}`);
      console.log(`   CPU: ${proc.cpu.toFixed(1)}%`);
      console.log(`   Memory: ${proc.memory.toFixed(1)} MB`);
      console.log(`   Uptime: ${this.formatUptime(proc.uptime)}`);
      console.log('');
    });
  }

  // โฐ Format uptime
  private formatUptime(ms: number): string {
    const seconds = Math.floor(ms / 1000);
    const minutes = Math.floor(seconds / 60);
    const hours = Math.floor(minutes / 60);
    
    if (hours > 0) return `${hours}h ${minutes % 60}m`;
    if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
    return `${seconds}s`;
  }

  // ๐Ÿ”„ Start monitoring
  async startMonitoring(interval: number = 5000): Promise<void> {
    console.log('๐Ÿš€ Starting PM2 monitoring...');
    
    setInterval(async () => {
      try {
        await this.getProcessList();
        console.clear();
        this.displayStats();
      } catch (error) {
        console.error('โŒ Monitoring error:', error);
      }
    }, interval);
  }

  // ๐Ÿ›‘ Disconnect
  disconnect(): void {
    pm2.disconnect();
    console.log('๐Ÿ‘‹ Disconnected from PM2');
  }
}

// ๐ŸŽฏ Usage example
async function runMonitor() {
  const monitor = new PM2Monitor();
  
  try {
    await monitor.connect();
    await monitor.startMonitoring();
  } catch (error) {
    console.error('๐Ÿ’ฅ Monitor failed:', error);
    monitor.disconnect();
  }
}

// ๐Ÿš€ Start monitoring if this file is run directly
if (require.main === module) {
  runMonitor();
}

export default PM2Monitor;

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Configuration Patterns

When youโ€™re ready to level up, try these advanced PM2 patterns:

// ๐ŸŽฏ Advanced ecosystem configuration with TypeScript types
interface PM2AppConfig {
  name: string;
  script: string;
  instances: number | 'max';
  exec_mode: 'cluster' | 'fork';
  env: Record<string, string>;
  env_production: Record<string, string>;
  log_file: string;
  out_file: string;
  error_file: string;
  time: boolean;
  max_memory_restart: string;
  kill_timeout: number;
  wait_ready: boolean;
  listen_timeout: number;
}

interface PM2EcosystemConfig {
  apps: PM2AppConfig[];
  deploy?: {
    production: {
      user: string;
      host: string;
      ref: string;
      repo: string;
      path: string;
      'post-deploy': string;
    };
  };
}

// ๐Ÿช„ Type-safe ecosystem configuration
const createEcosystemConfig = (): PM2EcosystemConfig => ({
  apps: [
    {
      name: 'typescript-api',
      script: './dist/app.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: '3000',
        LOG_LEVEL: 'debug'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: '8080',
        LOG_LEVEL: 'info'
      },
      log_file: './logs/combined.log',
      out_file: './logs/out.log',
      error_file: './logs/error.log',
      time: true,
      max_memory_restart: '1G',
      kill_timeout: 5000,
      wait_ready: true,
      listen_timeout: 10000
    }
  ]
});

๐Ÿ—๏ธ Advanced Health Monitoring

For the brave developers, hereโ€™s a comprehensive health monitoring system:

// ๐Ÿš€ Advanced health monitoring with PM2
interface HealthMetrics {
  timestamp: Date;
  process: {
    pid: number;
    uptime: number;
    memory: NodeJS.MemoryUsage;
    cpu: number;
  };
  application: {
    status: 'healthy' | 'degraded' | 'unhealthy';
    responseTime: number;
    errorRate: number;
    throughput: number;
  };
  emoji: string;
}

class AdvancedHealthMonitor {
  private metrics: HealthMetrics[] = [];
  private readonly maxMetrics = 100; // ๐Ÿ“Š Keep last 100 measurements

  // ๐Ÿ” Collect comprehensive metrics
  async collectMetrics(): Promise<HealthMetrics> {
    const startTime = Date.now();
    
    // ๐Ÿงช Simulate application health check
    const healthCheck = await this.performHealthCheck();
    const responseTime = Date.now() - startTime;

    const metrics: HealthMetrics = {
      timestamp: new Date(),
      process: {
        pid: process.pid,
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        cpu: process.cpuUsage().user / 1000000 // ๐Ÿ“Š Convert to percentage
      },
      application: {
        status: healthCheck.status,
        responseTime,
        errorRate: this.calculateErrorRate(),
        throughput: this.calculateThroughput()
      },
      emoji: this.getHealthEmoji(healthCheck.status)
    };

    this.addMetrics(metrics);
    return metrics;
  }

  // ๐Ÿฉบ Perform application health check
  private async performHealthCheck(): Promise<{ status: 'healthy' | 'degraded' | 'unhealthy' }> {
    try {
      // ๐Ÿงช Add your health checks here
      const checks = [
        this.checkDatabase(),
        this.checkExternalServices(),
        this.checkMemoryUsage()
      ];

      const results = await Promise.all(checks);
      const failedChecks = results.filter(r => !r).length;

      if (failedChecks === 0) return { status: 'healthy' };
      if (failedChecks <= 1) return { status: 'degraded' };
      return { status: 'unhealthy' };
    } catch (error) {
      console.error('โŒ Health check failed:', error);
      return { status: 'unhealthy' };
    }
  }

  // ๐Ÿ—„๏ธ Mock health checks
  private async checkDatabase(): Promise<boolean> {
    // ๐Ÿงช Simulate database check
    return new Promise(resolve => setTimeout(() => resolve(Math.random() > 0.1), 50));
  }

  private async checkExternalServices(): Promise<boolean> {
    // ๐ŸŒ Simulate external service check  
    return new Promise(resolve => setTimeout(() => resolve(Math.random() > 0.05), 100));
  }

  private async checkMemoryUsage(): Promise<boolean> {
    const usage = process.memoryUsage();
    const usedMB = usage.heapUsed / 1024 / 1024;
    return usedMB < 512; // ๐Ÿ“Š Fail if using more than 512MB
  }

  // ๐Ÿ“Š Calculate error rate
  private calculateErrorRate(): number {
    // ๐Ÿงฎ Mock calculation - in real app, track actual errors
    return Math.random() * 5; // 0-5% error rate
  }

  // ๐Ÿ“ˆ Calculate throughput
  private calculateThroughput(): number {
    // ๐Ÿ“Š Mock calculation - in real app, track actual requests
    return Math.floor(Math.random() * 1000); // 0-1000 requests/min
  }

  // ๐ŸŽจ Get health emoji
  private getHealthEmoji(status: string): string {
    const emojiMap: Record<string, string> = {
      'healthy': '๐Ÿ’š',
      'degraded': '๐ŸŸก',
      'unhealthy': '๐Ÿ”ด'
    };
    return emojiMap[status] || 'โšช';
  }

  // ๐Ÿ“Š Add metrics to collection
  private addMetrics(metrics: HealthMetrics): void {
    this.metrics.push(metrics);
    if (this.metrics.length > this.maxMetrics) {
      this.metrics.shift(); // ๐Ÿ—‘๏ธ Remove oldest metric
    }
  }

  // ๐Ÿ“‹ Get health summary
  getHealthSummary(): string {
    if (this.metrics.length === 0) return '๐Ÿ˜ด No metrics available';

    const latest = this.metrics[this.metrics.length - 1];
    const memoryMB = (latest.process.memory.heapUsed / 1024 / 1024).toFixed(1);

    return `
๐Ÿ“Š Health Summary (PID: ${latest.process.pid})
${latest.emoji} Status: ${latest.application.status}
โšก Response Time: ${latest.application.responseTime}ms
๐Ÿง  Memory: ${memoryMB}MB
๐Ÿ“ˆ Throughput: ${latest.application.throughput} req/min
โŒ Error Rate: ${latest.application.errorRate.toFixed(2)}%
โฑ๏ธ Uptime: ${Math.floor(latest.process.uptime / 60)}m
    `.trim();
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Memory Leaks in Cluster Mode

// โŒ Wrong way - no cleanup!
class LeakyServer {
  private intervals: NodeJS.Timeout[] = [];
  
  start() {
    // ๐Ÿ’ฅ This will create memory leaks in cluster mode!
    this.intervals.push(setInterval(() => {
      console.log('Still running...');
    }, 1000));
  }
}

// โœ… Correct way - proper cleanup!
class CleanServer {
  private intervals: NodeJS.Timeout[] = [];
  
  start() {
    const interval = setInterval(() => {
      console.log('โœ… Running cleanly...');
    }, 1000);
    
    this.intervals.push(interval);
  }
  
  // ๐Ÿงน Cleanup method
  cleanup() {
    console.log('๐Ÿงน Cleaning up intervals...');
    this.intervals.forEach(interval => clearInterval(interval));
    this.intervals = [];
  }
  
  // ๐Ÿ›‘ Graceful shutdown
  setupGracefulShutdown() {
    process.on('SIGTERM', () => {
      console.log('๐Ÿ›‘ Received SIGTERM, shutting down gracefully');
      this.cleanup();
      process.exit(0);
    });
  }
}

๐Ÿคฏ Pitfall 2: Ignoring PM2 Signals

// โŒ Dangerous - ignoring PM2 lifecycle!
const server = express();
server.listen(3000, () => {
  console.log('Server started');
  // ๐Ÿ’ฅ No signal handling - PM2 can't manage this properly!
});

// โœ… Safe - proper PM2 integration!
const server = express();
const httpServer = server.listen(3000, () => {
  console.log('โœ… Server started with PM2 integration');
  
  // ๐Ÿ“ก Tell PM2 we're ready
  if (process.send) {
    process.send('ready');
  }
});

// ๐Ÿ›ก๏ธ Handle PM2 signals properly
process.on('SIGINT', () => {
  console.log('๐Ÿ›‘ Received SIGINT, shutting down...');
  httpServer.close(() => {
    console.log('โœ… HTTP server closed');
    process.exit(0);
  });
});

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use Ecosystem Files: Always configure PM2 with ecosystem.config.js
  2. ๐Ÿ“ Enable Logging: Set up proper log rotation and monitoring
  3. ๐Ÿ›ก๏ธ Graceful Shutdowns: Handle SIGTERM and SIGINT signals properly
  4. ๐Ÿš€ Cluster Mode: Use cluster mode for CPU-intensive applications
  5. ๐Ÿ“Š Monitor Resources: Set memory limits and restart thresholds
  6. โšก Health Checks: Implement comprehensive health monitoring
  7. ๐Ÿ”„ Zero Downtime: Use pm2 reload for deployments
  8. ๐Ÿ“ฆ Environment Management: Use different configs for dev/prod

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Complete PM2-Managed Microservice

Create a TypeScript microservice with full PM2 integration:

๐Ÿ“‹ Requirements:

  • โœ… Express API with health checks
  • ๐Ÿท๏ธ PM2 ecosystem configuration
  • ๐Ÿ‘ค Process monitoring dashboard
  • ๐Ÿ“… Graceful shutdown handling
  • ๐ŸŽจ Real-time metrics collection
  • ๐Ÿ“Š Error handling and logging

๐Ÿš€ Bonus Points:

  • Add clustering with load balancing
  • Implement automatic scaling based on CPU usage
  • Create a deployment script with zero downtime
  • Add integration with monitoring tools

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Complete microservice with PM2 integration
import express from 'express';
import { createServer } from 'http';

interface ServiceMetrics {
  requests: number;
  errors: number;
  uptime: number;
  memory: number;
}

class MicroService {
  private app = express();
  private server = createServer(this.app);
  private metrics: ServiceMetrics = {
    requests: 0,
    errors: 0,
    uptime: Date.now(),
    memory: 0
  };

  constructor() {
    this.setupMiddleware();
    this.setupRoutes();
    this.setupGracefulShutdown();
  }

  // ๐Ÿ”ง Setup middleware
  private setupMiddleware(): void {
    this.app.use(express.json());
    
    // ๐Ÿ“Š Request counter
    this.app.use((req, res, next) => {
      this.metrics.requests++;
      console.log(`๐Ÿ“ ${new Date().toISOString()} - ${req.method} ${req.path} - PID: ${process.pid}`);
      next();
    });

    // โŒ Error counter
    this.app.use((error: any, req: any, res: any, next: any) => {
      this.metrics.errors++;
      console.error('โŒ Error:', error);
      res.status(500).json({ error: 'Internal server error' });
    });
  }

  // ๐Ÿ›’ Setup routes
  private setupRoutes(): void {
    // ๐Ÿ  Health check
    this.app.get('/health', (req, res) => {
      const memUsage = process.memoryUsage();
      this.metrics.memory = memUsage.heapUsed / 1024 / 1024;

      res.json({
        status: 'healthy ๐Ÿ’š',
        process: process.pid,
        uptime: (Date.now() - this.metrics.uptime) / 1000,
        memory: `${this.metrics.memory.toFixed(2)} MB`,
        metrics: this.metrics
      });
    });

    // ๐Ÿ“Š Metrics endpoint
    this.app.get('/metrics', (req, res) => {
      res.json({
        ...this.metrics,
        timestamp: new Date().toISOString(),
        process: {
          pid: process.pid,
          version: process.version,
          platform: process.platform
        }
      });
    });

    // ๐ŸŽฎ Demo endpoints
    this.app.get('/api/users', (req, res) => {
      res.json([
        { id: 1, name: 'Alice ๐Ÿ‘ฉโ€๐Ÿ’ป', role: 'developer' },
        { id: 2, name: 'Bob ๐Ÿ‘จโ€๐Ÿ”ง', role: 'devops' }
      ]);
    });

    this.app.post('/api/simulate-error', (req, res) => {
      throw new Error('๐Ÿ’ฅ Simulated error for testing!');
    });
  }

  // ๐Ÿ›‘ Graceful shutdown
  private setupGracefulShutdown(): void {
    const shutdown = (signal: string) => {
      console.log(`๐Ÿ›‘ Received ${signal}, shutting down gracefully...`);
      
      this.server.close(() => {
        console.log('โœ… HTTP server closed');
        console.log('๐Ÿ“Š Final metrics:', this.metrics);
        process.exit(0);
      });

      // ๐Ÿ•’ Force shutdown after 10 seconds
      setTimeout(() => {
        console.log('โš ๏ธ Force shutdown after timeout');
        process.exit(1);
      }, 10000);
    };

    process.on('SIGTERM', () => shutdown('SIGTERM'));
    process.on('SIGINT', () => shutdown('SIGINT'));
  }

  // ๐Ÿš€ Start server
  start(port: number = 3000): void {
    this.server.listen(port, () => {
      console.log(`๐Ÿš€ Microservice running on port ${port}`);
      console.log(`๐Ÿ“Š Process ID: ${process.pid}`);
      console.log(`โšก Environment: ${process.env.NODE_ENV || 'development'}`);
      
      // ๐Ÿ“ก Tell PM2 we're ready
      if (process.send) {
        process.send('ready');
      }
    });
  }
}

// ๐ŸŽฏ Start the microservice
const service = new MicroService();
service.start(parseInt(process.env.PORT || '3000'));

Ecosystem Configuration:

// ๐Ÿ”ง ecosystem.config.js
module.exports = {
  apps: [{
    name: 'typescript-microservice',
    script: './dist/microservice.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    },
    log_file: './logs/combined.log',
    out_file: './logs/out.log',
    error_file: './logs/error.log',
    time: true,
    max_memory_restart: '500M',
    kill_timeout: 5000,
    wait_ready: true,
    listen_timeout: 10000,
    restart_delay: 1000
  }]
};

Package.json Scripts:

{
  "scripts": {
    "build": "tsc",
    "start": "pm2 start ecosystem.config.js",
    "dev": "pm2 start ecosystem.config.js --env development --watch",
    "stop": "pm2 stop typescript-microservice",
    "restart": "pm2 restart typescript-microservice",
    "logs": "pm2 logs typescript-microservice",
    "monitor": "pm2 monit",
    "reload": "pm2 reload typescript-microservice",
    "delete": "pm2 delete typescript-microservice"
  }
}

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Set up PM2 with TypeScript applications confidently ๐Ÿ’ช
  • โœ… Configure ecosystem files for production deployments ๐Ÿ›ก๏ธ
  • โœ… Implement health monitoring and metrics collection ๐ŸŽฏ
  • โœ… Handle graceful shutdowns and process signals ๐Ÿ›
  • โœ… Build production-ready Node.js services! ๐Ÿš€

Remember: PM2 is your production companion, not your enemy! Itโ€™s here to help you build reliable, scalable applications. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered PM2 integration with TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the microservice exercise above
  2. ๐Ÿ—๏ธ Add PM2 to your existing TypeScript projects
  3. ๐Ÿ“š Move on to our next tutorial: Docker Integration with TypeScript
  4. ๐ŸŒŸ Share your PM2 deployment successes with others!

Remember: Every TypeScript expert was once a beginner. Keep coding, keep deploying, and most importantly, have fun! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ