Errors

FougereError represents a domain error with a typed code. Handlers throw it, and the following layers retain its code, message, and details.

throw new FougereError({
  code: ErrorCode.CONFLICT,
  message: `Slug '${slug}' is already taken`,
  entity: 'post',
  operation: 'create',
  // details?: [{ path, message }]   — per-field, used by VALIDATION_FAILED
  // cause?: unknown
});

The codes

CodeMeaningHTTP projection
VALIDATION_FAILEDinvalid input — details contains per-field { path, message }400
UNAUTHORIZEDno identity401
FORBIDDENidentity present, right absent403
NOT_FOUNDdesignated thing does not exist404
CONFLICTstate refuses the transition (already published, slug taken)409
SERVICE_UNAVAILABLEa remote Frond is unreachable503
INTERNAL_ERRORanything unexpected500

INTERNAL_ERROR is the one code whose message never leaves

The other six were written for the caller and travel whole. An INTERNAL_ERROR was not: it may quote a path, a query or a row, so it is replaced by a constant before leaving the process.

It is logged in the same place — masking and recording live in one function, so the sentence exists exactly once, on the server. An operator asking "why this 500" finds it in the logs with its cause, its entity and its operation; an attacker never sees it. If your message must reach the caller, then it is not an INTERNAL_ERROR: give it its code.

Error propagation

  1. Handler throws FougereError — the same object whether the Frond is local or remote.
  2. Local call — the error propagates in memory, untouched.
  3. Remote call — the transport frames it as a JSON-RPC error (code: -32000, data: { code, message, entity, operation, details? }) and the client side rebuilds a FougereError, including its details.
  4. In a pageuseQuery/useCommand expose it as error (a real FougereError); useFormFor maps VALIDATION_FAILED.details onto per-field errors automatically.
  5. On the REST surfacetoHttpError projects the code to the real HTTP status (table above), the full typed value in data.

The browser receives the same code, message, and per-field errors whether the Frond is local or remote. An unreachable host produces a typed SERVICE_UNAVAILABLE error.

Check order

The site's blog performs checks in this order:

requireUser(user)        // UNAUTHORIZED
requireOwn(id, user)   // NOT_FOUND, then FORBIDDEN
→ state checks           // CONFLICT (already published, empty draft)
→ realize                // orm.update(…)

Next: Seeds — initializing data through operations.

Built with Fougere — this site runs on the framework it documents.