Errors & problem+json
DaloyJS errors are first-class. Every thrown HttpError serializes to RFC 9457 problem+json with a stable type URI, a request-id, and the appropriate Content-Type.
Built-in error classes
import {
BadRequestError, // 400
ValidationError, // 422
UnauthorizedError, // 401
ForbiddenError, // 403
NotFoundError, // 404
MethodNotAllowedError, // 405 + Allow header
PayloadTooLargeError, // 413
UnsupportedMediaTypeError, // 415
RequestTimeoutError, // 408
TooManyRequestsError, // 429 + Retry-After
InternalError, // 500 (detail redacted in production)
} from "@daloyjs/core";Throwing in a handler
import { NotFoundError } from "@daloyjs/core";
app.route({
method: "GET",
path: "/users/:id",
operationId: "getUser",
responses: { 200: { description: "ok" }, 404: { description: "missing" } },
handler: async ({ params }) => {
const user = await db.find(params.id);
if (!user) throw new NotFoundError(`user ${params.id} not found`);
return { status: 200, body: user };
},
});Wire format
The request id is returned to the client in two places: the x-request-id response header, and (per RFC 9457 §3.1) the problem document's instance field as a urn:request:<uuid> URN. There is no top-level requestId property — clients should read the header or parse the URN from instance.
HTTP/1.1 404 Not Found
content-type: application/problem+json
x-request-id: c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431
{
"type": "https://daloyjs.dev/errors/not-found",
"title": "Not Found",
"status": 404,
"detail": "user 42 not found",
"instance": "urn:request:c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431"
}Production redaction
When NODE_ENV=production, DaloyJS strips the detailfield on any 5xx response so internal stack traces and SQL fragments don't leak to clients. The full error is still emitted to your logger via the onError hook.
Custom error classes
import { HttpError } from "@daloyjs/core";
export class QuotaExceededError extends HttpError {
constructor(resource: string) {
super(429, {
title: "Quota exceeded",
type: "https://api.example.com/errors/quota-exceeded",
detail: `Quota exceeded for ${resource}`,
});
}
}Custom onError
app.use({
onError: async (error, ctx) => {
logger.error({ err: error, requestId: ctx?.requestId }, "request failed");
// return a Response to override; otherwise DaloyJS serializes problem+json
},
});