SvelteKit health check endpoint
Create src/routes/health/+server.ts, export a GET handler that runs one query and returns json({ ok: true }) or a 503 when the query throws, and set export const prerender = false so the route is built as a real endpoint.
The value of a health route is entirely in what it refuses to say. Returning 200 because the handler ran is worth almost nothing: the Node adapter will happily serve that response from a box whose database credentials expired an hour ago. What you want is a route that goes quiet the moment the app stops being able to do its job, so that the monitor watching it has something real to react to.
One query is enough to get there. Run select 1 through whatever client your load functions use, return json({ ok: true }) when it comes back, and return a 503 when it throws. That status code is the contract with everything downstream - Logdash counts 200 to 399 as up and anything else as down, and alerts on the moment the state changes, so the 503 is what converts a dead connection pool into a message on your phone.
The endpoint
import { json } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import type { RequestHandler } from './$types';
export const prerender = false;
export const GET: RequestHandler = async () => {
try {
await db.execute('select 1');
return json({ ok: true });
} catch {
// Second argument is a ResponseInit, same as new Response().
return json({ ok: false, error: 'db' }, { status: 503 });
}
}; Two things are worth copying from that file beyond the query. The handler imports from $lib/server, which keeps the database client out of any bundle that could reach the browser and makes SvelteKit shout at you if it ever does. And it uses the same client your load functions use, rather than opening a fresh connection, so the check exercises the pool that real traffic depends on. A health route with its own private connection can pass while every page on the site is queued behind an exhausted pool.
Keep the check small
- One dependency check per thing the app cannot serve without, and no more. Two queries and a Redis ping is a full health check for most apps.
- Do not call your own API routes from it. You end up measuring your own HTTP stack twice and doubling the chance of a false alarm.
- Skip third-party APIs entirely. Their downtime is real, it is just not something an alert to you can fix at 3am.
- Stay under a second. Logdash abandons a request after 10 seconds and records it as down, which is correct behaviour but a confusing way to learn your query is slow.
- Return json({ ok: true }) and stop. Build hashes and adapter details in a public response body help attackers more than they help you.
Monitor it
- 1 Add the endpoint Create a project in Logdash and enter https://yourapp.com/health. The first check fires on save, which is when a typo in the route path is cheap to find.
- 2 Pick the check interval Free is every 5 minutes across five projects, Builder is every minute, Pro is every 15 seconds. The response time from each check lands on a chart next to the uptime history.
- 3 Trigger a real failure Point DATABASE_URL somewhere that does not exist and restart the app. The endpoint answers 503, the monitor turns red, and the Telegram alert arrives with the URL and status code in the message.
Prerendering will lie to you
If prerender is switched on globally in src/routes/+layout.ts, or you are on adapter-static, SvelteKit will try to render this endpoint at build time and bake the response into a file. The build machine has no database, so you either get a build error or, worse, a permanently cached 200 sitting on a CDN answering every check for the next six months. Setting prerender = false in the file itself makes the route dynamic whatever the layout says, and it is one line you will not have to remember when you swap adapters. Worth knowing where the endpoint actually runs, too: on adapter-node it runs in your process and a database check means something, while on a serverless adapter each check may hit a cold function, which is a real number but not the one you thought you were measuring.
Where do I put a health check route in SvelteKit?
src/routes/health/+server.ts, which serves GET /health. Any path works, so if the app already owns /health for something else, src/routes/api/health/+server.ts is the usual second choice.
Should I use json() or new Response() in +server.ts?
json() from @sveltejs/kit, since it sets the content type and takes a ResponseInit as its second argument, which is where the 503 goes. new Response("ok") is fine if the body is a plain string and you set the status yourself.
Why does my SvelteKit health check need prerender = false?
Because a prerendered endpoint is a static file. It runs once on a build machine with no database, then serves that same answer to every monitor forever, which is the exact failure mode a health check exists to prevent.
What status code should the SvelteKit health route return?
200 when the query succeeds, 503 when it does not. Logdash marks a monitor down on any status outside 200 to 399, so a correct 503 is the difference between an alert and a green dashboard during an outage.