In one line: Every error follows one structured JSON shape with a
machine-readable
code, a human message, and a requestId for Sentry
correlation. Operational (expected) errors are logged at warn; programmer
(unexpected) errors are logged at error and alert via Sentry.Design Principles
- Never leak internals — No stack traces, DB column names, or internal IDs in client responses
- Structured, predictable responses — Every error follows the same JSON shape
- Machine-readable codes — Clients can programmatically handle specific errors
- Human-readable messages — Users see meaningful text, not “Something went wrong”
- Correlation everywhere — Every error carries a
requestIdfor Sentry cross-referencing - Fail gracefully — Operational errors return proper HTTP codes; programmer errors trigger alerts
Standard Response Shapes
- Error
- Success
HTTP Status Code Map
Error Classification
- Operational (expected)
- Programmer (unexpected)
Logged at
warn level. No Sentry alert.Error Code Registry
All error codes are centralised inpackages/errors/codes.ts — no magic strings:
AppError Class
API Route Pattern
All API routes are wrapped withwithErrorHandler:
Business Layer Pattern
Business logic throws AppError — never catches it. The handler layer above decides what to do:Request ID Correlation
Every request gets a unique ID attached in middleware:Sentry Integration
Rate Limiting
All rate limiting runs in Next.js middleware via@upstash/ratelimit backed by Upstash Redis. Applied before auth checks or any business logic.
Client-Side Error Handling
The client switches on the errorcode to decide what the user sees:
UNAUTHENTICATED → redirect to sign-in
UNAUTHENTICATED → redirect to sign-in
The session is missing or expired — send the user to the sign-in page.
RATE_LIMITED → show a wait toast
RATE_LIMITED → show a wait toast
Show a toast: “Too many requests. Please wait.”
VALIDATION_ERROR → let the form handle it
VALIDATION_ERROR → let the form handle it
Let the form surface field-level errors from
details.Anything else → show error.message in a toast
Anything else → show error.message in a toast
Fall back to displaying
error.message in a toast.error.tsx that reports to Sentry and shows a retry button.