Task: Standardize API Error Response Shape #343

Open
opened 2026-02-28 00:09:39 +00:00 by jjalangtry · 0 comments
jjalangtry commented 2026-02-28 00:09:39 +00:00 (Migrated from github.com)

Standardize API Error Response Shape

Description

API routes return errors in inconsistent formats: some use plain text Response bodies, some use Response.json(), some expose raw database error objects via string interpolation (e.g. return new Response("Failed: " + e, { status: 500 })). This leaks internal details and makes client-side error handling fragile.

Associated Epic: #340

Subtasks

  • Create next/lib/apiError.ts with the following helpers:
    • validationError(issues: ZodIssue[]) — returns Response with status 400 and JSON body { error: "Validation failed", issues }
    • unauthorized() — returns Response with status 401 and body { error: "Unauthorized" }
    • forbidden() — returns Response with status 403 and body { error: "Forbidden" }
    • notFound(resource?: string) — returns Response with status 404 and body { error: "Not found" }
    • internalError() — returns Response with status 500 and body { error: "Internal server error" } (never exposes e.message)
  • Audit all route files for Response bodies that interpolate error objects (e.g. "Failed: ${e}") and replace with internalError()
  • Replace all plain-text 400 responses with validationError() or a structured 400
  • Replace all plain-text 401/403 responses with unauthorized() / forbidden()
  • Ensure server logs still receive the full error via console.error(e) before the sanitized response is sent

Acceptance Criteria

  • No 500 response body contains a stack trace, SQL error message, or Prisma error detail
  • All 400 responses return JSON with an "error" field and optionally an "issues" array
  • All 401 and 403 responses return JSON with an "error" field
  • Client receives { error: "Internal server error" } for any unhandled exception
# Standardize API Error Response Shape ## Description API routes return errors in inconsistent formats: some use plain text Response bodies, some use Response.json(), some expose raw database error objects via string interpolation (e.g. return new Response("Failed: " + e, { status: 500 })). This leaks internal details and makes client-side error handling fragile. Associated Epic: #340 ## Subtasks - [ ] Create next/lib/apiError.ts with the following helpers: - validationError(issues: ZodIssue[]) — returns Response with status 400 and JSON body { error: "Validation failed", issues } - unauthorized() — returns Response with status 401 and body { error: "Unauthorized" } - forbidden() — returns Response with status 403 and body { error: "Forbidden" } - notFound(resource?: string) — returns Response with status 404 and body { error: "Not found" } - internalError() — returns Response with status 500 and body { error: "Internal server error" } (never exposes e.message) - [ ] Audit all route files for Response bodies that interpolate error objects (e.g. "Failed: ${e}") and replace with internalError() - [ ] Replace all plain-text 400 responses with validationError() or a structured 400 - [ ] Replace all plain-text 401/403 responses with unauthorized() / forbidden() - [ ] Ensure server logs still receive the full error via console.error(e) before the sanitized response is sent ## Acceptance Criteria - [ ] No 500 response body contains a stack trace, SQL error message, or Prisma error detail - [ ] All 400 responses return JSON with an "error" field and optionally an "issues" array - [ ] All 401 and 403 responses return JSON with an "error" field - [ ] Client receives { error: "Internal server error" } for any unhandled exception
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
SSE/WebsiteTheSSEquel#343
No description provided.