API Error Handling: RFC 7807 Problem Details & Best Practices
Consistent, informative error responses are a mark of a well-designed API. This guide covers the HTTP status code vocabulary, the RFC 7807 Problem Details standard, validation error patterns, and Express middleware for centralizing error handling.
HTTP Status Code Quick Reference
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | POST created a resource (add Location header) |
| 204 | No Content | DELETE succeeded, no body returned |
| 400 | Bad Request | Malformed JSON, missing required params |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Authenticated but lacks permission |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate create, version conflict |
| 422 | Unprocessable Entity | Validation errors on well-formed input |
| 429 | Too Many Requests | Rate limit exceeded (add Retry-After) |
| 500 | Internal Server Error | Unexpected server failure |
| 503 | Service Unavailable | Downstream dependency is down |
RFC 7807 Problem Details Format
Use Content-Type: application/problem+json and structure errors as standardized Problem Details objects. Extend with custom fields as needed:
// HTTP 422 Unprocessable Entity
// Content-Type: application/problem+json
{
"type": "https://devplaybook.cc/problems/validation-error",
"title": "Validation Failed",
"status": 422,
"detail": "The request body contains invalid fields.",
"instance": "/requests/f7ab2c10-a1b2-4c3d",
"errors": [
{
"pointer": "/data/attributes/email",
"detail": "Must be a valid email address",
"code": "invalid_email"
},
{
"pointer": "/data/attributes/age",
"detail": "Must be a positive integer",
"code": "invalid_range"
}
]
} Express Central Error Handler
Centralize all error formatting in a single Express error middleware registered last. Custom error classes carry status codes and structured detail:
// errors/AppError.ts
export class AppError extends Error {
constructor(
public readonly status: number,
public readonly type: string,
message: string,
public readonly errors?: object[],
) { super(message); }
}
export class ValidationError extends AppError {
constructor(errors: { pointer: string; detail: string; code: string }[]) {
super(422, 'https://devplaybook.cc/problems/validation-error',
'Validation Failed', errors);
}
}
// middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
export function errorHandler(err: any, req: Request, res: Response, _next: NextFunction) {
// Never expose stack traces in production
if (process.env.NODE_ENV !== 'production') console.error(err);
const status = err.status ?? 500;
const body: Record<string, unknown> = {
type: err.type ?? 'https://devplaybook.cc/problems/internal-error',
title: err.message ?? 'An unexpected error occurred',
status,
instance: req.path,
};
if (err.errors) body.errors = err.errors;
// Log internal errors with correlation ID
if (status >= 500) {
const correlationId = req.headers['x-correlation-id'] ?? crypto.randomUUID();
console.error({ correlationId, error: err.message, stack: err.stack });
body.correlationId = correlationId;
body.title = 'An unexpected error occurred'; // sanitize message
}
res.status(status).contentType('application/problem+json').json(body);
} Related Products
on GumroadDevPlaybook Interview Vault
210+ real technical interview questions with model answers, asked at Google, Meta, Amazon, Microsoft, Stripe, and Shopify. Covers Frontend (55), Backend (55), System Design (50), and DSA (50). Available in English and Traditional Chinese.
Buy on Gumroad →TypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad →Developer Interview Vault
210 technical interview questions with model answers, key points, and follow-up questions. Covers Frontend, Backend, System Design, and DSA — bilingual English + Traditional Chinese (ZH-TW). PWYW from $29.
Buy on Gumroad →You Might Also Need
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
API Response Mock Generator
Generate realistic mock API responses with custom status codes, headers, and JSON body. Quick templates for 200, 401, 404, 429, 500 and more. Runs in your browser.
Open free tool →
API Response Time Tester
Test API endpoint response times directly from your browser. Run multiple requests, see average/min/max times, status codes, and response sizes. GET, POST, PUT, DELETE support. Free.
Open free tool →
More Free Tools
Related Articles
Hono vs Express: The New Node.js Server Framework Battle in 2026
A comprehensive comparison of Hono and Express in 2026 — performance benchmarks, middleware, TypeScript support, edge deployment, ecosystem maturity, and which framework to choose for your next API.
Complete HTTP Status Codes Reference for REST API Devs
The complete HTTP status codes reference for REST API developers. Every 1xx, 2xx, 3xx, 4xx, and 5xx code explained with real-world API examples and best practices.
REST API Best Practices: Endpoint Naming, Versioning, and Error Handling
Design clean, consistent REST APIs with proper URL naming, HTTP methods, versioning, error formats, pagination, auth, and rate limiting.
Understanding REST API Design Best Practices
A practical guide to designing REST APIs that developers love — covering resource modeling, HTTP semantics, status codes, versioning, and error handling.
curl 深度使用手冊:API 測試從入門到進階
curl 是每位開發者必備的 CLI 工具。本文涵蓋 GET/POST/PUT/DELETE 請求、認證、header 設定、檔案上傳、除錯技巧,以及 REST API 測試的完整實戰範例。
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →Frequently Asked Questions
What HTTP status codes should my API use?▼
Use 200 OK for successful GET/PUT/PATCH, 201 Created for POST that creates a resource (include a Location header), 204 No Content for DELETE. For errors: 400 Bad Request for malformed input, 401 Unauthorized for missing/invalid auth, 403 Forbidden for insufficient permissions, 404 Not Found for missing resources, 409 Conflict for duplicate creates, 422 Unprocessable Entity for validation errors, 429 Too Many Requests for rate limits, 500 Internal Server Error for unexpected failures.
What is RFC 7807 Problem Details?▼
RFC 7807 (now superseded by RFC 9457) is an IETF standard for structuring HTTP API error responses. It defines a JSON object with type (a URI identifying the error type), title (human-readable summary), status (HTTP status code), detail (specific explanation), and instance (a URI identifying this specific error occurrence). Using a standard format means clients can parse errors generically across different APIs.
Should I return validation errors in a single response or one at a time?▼
Always return all validation errors at once. Returning one error at a time forces the user or client to fix, resubmit, and discover the next error in a frustrating loop. Collect all validation failures, return them in an errors array in a single 422 response, and make each error object identify the specific field (via a pointer or field name) and the reason it failed.
Should I expose stack traces in API error responses?▼
Never in production. Stack traces expose your internal implementation details, file paths, library versions, and logic flow to potential attackers. Log the full stack trace server-side with a correlation ID, and return only that correlation ID in the error response so your team can look up the details. The error message returned to clients should describe what went wrong, not how your code works.
What is the difference between 401 and 403?▼
401 Unauthorized means the request lacks valid credentials — the client is not authenticated. The client should retry with a valid token or credentials. 403 Forbidden means the client is authenticated but does not have permission to perform the action — re-authenticating will not help. A common mistake is returning 401 for permission failures, which confuses clients into attempting re-authentication unnecessarily.
How should GraphQL APIs handle errors?▼
GraphQL uses a top-level errors array alongside the data field. A request can partially succeed — data contains the successful parts and errors lists the failures with message, locations (line/column in the query), path (which field failed), and extensions for additional metadata like error codes. For application-level errors that should be part of the type system, use union types (e.g. CreateUserResult = User | ValidationError | AuthError) so clients get typed error handling.