Nuxt health check endpoint
Add server/api/health.get.ts with a defineEventHandler that runs one query, returns a small object on success, and calls setResponseStatus(event, 503) before returning when the query throws.
Nitro makes the endpoint itself trivial. Drop a file in server/api, export a handler, return an object, and Nuxt serialises it to JSON with a 200. That convenience is also the problem: the default path through the framework produces exactly the health check that cannot tell you anything, because returning an object is something a completely broken app does just as easily as a healthy one.
Make the handler earn the 200. Run one query against the database - select 1, no rows, no joins - and if it throws, set the status to 503 before you return. Everything watching the endpoint keys off that number. A Logdash monitor treats 200 to 399 as up and everything else as down, and sends the alert on the transition, so an endpoint that never returns anything but 200 is an endpoint that can never alert.
The Nitro handler
import { db } from '~/server/utils/db';
// defineEventHandler and setResponseStatus are auto-imported by Nitro.
export default defineEventHandler(async (event) => {
try {
await db.execute('select 1');
return { ok: true };
} catch {
// Without this the body below still goes out as a 200.
setResponseStatus(event, 503);
return { ok: false, error: 'db' };
}
}); The order of the two lines in the catch block is the part people get wrong. setResponseStatus mutates the response that Nitro is about to send, so it has to run before the handler returns; call it after and there is nothing left to mutate. The other common mistake is returning the caught error itself for debugging. Nitro will serialise it, and a driver error object carries the host, the port and sometimes the user from your connection string out to a public URL.
What belongs in the check
- The database, and the cache only if a request fails without it. Everything else is noise you will eventually mute.
- No fan-out to payment providers, mail APIs or a sibling service. Their outage becomes your alert, and you have no lever to pull.
- A hard ceiling of one second. Logdash gives up on a request at 10 seconds and files it as down, so a slow health route reads as a dead app.
- Nothing revealing in the body. Nitro will serialise whatever object you hand it, including an error object with a connection string in it.
- One route, not one per dependency. A monitor watches one URL, and a single 503 is enough to get you looking.
Put a monitor on it
- 1 Register the URL Create a project in Logdash and add https://yourapp.com/api/health. The first check runs on save and records both the status code and how long the request took.
- 2 Choose the frequency Every 5 minutes on the free plan, every minute on Builder, every 15 seconds on Pro. Response times are charted from every check, and the uptime history is public if you turn on a status page.
- 3 Fail it deliberately Shut the database down and leave Nuxt running. The handler starts returning 503, the monitor flips to down, and the Telegram alert lands naming the endpoint and the status code it saw.
Nitro will cache anything you let it
Check your routeRules in nuxt.config before you trust any of this. A broad rule such as "/api/**" with swr or isr set will serve a stored copy of the last successful response, and a cached 200 is worse than having no health check at all, because it looks like it is working. Exclude the health path explicitly rather than assuming a per-request handler cannot be cached. The same applies to any CDN or reverse proxy in front of Nuxt: a health URL that something else can answer is a health URL your monitor has stopped measuring. Curl it once from outside your network after every deploy that touches routing, and read the status line rather than the body, since the body is the part that lies most convincingly.
Where does a health check go in a Nuxt app?
server/api/health.get.ts, which Nitro serves at GET /api/health. The .get suffix restricts it to GET, so anything else on that path gets a 405 instead of running your query.
How do I return a 503 from a Nuxt server route?
Call setResponseStatus(event, 503) and then return your body. Throwing createError works too and is the right tool for genuine errors, but setResponseStatus keeps the response body yours, which matters when something other than a monitor reads it.
Should the Nuxt health endpoint check the database?
Yes, with one query that touches no rows. A Nuxt server can serve pages fine while the database refuses connections, and a health check that skips the query will report up through all of it.
Does the health endpoint need to be protected?
No. Keep it public and keep the body to a boolean. An auth layer in front of it just means a rotated key can take your monitoring down while the app is perfectly healthy.