Error handling
Errors are thrown, not returned. Throw a typed error anywhere in the pipeline — a provider, a step, or a handler — and green-tea converts it to a response at one place.
The typed errors
Section titled “The typed errors”All live in @green-tea/core:
| Throw | Status |
|---|---|
Unauthorized(msg?) |
401 |
NotFound(msg?) |
404 |
NotModified() |
304 |
Redirect(location) |
302 + Location |
ValidationError |
422 (raised for you by schema validation) |
HttpError(status, msg?, body?) |
any status |
| anything else | 500 (message hidden — internals never leak) |
@Step({ provides: 'user', needs: ['db', 'req'] })class Authenticate { run(ctx) { const user = ctx.db.find(ctx.req.headers['x-token']); if (!user) throw new Unauthorized('invalid token'); // → 401, cuts the request return { user }; }}By default an error becomes JSON: { "error": "<message>" } (validation adds source + per-field issues).
Structured error bodies
Section titled “Structured error bodies”An HttpError can carry a body — return a structured payload instead of the default { error }:
throw new HttpError(409, 'conflict', { code: 'DUP_EMAIL', field: 'email' });// → 409 { "code": "DUP_EMAIL", "field": "email" }Render errors your way — onError
Section titled “Render errors your way — onError”Not everything should be JSON. Pass onError to createApp to render errors however you like — HTML, RFC 7807, content-negotiated, anything. It receives the error and the request, and returns a response — or undefined to fall back to the default JSON.
import { HttpError } from '@green-tea/core';
const app = createApp({ modules: [ApiModule], onError(error, req) { const status = error instanceof HttpError ? error.status : 500; // negotiate: HTML for browsers, JSON for API clients if (String(req.headers.accept).includes('text/html')) { return { status, headers: { 'content-type': 'text/html' }, body: `<h1>${status}</h1>` }; } return undefined; // fall back to the default JSON },});onError intercepts every HTTP error response — handler/step throws, a 404 for an unmatched route, 405, a 413 (body too large), and a 400 (malformed body, repeated slash, or malformed path encoding) — so your error surface is consistent across the whole app. HEAD error responses keep the same status and headers but suppress the body. Streaming and WebSocket errors are out of scope (they surface as an error frame or a WebSocket close code, not a response body).