feat: add api liveness and readiness checks
This commit is contained in:
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
import { ConfigurationModule } from '../../../libs/configuration/src';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { HealthModule } from './health/health.module';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigurationModule],
|
||||
imports: [ConfigurationModule, HealthModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
29
backend/apps/api/src/health/health.controller.spec.ts
Normal file
29
backend/apps/api/src/health/health.controller.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { HealthController } from './health.controller';
|
||||
import { ReadinessService } from './readiness.service';
|
||||
|
||||
describe('HealthController', () => {
|
||||
it('returns liveness without dependency checks', async () => {
|
||||
const readiness = { check: jest.fn() };
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [HealthController],
|
||||
providers: [{ provide: ReadinessService, useValue: readiness }],
|
||||
}).compile();
|
||||
|
||||
expect(moduleRef.get(HealthController).live()).toEqual({ status: 'ok' });
|
||||
expect(readiness.check).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('delegates readiness to dependency checks', async () => {
|
||||
const readiness = { check: jest.fn().mockResolvedValue({ status: 'ok' }) };
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [HealthController],
|
||||
providers: [{ provide: ReadinessService, useValue: readiness }],
|
||||
}).compile();
|
||||
|
||||
await expect(moduleRef.get(HealthController).ready()).resolves.toEqual({
|
||||
status: 'ok',
|
||||
});
|
||||
expect(readiness.check).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
17
backend/apps/api/src/health/health.controller.ts
Normal file
17
backend/apps/api/src/health/health.controller.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ReadinessService } from './readiness.service';
|
||||
|
||||
@Controller()
|
||||
export class HealthController {
|
||||
constructor(private readonly readiness: ReadinessService) {}
|
||||
|
||||
@Get('health/live')
|
||||
live(): { status: 'ok' } {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
@Get('health/ready')
|
||||
ready(): Promise<{ status: 'ok' }> {
|
||||
return this.readiness.check();
|
||||
}
|
||||
}
|
||||
14
backend/apps/api/src/health/health.module.ts
Normal file
14
backend/apps/api/src/health/health.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import {
|
||||
PostgresModule,
|
||||
RedisModule,
|
||||
} from '../../../../libs/infrastructure/src';
|
||||
import { HealthController } from './health.controller';
|
||||
import { ReadinessService } from './readiness.service';
|
||||
|
||||
@Module({
|
||||
imports: [PostgresModule, RedisModule],
|
||||
controllers: [HealthController],
|
||||
providers: [ReadinessService],
|
||||
})
|
||||
export class HealthModule {}
|
||||
22
backend/apps/api/src/health/readiness.service.ts
Normal file
22
backend/apps/api/src/health/readiness.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { Pool } from 'pg';
|
||||
import type Redis from 'ioredis';
|
||||
import {
|
||||
POSTGRES_POOL,
|
||||
REDIS_CLIENT,
|
||||
} from '../../../../libs/infrastructure/src';
|
||||
|
||||
@Injectable()
|
||||
export class ReadinessService {
|
||||
constructor(
|
||||
@Inject(POSTGRES_POOL) private readonly postgresPool: Pool,
|
||||
@Inject(REDIS_CLIENT) private readonly redis: Redis,
|
||||
) {}
|
||||
|
||||
async check(): Promise<{ status: 'ok' }> {
|
||||
await this.postgresPool.query('SELECT 1');
|
||||
const pong = await this.redis.ping();
|
||||
if (pong !== 'PONG') throw new Error('Redis ping failed');
|
||||
return { status: 'ok' as const };
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ApiModule } from './api.module';
|
||||
|
||||
export async function bootstrapApi(): Promise<void> {
|
||||
const app = await NestFactory.create(ApiModule);
|
||||
app.enableShutdownHooks();
|
||||
app.setGlobalPrefix('api/v1');
|
||||
app.setGlobalPrefix('api/v1', {
|
||||
exclude: [
|
||||
{ path: 'health/live', method: RequestMethod.GET },
|
||||
{ path: 'health/ready', method: RequestMethod.GET },
|
||||
],
|
||||
});
|
||||
await app.listen(3000, '0.0.0.0');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigurationModule } from '../../../libs/configuration/src';
|
||||
import { PostgresModule, RedisModule } from '../../../libs/infrastructure/src';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigurationModule],
|
||||
imports: [ConfigurationModule, PostgresModule, RedisModule],
|
||||
})
|
||||
export class WorkerModule {}
|
||||
|
||||
Reference in New Issue
Block a user