PM2: A Journey from Basics to Advanced Node.js Process Management

PM2: A Journey from Basics to Advanced Node.js Process Management

·

8 min read

Welcome to your journey with PM2, the powerful process manager for Node.js applications. Whether you're a beginner just starting with Node.js or an experienced developer looking to optimize your application management, this guide will walk you through PM2 from the ground up. We'll focus primarily on managing a MERN (MongoDB, Express, React, Node.js) stack application, but the principles apply to any Node.js project.

Ok, before discussing other things. Let’s answer the main question that you may have in your mind and that is - What is PM2?

PM2 is a production-grade process manager for Node.js applications. It is designed to help manage, monitor, and keep applications running continuously. PM2 provides features such as automatic application restarts on failure, and comprehensive log management. This tool simplifies the deployment and management of Node.js applications by enabling efficient resource use and ensuring high availability, making it especially useful for managing web servers, APIs, and complex Node.js services in production environments.

Step 1: Installing PM2

Let's begin our journey by installing PM2. Open your terminal and run:

npm install -g pm2

This command installs PM2 globally on your system, making it available for all your projects.

Step 2: Running Your First Node.js File with PM2

Let's start with a simple Node.js file. Create a file named app.js with the following content:

console.log('Hello, PM2!');
setInterval(() => {
    console.log("'I'm still running!");
}, 5000);

Now, let's run this file with PM2:

pm2 start app.js

Congratulations! You've just started your first process with PM2.

Step 3: Running npm Scripts with PM2

As your projects grow more complex, you'll often use npm scripts defined in your package.json file to start your application. PM2 can manage these scripts just as easily as it manages individual files.

Let's use a Next.js application as an example:

  1. First, ensure your package.json has a start script. For a Next.js app, it typically looks like this:
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
  1. To run the production version of your Next.js app with PM2, use the following command:
pm2 start npm --name "next-app" -- start

This tells PM2 to run the npm start command, which in turn runs next start for a Next.js application.

  • pm2 start npm: Runs the npm command using PM2.

  • --name "next-app": Names the process for easy identification.

  • -- start: Passes start as an argument to npm, ensuring that npm runs the start script defined in your package.json.

  1. If you want to run the development version, you can use:
pm2 start npm --name "next-app-dev" -- run dev

This runs npm run dev, which starts the Next.js development server.

  1. You can apply the same principle to other npm scripts. For example, if you have a custom script called server:
pm2 start npm --name "custom-server" -- run server

This approach is not limited to Next.js. You can use it with any Node.js application that uses npm scripts, such as Express.js servers, React applications, or even custom Node.js scripts.

Step 4: Managing Multiple npm Scripts

For complex projects with multiple components, such as a MERN (MongoDB, Express, React, Node.js) stack application, you'll often need to manage several npm scripts simultaneously. PM2's ecosystem file feature is perfect for this scenario, allowing you to define and manage multiple processes in a single configuration file.

Create a file named ecosystem.config.js in your project root. This file will define all the processes PM2 should manage. Here's an expanded example:

module.exports = {
  apps: [
    {
      name: "next-app",
      script: "npm",
      args: "start",
      cwd: "./frontend",
      env: {
        NODE_ENV: "production",
        PORT: 3000
      },
      env_development: {
        NODE_ENV: "development",
        PORT: 3000
      }
    },
    {
      name: "express-server",
      script: "npm",
      args: "run server",
      cwd: "./backend",
      env: {
        NODE_ENV: "production",
        PORT: 5000
      },
      env_development: {
        NODE_ENV: "development",
        PORT: 5000
      }
    },
  ]
}

Let's break down the configuration options:

  • name: A unique identifier for each process.

  • script: The command to run. Use "npm" for npm scripts.

  • args: Arguments to pass to the script. For npm scripts, use "start", "run server", etc.

  • cwd: The working directory for the process. Useful when your frontend and backend are in separate directories.

  • env: Environment variables for production.

  • env_development: Environment variables for development.

To start all processes defined in your ecosystem file:

pm2 start ecosystem.config.js

To start processes in development mode:

pm2 start ecosystem.config.js --env development

This approach gives you the flexibility to manage complex applications with multiple components, all through PM2.

Step 5: Basic PM2 Commands

Now that we have a process running, let's learn some basic PM2 commands:

  1. List running processes:
pm2 list
  1. Stop a specific process::
pm2 stop next-app
  1. Restart a specific process:
pm2 restart next-app
  1. Delete a specific process from PM2:
pm2 delete next-app
  1. Stop all processes:
pm2 stop all
  1. Restart all processes:
 pm2 restart all
  1. Delete all processes:
pm2 delete all

Step 6: Monitoring Your Application

  1. PM2 provides a real-time monitoring tool. Try it out:
pm2 monit

This command opens an interface where you can see CPU usage, memory consumption, and logs for your running processes.

  1. Display process details:
pm2 show next-app

This command provides detailed information about a specific process, including its configuration, metadata, and recent logs.

Step 7: Reloading Without Downtime

To update your application with zero downtime:

pm2 reload ecosystem.config.js

This command gracefully reloads all processes defined in your ecosystem file, ensuring that your application remains available during updates.

Step 8: Managing Logs

Logs are crucial for understanding what's happening in your application. Here's how to view them:

  1. View logs:
pm2 logs
  1. View logs for a specific application:
pm2 logs next-app
  1. Clear log files:
pm2 flush
  1. To view only error logs:
pm2 logs --err

Step 9: Automatic Restart on Crashes

One of PM2's most powerful features is its ability to automatically restart applications that crash or stop unexpectedly. This feature ensures high availability and reliability for your Node.js applications.

Here's how it works:

  1. PM2 constantly monitors the processes it manages.

  2. If a process crashes or exits with an error code, PM2 will automatically restart it.

  3. This happens without any manual intervention, minimizing downtime.

Here are some key options that allow you to fine-tune the automatic restart behavior. These options can be added to your ecosystem.config.js file or specified when starting an application.

  1. max_restarts: Sets the maximum number of restarts within a time frame.

  2. min_uptime: Minimum uptime of the app to be considered started.

  3. max_memory_restart: Restarts the app if it exceeds a specified memory limit.

  4. restart_delay: Delay between automatic restarts (in milliseconds).

  5. autorestart: Enables or disables the automatic restart feature.

Let's update the ecosystem.config.js file to include these options:

module.exports = {
  apps: [{
    name: "my-app",
    script: "app.js",
    max_restarts: 10,
    min_uptime: "5s",
    max_memory_restart: "200M",
    restart_delay: 4000,
    autorestart: true
  }]
}

In this configuration:

  • The app will restart a maximum of 10 times.

  • The app must run for at least 5 seconds to be considered successfully started.

  • If the app uses more than 200MB of memory, it will be restarted.

  • There will be a 4-second delay between restarts.

  • Automatic restarts are enabled (this is the default, but we're explicitly setting it here).

You can also set these options when starting an app from the command line:

pm2 start app.js --max-restarts 10 --min-uptime 5000 --max-memory-restart 200M --restart-delay 4000

Example: Demonstrating Automatic Restart

Let's create a simple Node.js application that will crash periodically to demonstrate PM2's automatic restart feature.

  1. Create a new file named crash-test.js with the following content:

     let count = 0;
    
     setInterval(() => {
       console.log(`Running for ${count} seconds`);
       count++;
    
       if (count % 10 === 0) {
         console.log("Simulating a crash...");
         throw new Error("Application crashed!");
       }
     }, 1000);
    

    This script will run for 10 seconds and then simulate a crash by throwing an error.

  2. Create an ecosystem.config.js file with the following content:

     module.exports = {
       apps: [{
         name: "crash-test",
         script: "crash-test.js",
         max_restarts: 5,
         min_uptime: "3s",
         restart_delay: 2000
       }]
     }
    
  3. Start the application with PM2:

     pm2 start ecosystem.config.js
    
  4. Monitor the application:

     pm2 monit
    

    You'll see in the monitoring interface that every 10 seconds, the application crashes and PM2 automatically restarts it, with a 2-second delay between restarts.

  5. To view the logs and see the crash and restart events:

     pm2 logs crash-test
    

    You'll see output similar to this:

     0|crash-test  | Running for 8 seconds
     0|crash-test  | Running for 9 seconds
     0|crash-test  | Simulating a crash...
     0|crash-test  | Error: Application crashed!
     0|crash-test  |     at Timeout._onTimeout (/path/to/crash-test.js:8:11)
     0|crash-test  |     at listOnTimeout (node:internal/timers:559:17)
     0|crash-test  |     at processTimers (node:internal/timers:502:7)
     PM2        | App [crash-test:0] exited with code [1] via signal [SIGINT]
     PM2        | App [crash-test:0] starting in -fork mode-
     0|crash-test  | Running for 0 seconds
     0|crash-test  | Running for 1 seconds
    

This example demonstrates how PM2 automatically restarts your application when it crashes, ensuring continuous operation even in the face of unexpected errors. The configuration options we've set limit the number of restarts to 5 and introduce a 2-second delay between restarts, helping to prevent rapid restart loops in case of persistent issues.

Real-world Example: MERN Stack

Here's how you might set up an ecosystem file for a typical MERN stack application:

module.exports = {
  apps: [
    {
      name: "react-frontend",
      script: "npm",
      args: "start",
      cwd: "./client",
      env: {
        NODE_ENV: "production",
        PORT: 3000
      }
    },
    {
      name: "express-backend",
      script: "npm",
      args: "run server",
      cwd: "./server",
      env: {
        NODE_ENV: "production",
        PORT: 5000,
        MONGODB_URI: "mongodb://localhost:27017/myapp"
      }
    },
    {
      name: "mongodb",
      script: "mongod",
      args: "--port 27017"
    }
  ]
}

This configuration starts a React frontend, an Express.js backend, and a MongoDB instance, all managed by PM2.

By leveraging PM2's ecosystem files, you can efficiently manage complex, multi-component applications, ensuring all parts of your stack start together and are monitored effectively. This approach simplifies deployment, improves maintainability, and gives you fine-grained control over each component of your application.

Conclusion

Congratulations! You've completed your journey from PM2 basics to advanced usage. You've learned how to start simple scripts, manage complex applications, handle logs, use environment variables, and even tackle a full MERN stack. Remember, PM2 is a powerful tool with many more features to explore. As you continue your development journey, don't hesitate to dive into the PM2 documentation for more advanced usage and optimizations.

Happy coding, and may your applications always stay up and running with PM2!