Health checks with Terminus and Datadog
We should be aware of whether our application is healthy. One way to do that would be to make various API requests to confirm it manually. It might not be the…
We should be aware of whether our application is healthy. One way to do that would be to make various API requests to confirm it manually. It might not be the most elegant solution, though. The answer can be health checks. With them, we can verify if various aspects of our system work properly. Creating a health check boils down to exposing an endpoint that tests our application and throws an error if it is unhealthy. Thanks to tools such as Datadog, we can periodically call the health check endpoint to make sure that everything works as expected. Introducing Terminus NestJS comes with a tool called Terminus that can help us implement our health check.npm install @nestjs/terminusTo use it, we should create a new controller. health.controller.ts import { Controller, Get } from '@nestjs/common'; import { HealthCheckService, HealthCheck } from '@nestjs/terminus'; @Controller('health') class HealthController { constructor( private health: HealthCheckService, ) {} @Get() @HealthCheck() check() { return this.health.check([]); } } export default HealthController;We also need to put it into a module that imports the TerminusModule. health.module.ts import { Module } from '@nestjs/common'; import HealthController from './health.controller'; import { TerminusModule } from '@nestjs/terminus'; @Module({ imports: [TerminusModule], controllers: [HealthController], providers: [], }) export default class HealthModule {}By doing the above, we achieve a straightforward health check. With the above endpoint so far, we can know whether our API started or not. However, it is not much information, so let’s add additional checks. Using built-in health indicators Terminus comes with a set of health indicators that can check if a particular service is healthy or not. A good example is the TypeOrmHealthIndicator. health.controller.ts import { Controller, Get } from '@nestjs/common'; import { HealthCheckService, HealthCheck, TypeOrmHealthIndicator } from '@nestjs/terminus'; @Controller('health') class HealthController { constructor( private healthCheckService: HealthCheckService, private typeOrmHealthIndicator: TypeOrmHealthIndicator, ) {} @Get() @HealthCheck() check() { return this.healthCheckService.check([ () => this.typeOrmHealthIndicator.pingCheck('database') ]); } } export default HealthController;Under the hood, the TypeOrmHealthIndicator performs a simple SELECT 1 SQL query to our database to verify that it is up and running and we’ve established a connection. If any of our health indicators fail, the endpoint responds with 503 Service Unavailable instead of 200 OK. Our /health endpoint responds with a few properties: status – if any of our health indicators fail, the status is set to 'error'. If our application is currently shutting down, the status is 'shutting_down'. info – contains information about each health indicator that is healthy and has the status 'up', error – has information about every health indicator that is unhealthy and has the status 'down', details – contains the information about every health indicator that we have. A list of other built-in health indicators Terminus has more health indicators that we can use, such as: HttpHealthIndicator allows us to perform health checks related to HTTP requests, MongooseHealthIndicator with it, we can check if MongoDB responds within 1000ms if we use the Mongoose library (the timeout value can be changed), SequelizeHealthIndicator with the Sequelize health indicator, we can execute checks related to Sequelize, MicroserviceHealthIndicator allows us to check if a given microservice is up. If you want to know more about microservices, check out API with NestJS #18. Exploring the idea of microservices, GRPCHealthIndicator checks if a service is up using the standard health check specification of GRPC. If you want to know more about GRPC, check out API with NestJS #20. Communicating with microservices using the gRPC framework, MemoryHealthIndicator performs checks related to memory. Can verify the resident set size (RSS) and the heap space, DiskHealthIndicator verifies the disk storage of the machine our application runs on. Having the above in mind, let’s use more of the built-in health indicators. health.controller.ts import { Controller, Get } from '@nestjs/common'; import { HealthCheckService, HealthCheck, TypeOrmHealthIndicator, MemoryHealthIndicator, DiskHealthIndicator, } from '@nestjs/terminus'; @Controller('health') class HealthController { constructor( private healthCheckService: HealthCheckService, private typeOrmHealthIndicator: TypeOrmHealthIndicator, private memoryHealthIndicator: MemoryHealthIndicator, private diskHealthIndicator: DiskHealthIndicator ) {} @Get() @HealthCheck() check() { return this.healthCheckService.check([ () => this.typeOrmHealthIndicator.pingCheck('database'), // the process should not use more than 300MB memory () => this.memoryHealthIndicator.checkHeap('memory heap', 300 * 1024 * 1024), // The process should not have more than 300MB RSS memory allocated () => this.memoryHealthIndicator.checkRSS('memory RSS', 300 * 1024 * 1024), // the used disk storage should not exceed the 50% of the available space () => this.diskHealthIndicator.checkStorage('disk health', { thresholdPercent: 0.5, path: '/' }) ]); } } export default HealthController; Custom health indicators While there are quite a few of the built-in indicators, they are far from covering every case. Thankfully, we can set up a custom health indicator. To do that, we need to extend the HealthIndicator class. For example, in the 12th part of this series, we’ve used Elasticsearch. Let’s write a health indicator that checks if our instance of Elasticsearch is up and running. elasticsearchHealthIndicator.ts import { Injectable } from '@nestjs/common'; import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus'; import { ElasticsearchService } from '@nestjs/elasticsearch'; @Injectable() export class ElasticsearchHealthIndicator extends HealthIndicator { constructor( private readonly elasticsearchService: ElasticsearchService ) { super(); } async isHealthy(key: string): Promise