Skip to content

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. For VALIDATION, the list of Zod issues. The pattern field is left out: it is the regular expression your schema uses, and clients do not need it.

Error codes ​

CodeStatusWhen
INVALID_HOST400listen() received a Host header that is not allowed
VALIDATION400The body, params or query don't match their schema, or a database query got a missing or malformed value
NO_BODY400A body schema is declared, but no body was sent
INVALID_JSON400The body is not valid JSON
PAYLOAD_TOO_LARGE413The JSON body is larger than server.maxBodyBytes (default 1 MiB)
REQUEST_TIMEOUT408The request took longer than server.requestTimeoutMs (default 30 seconds)
TOO_MANY_REQUESTS429The client went over the rate limit. Retry-After gives the seconds to wait
UNSUPPORTED_MEDIA_TYPE415A body schema is declared, but the Content-Type is not JSON
NOT_FOUND404No route has this path
METHOD_NOT_ALLOWED405The path exists, but not for this method. The Allow header lists the valid ones
CONFLICT409A database query broke a unique or foreign key constraint. See Database errors
INTERNAL_SERVER_ERROR500Your 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 LynError class to extend. Until then, use the isLynError object above.

Released under the MIT License.