DEV Community

Tyler Steck
Tyler Steck

Posted on • Edited on

Creating a Health Check endpoint and verify using SuperTest

To help improve the stability of a hosted servers a health check route can be used to ensure services are working as expected. Here is an example of an Express.js application in TypeScript that includes a route for a health check that verifies the DB connection to PostgreSQL:

First, install the necessary dependencies:

npm install express pg
npm install --save-dev @types/express @types/pg
Enter fullscreen mode Exit fullscreen mode

Next, create a new file app.tsand paste the following code:

import express from 'express';
import { Pool } from 'pg';

const app = express();

// Create a new pool with the PostgreSQL database connection details
const pool = new Pool({
  user: 'db_user',
  host: 'localhost',
  database: 'mydb',
  password: 'mysecretpassword',
  port: 5432,
});

// Define a route for the health check
app.get('/healthCheck', async (req, res) => {
  try {
    // Use the connection pool to acquire a connection
    const client = await pool.connect();
    // Release the connection back to the pool
    client.release();
    // Send a 200 OK status response
    res.sendStatus(200);
  } catch (err) {
    // Send a 500 Internal Server Error status response if there was an error
    res.sendStatus(500);
  }
});

// Start the Express.js server
app.listen(3000, () => {
  console.log('Server running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

In the above code, we first import the necessary dependencies (express and pg). We then create a new instance of the Express.js app using express(). We also create a new connection pool to the PostgreSQL database using the Pool class from the pg package.

We then define a route for the health check at the /healthCheck endpoint using app.get(). Inside the route handler function, we use the connection pool to acquire a connection to the database using pool.connect(). If the connection is successful, we release the connection back to the pool and send a 200 OK status response using res.sendStatus(200). If there was an error, we catch the error and send a 500 Internal Server Error status response using res.sendStatus(500).

We start the Express.js server by calling app.listen() and passing in the port number we want to listen on (in this case, port 3000).

Testing using SuperTest

Example of how you could write integration tests to verify that the code works:

First, install the necessary dependencies:

npm install --save-dev jest supertest ts-jest @types/jest @types/supertest
Enter fullscreen mode Exit fullscreen mode

Next, create a new file app.test.ts in the same directory as app.ts and paste the following code:

import request from 'supertest';
import app from './app';

describe('healthCheck route', () => {
  it('should return 200 OK if the database connection is successful', async () => {
    // Make a GET request to the /healthCheck endpoint
    const res = await request(app).get('/healthCheck');
    // Expect a 200 OK status response
    expect(res.status).toBe(200);
  });
});
Enter fullscreen mode Exit fullscreen mode

In the above code, we first import the necessary dependencies (supertest and app from ./app). We then define a describe block for the healthCheck route and an it block for the happy path scenario where the database connection is successful.

Inside the it block, we use request(app) to make a GET request to the /healthCheck endpoint. We then use expect(res.status).toBe(200) to assert that the response status is 200 OK.

Now, we need to update our package.json file to run the integration tests. Add the following to the "scripts" section:

"test": "jest"
Enter fullscreen mode Exit fullscreen mode

Finally, run the integration tests by executing the following command:

npm test
Enter fullscreen mode Exit fullscreen mode

This should run the integration tests and output something like the following:

 PASS  ./app.test.ts
  healthCheck route
    ✓ should return 200 OK if the database connection is successful (34 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Enter fullscreen mode Exit fullscreen mode

This indicates that the integration tests passed and the /healthCheck route is functioning correctly.

If you want to test the error case where the database connection fails, you can add another it block to the describe block in app.test.ts. Here's an example of how you could write the test:

it('should return 500 Internal Server Error if the database connection fails', async () => {
  // Force the pool to return an error when connecting to the database
  jest.spyOn(app.locals.pool, 'connect').mockImplementation(() => {
    throw new Error('Connection failed');
  });
  // Make a GET request to the /healthCheck endpoint
  const res = await request(app).get('/healthCheck');
  // Expect a 500 Internal Server Error status response
  expect(res.status).toBe(500);
});
Enter fullscreen mode Exit fullscreen mode

In the above code, we use Jest's spyOn function to mock the pool.connect() method and force it to throw an error when connecting to the database. We then make a GET request to the /healthCheck endpoint and use expect(res.status).toBe(500) to assert that the response status is 500 Internal Server Error.

When you run the integration tests again, you should see that both tests pass:

 PASS  ./app.test.ts
  healthCheck route
    ✓ should return 200 OK if the database connection is successful (32 ms)
    ✓ should return 500 Internal Server Error if the database connection fails (12 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Enter fullscreen mode Exit fullscreen mode

Great! That means our integration tests are passing for both the happy and error path scenarios. Now, we can be confident that our /healthCheck route is functioning correctly and verifying the database connection before responding with the appropriate status code.

Top comments (0)