# RealWorld API Validation Error Response Format

> Discover the RealWorld API validation error response format. Learn how validation errors are returned as HTTP 422 responses with detailed field-specific messages for efficient debugging.

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

---

**The RealWorld API returns validation errors as HTTP 422 (Unprocessable Entity) responses with a JSON body containing an `errors` object that maps field names to arrays of human-readable error messages.**

The RealWorld specification serves as the standard for building full-stack demo applications, and the `gothinkster/realworld` repository provides canonical backend implementations. Understanding the RealWorld API validation error response format is essential for frontend developers, as it ensures consistent error handling across all endpoints that process user input, from authentication to content creation.

## Structure of the Validation Error Response

### HTTP Status Code 422

When input validation fails, route handlers throw an `HttpException` with status code **422** (Unprocessable Entity). This status indicates that while the server understands the request syntax, it cannot process the contained instructions due to semantic errors in the submitted data.

### The Errors Object Schema

The response body follows a strict JSON schema enforced across all validated endpoints:

- A top-level **`errors`** property containing an object
- Keys represent invalid field names (e.g., `email`, `username`, `title`, `body`)
- Values are **arrays of strings** describing specific validation failures

```json
{
  "errors": {
    "email": ["can't be blank"],
    "password": ["can't be blank"],
    "username": ["has already been taken"]
  }
}

```

Clients receive this payload with a `422` status code and can iterate over the `errors` object to display messages next to relevant form fields.

## Implementation in the RealWorld Codebase

The validation logic is implemented manually within individual route handlers rather than through centralized middleware. The `HttpException` class defined in [`apps/api/server/models/http-exception.model.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/models/http-exception.model.ts) provides the mechanism for formatting these responses, storing the HTTP status code and the error payload.

### User Login Validation

In [`apps/api/server/routes/api/users/login.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/users/login.post.ts), missing credentials trigger specific error messages by manually constructing the payload:

```typescript
if (!email) {
  throw new HttpException(422, { errors: { email: ["can't be blank"] } });
}
if (!password) {
  throw new HttpException(422, { errors: { password: ["can't be blank"] } });
}

```

### Registration and Uniqueness Constraints

The registration endpoint in [`apps/api/server/routes/api/users/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/users/index.post.ts) demonstrates both required field validation and database uniqueness checks:

```typescript
if (!email) {
  throw new HttpException(422, { errors: { email: ["can't be blank"] } });
}
if (!username) {
  throw new HttpException(422, { errors: { username: ["can't be blank"] } });
}
if (!password) {
  throw new HttpException(422, { errors: { password: ["can't be blank"] } });
}

// Duplicate-check error
if (existingUserByEmail || existingUserByUsername) {
  throw new HttpException(422, {
    errors: {
      ...(existingUserByEmail ? { email: ["has already been taken"] } : {}),
      ...(existingUserByUsername ? { username: ["has already been taken"] } : {}),
    },
  });
}

```

### Article Creation Validation

Content creation endpoints follow the same pattern. In [`apps/api/server/routes/api/articles/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/articles/index.post.ts), required article fields generate 422 responses when omitted:

```typescript
if (!title) {
  throw new HttpException(422, { errors: { title: ["can't be blank"] } });
}
if (!description) {
  throw new HttpException(422, { errors: { description: ["can't be blank"] } });
}
if (!body) {
  throw new HttpException(422, { errors: { body: ["can't be blank"] } });
}

```

## Parsing Validation Errors in Client Applications

Frontend applications consuming the RealWorld API should expect the `422` status code and parse the `errors` object to display field-specific feedback. Since each key maps directly to a form input name, clients can iterate over the error entries and attach messages to their corresponding UI elements without additional mapping logic. This standardization allows a single error handler component to manage validation feedback for login forms, registration flows, and article editors alike.

## Summary

- The RealWorld API uses **HTTP 422** status codes for all validation failures, signaling unprocessable entity errors.
- Error responses contain a JSON object with a single **`errors`** property at the root level.
- Each key in the `errors` object represents a field name, with values being arrays of descriptive error strings (e.g., `["can't be blank"]` or `["has already been taken"]`).
- Implementation occurs in individual route handlers such as [`apps/api/server/routes/api/users/login.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/users/login.post.ts) and [`apps/api/server/routes/api/articles/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/articles/index.post.ts) using the `HttpException` class from [`apps/api/server/models/http-exception.model.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/models/http-exception.model.ts).
- This standardized format enables client applications to handle validation feedback consistently across login, registration, article creation, and comment endpoints.

## Frequently Asked Questions

### What HTTP status code does the RealWorld API use for validation errors?

The API returns **422 Unprocessable Entity** for all validation failures. This status indicates that while the request syntax is valid, the server cannot process the instructions due to semantic errors in the submitted data, such as missing required fields or duplicate values.

### How does the RealWorld API handle multiple validation errors on different fields?

The API aggregates all validation failures into a single `errors` object within the response body. Each invalid field receives its own key, allowing clients to retrieve comprehensive feedback in one request. For example, submitting a blank registration form might return both `email: ["can't be blank"]` and `password: ["can't be blank"]` simultaneously.

### Where is the RealWorld API error response format defined in the source code?

While the format is documented in the API specification, the implementation resides in [`apps/api/server/models/http-exception.model.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/models/http-exception.model.ts) where the `HttpException` class is defined. Individual routes instantiate this class with status code 422 and the specific `errors` payload, as seen in [`apps/api/server/routes/api/users/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/users/index.post.ts).

### Does the RealWorld API use automatic validation middleware?

No, the reference implementation handles validation manually within each route handler. Routes check conditions individually—such as verifying field presence or database uniqueness—and construct the `errors` object explicitly before throwing `HttpException`, rather than using schema validation middleware or decorator-based validation systems.