Errors
The error format
Every error Lyn sends has the same JSON shape:
json
{
"code": "VALIDATION",
"message": "Bad Request",
"cause": [
{
"expected": "string",
"code": "invalid_type",
"path": ["name"],
"message": "Invalid input: expected string, received undefined"
}
]
}code: a stable identifier your client can test.message: a human-readable explanation.cause: extra details, when there are some. ForVALIDATION, the list of Zod issues. Thepatternfield is left out: it is the regular expression your schema uses, and clients do not need it.
Error codes
| Code | Status | When |
|---|---|---|
INVALID_HOST | 400 | listen() received a Host header that is not allowed |
VALIDATION | 400 | The body, params or query don't match their schema, or a database query got a missing or malformed value |
NO_BODY | 400 | A body schema is declared, but no body was sent |
INVALID_JSON | 400 | The body is not valid JSON |
PAYLOAD_TOO_LARGE | 413 | The JSON body is larger than server.maxBodyBytes (default 1 MiB) |
REQUEST_TIMEOUT | 408 | The request took longer than server.requestTimeoutMs (default 30 seconds) |
TOO_MANY_REQUESTS | 429 | The client went over the rate limit. Retry-After gives the seconds to wait |
UNSUPPORTED_MEDIA_TYPE | 415 | A body schema is declared, but the Content-Type is not JSON |
NOT_FOUND | 404 | No route has this path |
METHOD_NOT_ALLOWED | 405 | The path exists, but not for this method. The Allow header lists the valid ones |
CONFLICT | 409 | A database query broke a unique or foreign key constraint. See Database errors |
INTERNAL_SERVER_ERROR | 500 | Your handler threw an unexpected error |
Unexpected errors
If a handler throws, the client gets a generic 500. The real error is logged on the server with the request id, and is never sent to the client:
ts
.get("/boom", () => {
throw new Error("db password is wrong");
})json
{ "code": "INTERNAL_SERVER_ERROR", "message": "Internal Server Error" }Sending your own errors
To answer with your own status and code, throw an error that has isLynError: true, a code and a status:
ts
const notFound = (message: string) =>
Object.assign(new Error(message), {
isLynError: true,
code: "NOT_FOUND",
status: 404,
});
.get(
"/users/:id",
({ params }) => {
const user = users.find((user) => user.id === params.id);
if (!user) throw notFound("User not found");
return user;
},
{ params: z.object({ id: z.string() }) }
)json
{ "code": "NOT_FOUND", "message": "User not found" }code must be a string and status an integer from 400 to 599. Otherwise the error is treated as unexpected: the client gets a generic 500 and the error is logged.
Keep messages for humans: no SQL, paths or other users' data. See Keep errors boring.
Not supported yet
- An exported
LynErrorclass to extend. Until then, use theisLynErrorobject above.