Creating a Health Check endpoint and verify using SuperTest
Published: 2023-02-27 17:42:24

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:

```terminal
npm install express pg
npm install --save-dev @types/express @types/pg
```

Next, create a new file `app.ts `and paste the following code:

```typescript
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');
});
```

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:

```terminal
npm install --save-dev jest supertest ts-jest @types/jest @types/supertest
```

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

```typescript
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);
  });
});
```

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:

```json
"test": "jest"
```

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

```terminal
npm test
```

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

```terminal
 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
```

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:

```typescript
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);
});
```

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:

```terminal
 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
```

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.