Careeroza
Health Checks with Terminus
Liveness and Readiness
Load balancers, Docker and Kubernetes call a health endpoint to decide whether to send traffic to an instance or restart it.
Example
npm i @nestjs/terminusExample output
added 2 packages in 2s
src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, MemoryHealthIndicator, PrismaHealthIndicator } from '@nestjs/terminus';
import { Public } from '../auth/public.decorator';
import { PrismaService } from '../prisma/prisma.service';
@Public()
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly prismaHealth: PrismaHealthIndicator,
private readonly memory: MemoryHealthIndicator,
private readonly prisma: PrismaService,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.prismaHealth.pingCheck('database', this.prisma),
() => this.memory.checkHeap('memory_heap', 400 * 1024 * 1024),
]);
}
}
// health.module.ts: imports: [TerminusModule], controllers: [HealthController]Example
curl -s localhost:3000/api/health
# database stopped:
curl -si localhost:3000/api/healthExample output
{"status":"ok","info":{"database":{"status":"up"},"memory_heap":{"status":"up"}},"error":{},"details":{"database":{"status":"up"},"memory_heap":{"status":"up"}}}
HTTP/1.1 503 Service Unavailable
{"status":"error","info":{"memory_heap":{"status":"up"}},"error":{"database":{"status":"down","message":"Can't reach database server at db:5432"}},"details":{...}}How the Result is Combined
Example
Terminus runs every indicator, collects up/down results and answers 200 only if all are up:
type Indicator = () => Promise<Record<string, { status: 'up' | 'down'; message?: string }>>;
async function check(indicators: Indicator[]) {
const results = await Promise.all(indicators.map((i) => i().catch((e: Error) => ({ unknown: { status: 'down' as const, message: e.message } }))));
const details = Object.assign({}, ...results) as Record<string, { status: string; message?: string }>;
const info = Object.fromEntries(Object.entries(details).filter(([, v]) => v.status === 'up'));
const error = Object.fromEntries(Object.entries(details).filter(([, v]) => v.status !== 'up'));
const ok = Object.keys(error).length === 0;
return { httpStatus: ok ? 200 : 503, body: { status: ok ? 'ok' : 'error', info, error } };
}
const db = (up: boolean): Indicator => async () =>
up ? { database: { status: 'up' } } : { database: { status: 'down', message: "Can't reach database server" } };
const redis: Indicator = async () => ({ redis: { status: 'up' } });
async function main() {
console.log(JSON.stringify(await check([db(true), redis])));
console.log(JSON.stringify(await check([db(false), redis])));
}
main();Output
{"httpStatus":200,"body":{"status":"ok","info":{"database":{"status":"up"},"redis":{"status":"up"}},"error":{}}}
{"httpStatus":503,"body":{"status":"error","info":{"redis":{"status":"up"}},"error":{"database":{"status":"down","message":"Can't reach database server"}}}}