diff --git a/apps/backend/README.md b/apps/backend/README.md index ff7f659..0548380 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -20,5 +20,8 @@ npm run test - Pretty logs in development with [pino-pretty](https://npm.im/pino-pretty). - Graceful shutdown on exit signals via [close-with-grace](https://npm.im/close-with-grace). +- Health checks via + [fastify-custom-healthcheck](https://npm.im/fastify-custom-healthcheck) allowing other + plugins to add custom health checks. - Basic multipart form handling via [@fastify/multipart](https://npm.im/@fastify/multipart). diff --git a/apps/backend/app.ts b/apps/backend/app.ts index 38c3414..5c7d599 100644 --- a/apps/backend/app.ts +++ b/apps/backend/app.ts @@ -1,23 +1,15 @@ import { Multipart } from "@fastify/multipart"; -import fastify, { FastifyRequest } from "fastify"; +import fastify, { FastifyBaseLogger, FastifyHttpOptions, FastifyRequest } from "fastify"; +import * as http from "node:http"; import { basePlugin } from "./plugins/base.js"; -export async function buildApp() { - const loggerConfig = - process.env.NODE_ENV === "development" ? - { - transport: { - target: "pino-pretty", - options: { - ignore: "pid,hostname", - }, - }, - } - : true; - - const app = fastify({ - logger: loggerConfig, - }); +export async function buildApp(opts: FastifyHttpOptions = {}) { + const app = fastify< + http.Server, + http.IncomingMessage, + http.ServerResponse, + FastifyBaseLogger + >(opts); app.register(basePlugin); diff --git a/apps/backend/package.json b/apps/backend/package.json index 3904e70..efb8faf 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -14,6 +14,7 @@ "close-with-grace": "^1.2.0", "dotenv": "^16.4.4", "fastify": "^4.26.0", + "fastify-custom-healthcheck": "^3.1.0", "fastify-plugin": "^4.5.1", "@fastify/multipart": "^8.1.0" }, diff --git a/apps/backend/plugins/base.test.ts b/apps/backend/plugins/base.test.ts index c9bf995..f69d7ae 100644 --- a/apps/backend/plugins/base.test.ts +++ b/apps/backend/plugins/base.test.ts @@ -25,7 +25,6 @@ test("base", async (t) => { }); const data = await response.json(); - console.log(data); assert.equal(response.status, 200); assert.deepEqual(data, [ @@ -39,4 +38,27 @@ test("base", async (t) => { }, ]); }); + + await t.test("health check", async (t) => { + await t.test("success", async (t) => { + const response = await app.inject({ + method: "get", + path: "/health", + }); + + assert.equal(response.statusCode, 200); + assert.equal(response.json().healthChecks.label, "HEALTHY"); + }); + + await t.test("fail", async (t) => { + app.addHealthCheck("label2", () => false, { value: true }); + const response = await app.inject({ + method: "get", + path: "/health", + }); + + assert.equal(response.statusCode, 500); + assert.equal(response.json().healthChecks.label2, "FAIL"); + }); + }); }); diff --git a/apps/backend/plugins/base.ts b/apps/backend/plugins/base.ts index d1d098d..e903ad4 100644 --- a/apps/backend/plugins/base.ts +++ b/apps/backend/plugins/base.ts @@ -1,9 +1,11 @@ -import { FastifyInstance, FastifyRegisterOptions, RegisterOptions } from "fastify"; -import fp from "fastify-plugin"; import fastifyMultipart, { FastifyMultipartBaseOptions } from "@fastify/multipart"; +import { RegisterOptions } from "fastify"; +import fastifyCustomHealthCheck from "fastify-custom-healthcheck"; +import fp from "fastify-plugin"; +import { FastifyBase } from "../types.js"; async function base( - fastify: FastifyInstance, + fastify: FastifyBase, options: RegisterOptions & { multipart?: FastifyMultipartBaseOptions; }, @@ -41,6 +43,20 @@ async function base( ...options.multipart, }); + + // TODO: Why do we need `as any` here? + fastify.register(fastifyCustomHealthCheck as any, { + // TODO: we should allow configuring one or multiple routes + + path: "/health", + info: {}, + }); + + fastify.ready(() => { + fastify.addHealthCheck("label", () => true, { + value: true, + }); + }); } export const basePlugin = fp(base, { diff --git a/apps/backend/server.ts b/apps/backend/server.ts index f98a04a..ab04388 100644 --- a/apps/backend/server.ts +++ b/apps/backend/server.ts @@ -4,7 +4,21 @@ import dotenv from "dotenv"; dotenv.config({ path: [".env.local", ".env"] }); -const app = await buildApp(); +const loggerConfig = + process.env.NODE_ENV === "development" ? + { + transport: { + target: "pino-pretty", + options: { + ignore: "pid,hostname", + }, + }, + } + : true; + +const app = await buildApp({ + logger: loggerConfig, +}); const closeGracefully = closeWithGrace({ delay: 500 }, async (options) => { if (options.err) { diff --git a/apps/backend/types.ts b/apps/backend/types.ts new file mode 100644 index 0000000..5272638 --- /dev/null +++ b/apps/backend/types.ts @@ -0,0 +1,9 @@ +import type { FastifyBaseLogger, FastifyInstance } from "fastify"; +import http from "node:http"; + +export type FastifyBase = FastifyInstance< + http.Server, + http.IncomingMessage, + http.ServerResponse, + FastifyBaseLogger +>;