From da77429939ce4c8f1d3a354208556d233fc522b6 Mon Sep 17 00:00:00 2001 From: hartecho Date: Fri, 24 Apr 2026 00:03:19 -0600 Subject: [PATCH] feat(web): add public /api/health endpoint with DB probe --- packages/web/src/app/api/health/route.ts | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/web/src/app/api/health/route.ts diff --git a/packages/web/src/app/api/health/route.ts b/packages/web/src/app/api/health/route.ts new file mode 100644 index 0000000..c57812f --- /dev/null +++ b/packages/web/src/app/api/health/route.ts @@ -0,0 +1,53 @@ +// Public health probe. Verifies DB connectivity so post-deploy smoke tests +// can confirm the serverless function talks to Postgres before real traffic hits. +// +// GET /api/health → 200 when all checks pass, 503 when any fail. +// No auth — meant to be hit by monitors and CI. + +import { NextResponse } from "next/server"; +import { prisma } from "@relay/db"; + +export const dynamic = "force-dynamic"; +export const runtime = "nodejs"; + +interface CheckResult { + name: string; + ok: boolean; + ms: number; + detail?: string; +} + +async function time(fn: () => Promise): Promise<{ result: T; ms: number }> { + const start = Date.now(); + const result = await fn(); + return { result, ms: Date.now() - start }; +} + +async function checkDb(): Promise { + try { + const { ms } = await time(() => prisma.$queryRaw`SELECT 1`); + return { name: "db", ok: true, ms }; + } catch (err) { + return { + name: "db", + ok: false, + ms: 0, + detail: err instanceof Error ? err.message : String(err), + }; + } +} + +export async function GET() { + const checks = [await checkDb()]; + const allOk = checks.every((c) => c.ok); + return NextResponse.json( + { + ok: allOk, + version: process.env.VERCEL_GIT_COMMIT_SHA ?? "dev", + deployment: process.env.VERCEL_DEPLOYMENT_ID ?? null, + region: process.env.VERCEL_REGION ?? null, + checks, + }, + { status: allOk ? 200 : 503 }, + ); +}