Health checks (Terminus)

The terminus offers hooks to react on graceful shutdowns and supports you creating proper Kubernetes readiness / liveness checks for any HTTP application. The module @nestjs/terminus integrates the terminus library with the Nest ecosystem.

Getting started

To get started with @nestjs/terminus we need to install the required dependencies.

  1. $ npm install --save @nestjs/terminus @godaddy/terminus

Setting up a health check

A health check represents a summary of health indicators. A health indicator executes a check of a service, whether it is in a healthy state or not. A health check is positive, if all the assigned health indicators are up and running. Because a lot of applications will need similar health indicators, @nestjs/terminus provides a set of predefined health indicators, such as:

  • DNSHealthIndicator
  • TypeOrmHealthIndicator
  • MongooseHealthIndicator
  • MicroserviceHealthIndicator
  • MemoryHealthIndicator
  • DiskHealthIndicator

DNS Health Check

The first step to get started with our first health check, is to setup a service which will associate health indicators to an endpoint.

  1. @@filename(terminus-options.service)
  2. import {
  3. TerminusEndpoint,
  4. TerminusOptionsFactory,
  5. DNSHealthIndicator,
  6. TerminusModuleOptions
  7. } from '@nestjs/terminus';
  8. import { Injectable } from '@nestjs/common';
  9. @Injectable()
  10. export class TerminusOptionsService implements TerminusOptionsFactory {
  11. constructor(
  12. private readonly dns: DNSHealthIndicator,
  13. ) {}
  14. createTerminusOptions(): TerminusModuleOptions {
  15. const healthEndpoint: TerminusEndpoint = {
  16. url: '/health',
  17. healthIndicators: [
  18. async () => this.dns.pingCheck('google', 'https://google.com'),
  19. ],
  20. };
  21. return {
  22. endpoints: [healthEndpoint],
  23. };
  24. }
  25. }
  26. @@switch
  27. import { Injectable, Dependencies } from '@nestjs/common';
  28. import { DNSHealthIndicator } from '@nestjs/terminus';
  29. @Injectable()
  30. @Dependencies(DNSHealthIndicator)
  31. export class TerminusOptionsService {
  32. constructor(dns) {
  33. this.dns = dns;
  34. }
  35. createTerminusOptions() {
  36. const healthEndpoint = {
  37. url: '/health',
  38. healthIndicators: [
  39. async () => this.dns.pingCheck('google', 'https://google.com'),
  40. ],
  41. };
  42. return {
  43. endpoints: [healthEndpoint],
  44. };
  45. }
  46. }

Once we have set up our TerminusOptionsService, we can import the TerminusModule into the root ApplicationModule. The TerminusOptionsService will provide the settings, which in turn will be used by the TerminusModule.

  1. @@filename(app.module)
  2. import { Module } from '@nestjs/common';
  3. import { TerminusModule } from '@nestjs/terminus';
  4. import { TerminusOptionsService } from './terminus-options.service';
  5. @Module({
  6. imports: [
  7. TerminusModule.forRootAsync({
  8. useClass: TerminusOptionsService,
  9. }),
  10. ],
  11. })
  12. export class ApplicationModule { }

info Hint If done correctly, Nest will expose the defined health check(s), which are reachable through a GET request to the defined route. For example curl -X GET 'http://localhost:3000/health'

Custom health indicator

In some cases, the predefined health indicators provided by @nestjs/terminus do not cover all of your health check requirements. In this case you can set up a custom health indicator according to your needs.

Let’s get started by creating a service which will represent our custom health indicator. To get a basic understanding how a health indicator is structured, we will create an example DogHealthIndicator. This health indicator should have the state “up”, if every Dog object has the type goodboy, otherwise it will throw an error, which then the health indicator will be seen as “down”.

  1. @@filename(dog.health)
  2. import { Injectable } from '@nestjs/common';
  3. import { HealthCheckError } from '@godaddy/terminus';
  4. import { HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus';
  5. export interface Dog {
  6. name: string;
  7. type: string;
  8. }
  9. @Injectable()
  10. export class DogHealthIndicator extends HealthIndicator {
  11. private readonly dogs: Dog[] = [
  12. { name: 'Fido', type: 'goodboy' },
  13. { name: 'Rex', type: 'badboy' },
  14. ];
  15. async isHealthy(key: string): Promise<HealthIndicatorResult> {
  16. const badboys = this.dogs.filter(dog => dog.type === 'badboy');
  17. const isHealthy = badboys.length === 0;
  18. const result = this.getStatus(key, isHealthy, { badboys: badboys.length });
  19. if (isHealthy) {
  20. return result;
  21. }
  22. throw new HealthCheckError('Dogcheck failed', result);
  23. }
  24. }
  25. @@switch
  26. import { Injectable } from '@nestjs/common';
  27. import { HealthCheckError } from '@godaddy/terminus';
  28. @Injectable()
  29. export class DogHealthIndicator extends HealthIndicator {
  30. dogs = [
  31. { name: 'Fido', type: 'goodboy' },
  32. { name: 'Rex', type: 'badboy' },
  33. ];
  34. async isHealthy(key) {
  35. const badboys = this.dogs.filter(dog => dog.type === 'badboy');
  36. const isHealthy = badboys.length === 0;
  37. const result = this.getStatus(key, isHealthy, { badboys: badboys.length });
  38. if (isHealthy) {
  39. return result;
  40. }
  41. throw new HealthCheckError('Dogcheck failed', result);
  42. }
  43. }

The next thing we need to do is registering the health indicator as a provider.

  1. @@filename(app.module)
  2. import { Module } from '@nestjs/common';
  3. import { TerminusModule } from '@nestjs/terminus';
  4. import { TerminusOptionsService } from './terminus-options.service';
  5. import { DogHealthIndicator } from './dog.health';
  6. @Module({
  7. imports: [
  8. TerminusModule.forRootAsync({
  9. imports: [ApplicationModule],
  10. useClass: TerminusOptionsService,
  11. }),
  12. ],
  13. providers: [DogHealthIndicator],
  14. exports: [DogHealthIndicator],
  15. })
  16. export class ApplicationModule { }

info Hint In a real world application the DogHealthIndicator should be provided in a separate module, for example DogsModule, which then will be imported by the ApplicationModule. But keep in mind to add the DogHealthIndicator to the exports array of the DogModule and add the DogModule in imports array of the TerminusModule.forRootAsync() parameter object.

The last required thing to do is to add the now available health indicator in the required health check endpoint. For that we go back to our TerminusOptionsService and implement it to the /health endpoint.

  1. @@filename(terminus-options.service)
  2. import {
  3. TerminusEndpoint,
  4. TerminusOptionsFactory,
  5. DNSHealthIndicator,
  6. TerminusModuleOptions
  7. } from '@nestjs/terminus';
  8. import { Injectable } from '@nestjs/common';
  9. import { DogHealthIndicator } from './dog.health';
  10. @Injectable()
  11. export class TerminusOptionsService implements TerminusOptionsFactory {
  12. constructor(
  13. private readonly dogHealthIndicator: DogHealthIndicator
  14. ) {}
  15. createTerminusOptions(): TerminusModuleOptions {
  16. const healthEndpoint: TerminusEndpoint = {
  17. url: '/health',
  18. healthIndicators: [
  19. async () => this.dogHealthIndicator.isHealthy('dog'),
  20. ],
  21. };
  22. return {
  23. endpoints: [healthEndpoint],
  24. };
  25. }
  26. }
  27. @@switch
  28. import { DogHealthIndicator } from './dog.health';
  29. import { Injectable, Dependencies } from '@nestjs/common';
  30. @Injectable()
  31. @Dependencies(DogHealthIndicator)
  32. export class TerminusOptionsService {
  33. constructor(dogHealthIndicator) {
  34. this.dogHealthIndicator = dogHealthIndicator;
  35. }
  36. createTerminusOptions() {
  37. const healthEndpoint = {
  38. url: '/health',
  39. healthIndicators: [
  40. async () => this.dogHealthIndicator.isHealthy('dog'),
  41. ],
  42. };
  43. return {
  44. endpoints: [healthEndpoint],
  45. };
  46. }
  47. }

If everything has been done correctly, the /health endpoint should respond with a 503 response code and the following data.

  1. {
  2. "status": "error",
  3. "error": {
  4. "dog": {
  5. "status": "down",
  6. "badboys": 1
  7. }
  8. }
  9. }

You can view working examples in the @nestjs/terminus repository.

Custom Logger

The Terminus module automatically logs every error during a health check request. By default, it will use the globally defined Nest logger.You can read more about the global logger in the Logger chapter.In some cases, you want to handle the logs of Terminus explicitly. In this case, the TerminusModule.forRoot[Async] function offers an optionfor a custom logger.

  1. TerminusModule.forRootAsync({
  2. logger: (message: string, error: Error) => console.error(message, error),
  3. endpoints: [
  4. ...
  5. ]
  6. })

The logger can also be disabled by setting the logger option to null.

  1. TerminusModule.forRootAsync({
  2. logger: null,
  3. endpoints: [
  4. ...
  5. ]
  6. })