diff --git a/apps/web/app/api/health/route.ts b/apps/web/app/api/health/route.ts new file mode 100644 index 0000000..2a05c63 --- /dev/null +++ b/apps/web/app/api/health/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from 'next/server'; +import { getDb } from '@/lib/db'; + +// Never cache: a cached 200 would keep reporting health after the app goes away. +export const dynamic = 'force-dynamic'; + +const NO_STORE = { 'Cache-Control': 'no-store' }; + +/** + * Liveness for status.profullstack.com: the app answers and the database + * answers `SELECT 1` within 3s. Unlike /api/health/db it never says why it + * failed. + */ +export async function GET() { + let timer: ReturnType | undefined; + try { + await Promise.race([ + getDb().sql`SELECT 1`, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error('timeout')), 3000); + }), + ]); + return NextResponse.json({ status: 'ok', db: 'ok' }, { headers: NO_STORE }); + } catch { + return NextResponse.json({ status: 'error', db: 'down' }, { status: 503, headers: NO_STORE }); + } finally { + clearTimeout(timer); + } +}