Skip to content

Commit

Permalink
feat(api): Add /health endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
alepefe committed Nov 4, 2024
1 parent d583773 commit d440e49
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 8 deletions.
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@nestjs/jwt": "10.2.0",
"@nestjs/passport": "10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/terminus": "10.2.3",
"@nestjs/typeorm": "^10.0.2",
"@ts-rest/nest": "3.45.2",
"bcrypt": "5.1.1",
Expand Down
24 changes: 19 additions & 5 deletions api/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,33 @@ import { AppService } from '@api/app.service';
import { tsRestHandler, TsRestHandler } from '@ts-rest/nest';
import { contactContract as c } from '@shared/contracts/contact.contract';
import { Public } from '@api/decorators/is-public.decorator';
import { ControllerResponse } from '@api/types/controller.type';
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
constructor(
private readonly health: HealthCheckService,
private readonly db: TypeOrmHealthIndicator,
private readonly appService: AppService,
) {}

@Public()
@Get()
getHello(): string {
return this.appService.getHello();
@Get('/health')
@HealthCheck({ noCache: true })
public checkHealth(): ControllerResponse {
return this.health.check([
async () => this.db.pingCheck('database', { timeout: 1500 }),
]);
}

@Public()
@TsRestHandler(c.contact)
async recoverPassword(): Promise<any> {
async recoverPassword(): Promise<ControllerResponse> {
return tsRestHandler(c.contact, async ({ body }) => {
await this.appService.contact(body);
return { body: null, status: HttpStatus.CREATED };
Expand Down
4 changes: 4 additions & 0 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import { WidgetsModule } from '@api/modules/widgets/widgets.module';
import { DataSourceManager } from '@api/infrastructure/data-source-manager';
import { LoggingModule } from '@api/modules/logging/logging.module';
import { SQLAdapter } from '@api/infrastructure/sql-adapter';
import { TerminusModule } from '@nestjs/terminus';

const NODE_ENV = process.env.NODE_ENV;

@Module({
imports: [
TerminusModule.forRoot({ logger: NODE_ENV === 'test' ? false : true }),
TsRestModule.register({
isGlobal: true,
validateRequestBody: true,
Expand Down
3 changes: 0 additions & 3 deletions api/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { ContactForm, ContactMailer } from '@api/contact.mailer';
@Injectable()
export class AppService {
constructor(private readonly contactMailer: ContactMailer) {}
getHello(): string {
return 'Hello World!';
}

async contact(contactForm: ContactForm): Promise<void> {
await this.contactMailer.sendContactMail(contactForm);
Expand Down
46 changes: 46 additions & 0 deletions api/test/e2e/health/health.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TestManager } from 'api/test/utils/test-manager';

describe('Health', () => {
let testManager: TestManager<unknown>;

beforeAll(async () => {
testManager = await TestManager.createTestManager({ logger: false });
});

beforeEach(async () => {
await testManager.clearDatabase();
});

afterAll(async () => {
await testManager.close();
});

it("Should return the app's health status: OK", async () => {
// Given
// The app is running

// When
const { statusCode, body } = await testManager.request().get('/health');

// Then
expect(statusCode).toBe(200);
expect(body).toStrictEqual({
status: 'ok',
info: { database: { status: 'up' } },
error: {},
details: { database: { status: 'up' } },
});
});

it("Should return the app's health status: Unavailable", async () => {
// Given
// The app is running and the database becomes unavailable
await testManager.dataSource.destroy();

// When
const { statusCode } = await testManager.request().get('/health');

// Then
expect(statusCode).toBe(503);
});
});
108 changes: 108 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d440e49

Please sign in to comment.