# RealWorld API Error Handling Best Practices: A Complete Implementation Guide

> Master RealWorld API error handling with this guide. Learn best practices for unified error contracts, consistent status codes, and cross-platform failure communication.

- Repository: [Thinkster/realworld](https://github.com/gothinkster/realworld)
- Tags: best-practices
- Published: 2026-02-28

---

**The RealWorld specification enforces a unified error contract using the `GenericErrorModel` schema with consistent HTTP status codes, frontend `.error-messages` rendering, and Playwright test verification to ensure reliable cross-platform failure communication.**

The `gothinkster/realworld` repository defines a strict, implementation-agnostic protocol for error handling within the RealWorld API that governs how backends serialize failures and how frontends consume them. By adhering to these standards—documented in the OpenAPI specification and enforced via end-to-end tests—developers ensure that validation errors, authentication failures, and server exceptions are communicated predictably across all technology stacks.

## Unified Error Response Format

All error responses must conform to the **`GenericErrorModel`** schema defined in [`specs/api/openapi.yml`](https://github.com/gothinkster/realworld/blob/main/specs/api/openapi.yml). This schema requires a JSON object containing an `errors` field, where keys identify the failing entity (such as `email`, `title`, or `token`) and values are arrays of human-readable strings.

```json
{
  "errors": {
    "title": ["can't be empty"],
    "body": ["can't be blank"]
  }
}

```

This structure appears in the OpenAPI specification at `components.schemas.GenericErrorModel` (lines 33-44) and is referenced by the `GenericError` response definition (lines 63-74). The contract ensures that clients can programmatically parse field-specific validation failures without inspecting proprietary error formats.

## Standard HTTP Status Codes

The RealWorld API assigns specific semantic meanings to HTTP status codes, documented in [`apps/documentation/src/content/docs/specifications/backend/error-handling.md`](https://github.com/gothinkster/realworld/blob/main/apps/documentation/src/content/docs/specifications/backend/error-handling.md) (lines 5-25). Implementations must return the following codes under the specified conditions:

- **401 Unauthorized** – The request lacks a valid JWT token or the provided token has expired.
- **403 Forbidden** – The user is authenticated but lacks permission to access the requested resource.
- **404 Not Found** – The requested resource (e.g., an article or profile) does not exist.
- **422 Unprocessable Entity** – The request body fails schema validation, such as empty required fields or malformed email addresses.
- **5xx Server Error** – An unexpected internal error occurred; clients should implement retry logic or display a generic connection failure message.

Each endpoint in the OpenAPI specification explicitly declares which error codes it may return, allowing client generators to create type-safe error handling logic.

## Frontend Error Consumption Patterns

Client applications must follow a four-step pattern to display errors according to the RealWorld UI specification:

1. **Detect non-2xx status codes** using the response object (e.g., `!response.ok` in Fetch API).
2. **Parse the JSON payload** and extract the `errors` object.
3. **Flatten message arrays** into a single list of strings suitable for UI rendering.
4. **Render the list** inside a `<ul class="error-messages">` element as defined in [`apps/documentation/src/content/docs/specifications/frontend/templates.md`](https://github.com/gothinkster/realworld/blob/main/apps/documentation/src/content/docs/specifications/frontend/templates.md) (lines 235-242).

The CSS selector `.error-messages` is styled in [`assets/theme/styles.css`](https://github.com/gothinkster/realworld/blob/main/assets/theme/styles.css) (line 1111) to ensure consistent visual presentation across all implementations. This markup contract allows the automated test suite to verify that errors are actually visible to users.

## Backend Implementation Guidelines

When handling failures, backend implementations must:

- Return **422** for validation errors and **5xx** for unexpected internal exceptions.
- Serialize the response using the **`GenericErrorModel`** schema without exposing stack traces or sensitive system details.
- Ensure that the `errors` object keys map directly to input field names for automatic form association.

For example, in a Node.js/Express controller:

```typescript
app.post('/api/articles', async (req, res) => {
  const validationErrors = validateArticle(req.body.article);
  if (validationErrors) {
    return res.status(422).json({ errors: validationErrors });
  }
  // Process valid request...
});

```

This pattern aligns with the OpenAPI-defined 422 response for the POST `/articles` endpoint in [`specs/api/openapi.yml`](https://github.com/gothinkster/realworld/blob/main/specs/api/openapi.yml).

## Automated Testing Requirements

The Playwright test suite in [`specs/e2e/error-handling.spec.ts`](https://github.com/gothinkster/realworld/blob/main/specs/e2e/error-handling.spec.ts) (lines 40-58) enforces the error handling contract by verifying that failed API calls result in visible error messages. The helper utilities in [`specs/e2e/helpers/auth.ts`](https://github.com/gothinkster/realworld/blob/main/specs/e2e/helpers/auth.ts) (lines 12-21) demonstrate the recommended client-side error extraction pattern:

```typescript
async function handleApiError(resp: Response) {
  if (resp.ok) return await resp.json();
  
  const { errors } = await resp.json() as { errors: Record<string, string[]> };
  const messages = Object.values(errors).flat();
  throw new Error(messages.join(' '));
}

```

End-to-end tests specifically check for the presence of the `.error-messages` CSS class and validate that it contains the expected error text, ensuring that implementations honor both the API contract and the UI rendering requirements.

## Summary

- **RealWorld API error handling** relies on the `GenericErrorModel` schema with an `errors` object mapping field names to message arrays.
- **HTTP status codes** follow strict semantics: 401 for authentication, 403 for authorization, 404 for missing resources, 422 for validation, and 5xx for server failures.
- **Frontend implementations** must render errors within a `<ul class="error-messages">` element to satisfy the specification and automated test suite.
- **Source files** defining these standards include [`specs/api/openapi.yml`](https://github.com/gothinkster/realworld/blob/main/specs/api/openapi.yml), [`apps/documentation/src/content/docs/specifications/backend/error-handling.md`](https://github.com/gothinkster/realworld/blob/main/apps/documentation/src/content/docs/specifications/backend/error-handling.md), and [`specs/e2e/error-handling.spec.ts`](https://github.com/gothinkster/realworld/blob/main/specs/e2e/error-handling.spec.ts).
- **Security best practices** prohibit exposing stack traces or internal system details in error responses.

## Frequently Asked Questions

### What JSON structure must RealWorld API error responses follow?

Error responses must follow the `GenericErrorModel` schema defined in [`specs/api/openapi.yml`](https://github.com/gothinkster/realworld/blob/main/specs/api/openapi.yml), returning an object with an `errors` key containing field-specific message arrays, such as `{"errors": {"email": ["is invalid"]}}`.

### Which HTTP status code should I return for validation failures?

Return **422 Unprocessable Entity** for any request that fails schema validation (e.g., missing required fields or invalid formats), as documented in the backend error-handling specification and the OpenAPI contract.

### How does the frontend know where to display API errors?

The specification requires rendering error lists inside a `<ul>` element with the CSS class `error-messages`, styled by [`assets/theme/styles.css`](https://github.com/gothinkster/realworld/blob/main/assets/theme/styles.css) and verified by the Playwright tests in [`specs/e2e/error-handling.spec.ts`](https://github.com/gothinkster/realworld/blob/main/specs/e2e/error-handling.spec.ts).

### Can I include stack traces in error responses for debugging?

No. The RealWorld API security guidelines explicitly prohibit exposing stack traces or sensitive internal details. Only user-readable error messages should be serialized using the `GenericErrorModel` schema.