> For the complete documentation index, see [llms.txt](https://azutopia.gitbook.io/azu/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://azutopia.gitbook.io/azu/validation/handle-validation-errors.md).

# Handle Validation Errors

This guide shows you how to handle and respond to validation errors in your Azu application.

## Automatic Error Handling

Azu automatically validates requests and raises `ValidationError` for invalid data:

```crystal
struct CreateUserEndpoint
  include Azu::Endpoint(CreateUserRequest, UserResponse)

  post "/users"

  def call : UserResponse
    # If validation fails, Azu raises ValidationError automatically
    # The error handler converts it to a 422 response
    UserResponse.new(User.create!(create_user_request))
  end
end
```

## Default Error Response

When validation fails, Azu returns:

```json
{
  "errors": [
    {"field": "name", "message": "can't be blank"},
    {"field": "email", "message": "is invalid"}
  ]
}
```

With HTTP status `422 Unprocessable Entity`.

## Custom Error Responses

Create a custom error response format:

```crystal
struct ValidationErrorResponse
  include Azu::Response

  def initialize(@errors : Array(Azu::Request::Error))
  end

  def render
    {
      success: false,
      error: {
        code: "VALIDATION_FAILED",
        details: @errors.map do |e|
          {field: e.field.to_s, message: e.message}
        end
      }
    }.to_json
  end
end
```

## Manual Validation Handling

Handle validation manually in your endpoint:

```crystal
struct CreateUserEndpoint
  include Azu::Endpoint(CreateUserRequest, Azu::Response)

  post "/users"

  def call : Azu::Response
    request = create_user_request

    unless request.valid?
      status 422
      return ValidationErrorResponse.new(request.errors)
    end

    status 201
    UserResponse.new(User.create!(request))
  end
end
```

## Model Validation Errors

Handle model validation errors:

```crystal
def call : Azu::Response
  user = User.new(
    name: create_user_request.name,
    email: create_user_request.email
  )

  if user.save
    status 201
    UserResponse.new(user)
  else
    status 422
    ModelErrorResponse.new(user.errors)
  end
end
```

## Custom Error Handler

Create a global error handler for validation errors:

```crystal
class ValidationErrorHandler < Azu::Handler::Base
  def call(context)
    call_next(context)
  rescue error : Azu::Response::ValidationError
    context.response.status_code = 422
    context.response.content_type = "application/json"
    context.response.print({
      error: "Validation Failed",
      details: error.errors.map { |e| {field: e.field, message: e.message} }
    }.to_json)
  end
end
```

Register the handler:

```crystal
MyApp.start [
  ValidationErrorHandler.new,
  Azu::Handler::Rescuer.new,
  # ... other handlers
]
```

## Collecting All Errors

Ensure all validation errors are collected:

```crystal
struct MultiFieldRequest
  include Azu::Request

  getter name : String
  getter email : String
  getter password : String

  def initialize(@name = "", @email = "", @password = "")
  end

  validate name, presence: true, length: {min: 2}
  validate email, presence: true, format: /@/
  validate password, presence: true, length: {min: 8}

  # All errors are collected, not just the first one
end
```

## Displaying Errors to Users

For HTML responses, pass errors to templates:

```crystal
def call
  request = create_user_request

  unless request.valid?
    return view "users/new.html", {
      errors: request.errors,
      form_data: request
    }
  end

  redirect_to "/users"
end
```

In your template:

```html
{% if errors %}
<div class="errors">
  <ul>
    {% for error in errors %}
    <li>{{ error.field }}: {{ error.message }}</li>
    {% endfor %}
  </ul>
</div>
{% endif %}
```

## Internationalization

Customize error messages:

```crystal
struct CreateUserRequest
  include Azu::Request

  getter name : String

  def initialize(@name = "")
  end

  def validate
    if name.empty?
      errors << Error.new(:name, I18n.t("errors.name.blank"))
    elsif name.size < 2
      errors << Error.new(:name, I18n.t("errors.name.too_short", min: 2))
    end
  end
end
```

## See Also

* [Validate Requests](/azu/validation/validate-requests.md)
* [Validate Models](/azu/validation/validate-models.md)
* [Create Custom Errors](/azu/error-handling/create-custom-errors.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://azutopia.gitbook.io/azu/validation/handle-validation-errors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
