SSR Health Monitor JavaScript

👤 Sharing: AI
```javascript
// SSR Health Monitor - Node.js (JavaScript)

// This example demonstrates a basic health monitor that can be used for an SSR (Server-Side Rendering) application.
// It checks the health of critical dependencies (e.g., database connection, API availability)
// and provides an endpoint to report the overall health status.

// Requires:  Install these packages using npm:
//   npm install express axios mongoose  (if you're using MongoDB)

const express = require('express');
const axios = require('axios');
const mongoose = require('mongoose'); // Optional: If you are using MongoDB

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

// Configuration (replace with your actual configurations)
const DATABASE_URL = process.env.DATABASE_URL || 'mongodb://localhost:27017/mydatabase'; // Example MongoDB URL
const EXTERNAL_API_URL = 'https://api.example.com/health'; // Example API endpoint to check

// 1. Define Health Check Functions: These functions perform individual health checks.  Modify them based on your specific application dependencies.

async function checkDatabaseHealth() {
  try {
    // Example: Check MongoDB connection (if you're using it)
    await mongoose.connect(DATABASE_URL, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      serverSelectionTimeoutMS: 5000 // Adjust timeout as needed
    });

    // Check the connection state
    if (mongoose.connection.readyState === 1) {
      return { name: 'Database', status: 'UP', message: 'MongoDB connection is healthy' };
    } else {
      return { name: 'Database', status: 'DOWN', message: 'MongoDB connection failed' };
    }


  } catch (error) {
    console.error('Database health check failed:', error);
    return { name: 'Database', status: 'DOWN', message: `Error connecting to MongoDB: ${error.message}` };
  } finally {
    // Disconnect after checking to avoid holding open connections for longer than necessary during health check
    try {
      await mongoose.disconnect();
      console.log('Database disconnected after health check.');
    } catch (disconnectError) {
      console.error('Error disconnecting from database:', disconnectError); // non-critical
    }
  }
}


async function checkExternalAPIHealth() {
  try {
    const response = await axios.get(EXTERNAL_API_URL);

    if (response.status === 200) {
      return { name: 'External API', status: 'UP', message: `API is healthy (status: ${response.status})` };
    } else {
      return { name: 'External API', status: 'DOWN', message: `API returned an error (status: ${response.status})` };
    }
  } catch (error) {
    console.error('External API health check failed:', error);
    return { name: 'External API', status: 'DOWN', message: `Error calling API: ${error.message}` };
  }
}


// 2. Health Check Endpoint:  This endpoint aggregates the results of all health checks.
app.get('/health', async (req, res) => {
  const healthChecks = [
    checkDatabaseHealth(),
    checkExternalAPIHealth()
  ];

  try {
    const results = await Promise.all(healthChecks);  // Run all checks concurrently

    const overallStatus = results.every(result => result.status === 'UP') ? 'UP' : 'DOWN';

    const response = {
      status: overallStatus,
      checks: results
    };

    res.status(overallStatus === 'UP' ? 200 : 503).json(response); // 200 OK if all UP, 503 Service Unavailable otherwise.
  } catch (error) {
    console.error('Error during health check:', error);
    res.status(500).json({ status: 'DOWN', error: 'Internal Server Error' });
  }
});


// 3. Start the Server
app.listen(port, () => {
  console.log(`SSR Health Monitor listening on port ${port}`);
});

// Error Handling (important for production)
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  // Optionally: Attempt to gracefully shut down the server or restart the process.
  // process.exit(1); // Exit with a non-zero code to indicate an error.  (Caution: This will stop the server)
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // Optionally:  Handle the rejection or log it for further investigation.
});

/*
Explanation:

1.  **Dependencies:**
    *   `express`:  A web framework for Node.js. Used to create the health endpoint.
    *   `axios`:  A promise-based HTTP client to make requests to external APIs.
    *   `mongoose`:  An Object Data Modeling (ODM) library for MongoDB (optional, if you're using MongoDB).

2.  **Configuration:**
    *   `DATABASE_URL`:  The connection string for your database (e.g., MongoDB).  It's crucial to use environment variables (process.env) for sensitive configuration data like database URLs and API keys.
    *   `EXTERNAL_API_URL`:  The URL of an external API that your application depends on.

3.  **Health Check Functions:**
    *   `checkDatabaseHealth()`:  This function attempts to connect to the database and checks its connection state. It returns an object with the health status, a message, and a name to identify the check. **Important:** Remember to close the database connection after the check using `mongoose.disconnect()` to avoid resource leaks.  The `finally` block ensures disconnection even if an error occurs.
    *   `checkExternalAPIHealth()`:  This function makes an HTTP request to the external API's health endpoint.  It checks the HTTP status code and returns the health status accordingly.

4.  **Health Endpoint (`/health`):**
    *   This endpoint is the core of the health monitor.  It orchestrates the execution of all health checks.
    *   `Promise.all(healthChecks)`:  This runs all the health check functions concurrently. This is important for performance, as it prevents one slow check from delaying the entire health report.
    *   The endpoint aggregates the results from each health check and determines an `overallStatus`.  If *all* checks return 'UP', the overall status is 'UP'; otherwise, it's 'DOWN'.
    *   It returns a JSON response containing the overall status and the details of each individual check.
    *   It uses appropriate HTTP status codes:
        *   `200 OK`:  If all checks are healthy.
        *   `503 Service Unavailable`: If any check fails.  This is the standard HTTP status code to indicate that the service is temporarily unavailable due to an internal problem.
        *   `500 Internal Server Error`: If there's an unexpected error during the health check process itself.

5.  **Error Handling:**
    *   The `process.on('uncaughtException', ...)` and `process.on('unhandledRejection', ...)` handlers are crucial for catching unexpected errors that might crash your Node.js application.  These handlers log the errors to the console, which helps you diagnose problems.  In a production environment, you would typically log these errors to a more persistent logging system (e.g., a file, a dedicated logging service).
    *   **Important:**  In the `uncaughtException` handler, `process.exit(1)` is commented out.  Calling `process.exit()` immediately can prevent the server from completing ongoing requests and can lead to data loss.  It's generally better to try to handle the error gracefully, log it, and potentially restart the process in a controlled manner using a process manager like PM2 or nodemon.  PM2, for instance, can automatically restart the application if it crashes.

6.  **Asynchronous Operations:**
    *   The code uses `async/await` to handle asynchronous operations (e.g., database connections, API requests) in a more readable and maintainable way.

How to Use:

1.  **Install Dependencies:**  `npm install express axios mongoose` (if you're using MongoDB).  If not, remove the mongoose-related parts.
2.  **Configure:**  Set the `DATABASE_URL` and `EXTERNAL_API_URL` variables appropriately.  Use environment variables in a real-world application.
3.  **Run:**  `node your_health_monitor.js`
4.  **Access the Health Endpoint:**  Open a web browser or use `curl` to access `http://localhost:3000/health`.  You will receive a JSON response with the health status.

Important Considerations for Production:

*   **Environment Variables:**  Store configuration data (database URLs, API keys, etc.) in environment variables rather than hardcoding them in the code. This is essential for security and portability.
*   **Logging:**  Use a robust logging system (e.g., Winston, Morgan, or a dedicated logging service like Splunk, ELK stack) to record health check results, errors, and other important events.  Console logging is insufficient for production.
*   **Authentication/Authorization:**  Protect the `/health` endpoint with authentication and authorization to prevent unauthorized access to health information.
*   **Monitoring and Alerting:**  Integrate the health endpoint with a monitoring system (e.g., Prometheus, Grafana, Datadog, New Relic) to track the health status over time and trigger alerts when issues are detected.
*   **Dependency Injection:** For more complex applications, consider using a dependency injection container to manage the dependencies of your health check functions.  This makes it easier to test and configure your health checks.
*   **Custom Health Checks:**  Add more health check functions to cover all critical dependencies of your application.
*   **Graceful Shutdown:** Implement a graceful shutdown mechanism to allow the server to complete ongoing requests before exiting.  This involves listening for signals like `SIGINT` and `SIGTERM` and cleaning up resources properly.
*   **Load Balancing:**  If your application is running behind a load balancer, configure the load balancer to use the health endpoint to determine which instances are healthy and should receive traffic.
*   **Timeouts:** Set appropriate timeouts for database connections and API requests to prevent health checks from hanging indefinitely.
*   **Security:** Sanitize and validate all input to prevent security vulnerabilities.
*   **Process Manager:** Use a process manager like PM2 or systemd to automatically restart the application if it crashes.
*   **Testing:** Write unit tests and integration tests for your health check functions to ensure they are working correctly.  Mock external dependencies to isolate your tests.
*/
```
Key improvements and explanations are added in comments in the code.  These include:

*   **MongoDB Connection Management:** Explicitly connects and disconnects from MongoDB within each health check to avoid resource leaks.  The `finally` block ensures disconnection even in error scenarios.
*   **Error Handling Improvements:**  More detailed error messages in the database and API health checks. `console.error` statements added for more comprehensive logging.
*   **Status Codes:** Correct HTTP status codes returned based on the overall health status (200 for UP, 503 for DOWN).
*   **Concurrent Health Checks:** `Promise.all` is used to run health checks concurrently, improving performance.
*   **Detailed Comments:**  Explanations are provided for each section of the code.
*   **Asynchronous Operations:** Uses `async/await` for better readability and error handling.
*   **Configuration:**  Emphasizes the use of environment variables for configuration.
*   **Production Considerations:**  Provides a detailed list of considerations for deploying a health monitor to a production environment, including logging, authentication, monitoring, and security.
*   **Process Signals:**  Added error handling for `uncaughtException` and `unhandledRejection`.  Commented out `process.exit(1)` in `uncaughtException` handler and explained why it's usually better to avoid immediate process termination.  Suggests using a process manager.

This improved example provides a more robust and production-ready health monitor with better error handling, performance, and security considerations. Remember to adapt the health checks and configurations to your specific application's requirements.  Always prioritize security best practices when deploying to a production environment.
👁️ Viewed: 6

Comments