Isaac.

web

API Error Handling

Implement consistent error handling and responses in APIs.

By Emem IsaacApril 9, 20213 min read
#error handling#api responses#status codes#validation
Share:

A Simple Analogy

Good error handling is like clear signage. It tells users exactly what went wrong and how to fix it.


Why Error Handling?

  • Clarity: Users understand what failed
  • Debugging: Easier to fix issues
  • Security: Don't expose internals
  • Consistency: Predictable error format
  • Recovery: Guide users to solutions

HTTP Status Codes

2xx Success
- 200 OK: Request succeeded
- 201 Created: Resource created
- 204 No Content: Success, no body

4xx Client Error
- 400 Bad Request: Invalid input
- 401 Unauthorized: Not authenticated
- 403 Forbidden: No permission
- 404 Not Found: Resource missing
- 409 Conflict: State conflict
- 422 Unprocessable: Validation failed

5xx Server Error
- 500 Internal Server Error: Server fault
- 503 Service Unavailable: Temporarily down

Error Response Format

public class ApiError
{
    public string Code { get; set; }
    public string Message { get; set; }
    public Dictionary<string, string[]> Errors { get; set; }
    public string TraceId { get; set; }
}

// Success response
public class ApiResponse<T>
{
    public T Data { get; set; }
    public bool Success { get; set; }
}

// Controller
[HttpPost]
public async Task<ActionResult<ApiResponse<OrderDto>>> CreateOrder(CreateOrderRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(new ApiError
        {
            Code = "VALIDATION_ERROR",
            Message = "Validation failed",
            Errors = ModelState
                .Where(x => x.Value.Errors.Count > 0)
                .ToDictionary(x => x.Key, x => x.Value.Errors.Select(e => e.ErrorMessage).ToArray()),
            TraceId = HttpContext.TraceIdentifier
        });
    }
    
    try
    {
        var order = await _service.CreateAsync(request);
        return Ok(new ApiResponse<OrderDto> { Data = order, Success = true });
    }
    catch (ValidationException ex)
    {
        return BadRequest(new ApiError
        {
            Code = "INVALID_ORDER",
            Message = ex.Message,
            TraceId = HttpContext.TraceIdentifier
        });
    }
}

Global Exception Handling

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async context =>
    {
        context.Response.ContentType = "application/json";
        
        var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
        var exception = exceptionHandlerPathFeature?.Error;
        
        var response = new ApiError
        {
            TraceId = context.TraceIdentifier,
            Code = "INTERNAL_ERROR",
            Message = "An unexpected error occurred"
        };
        
        context.Response.StatusCode = exception switch
        {
            ArgumentException => StatusCodes.Status400BadRequest,
            UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
            KeyNotFoundException => StatusCodes.Status404NotFound,
            InvalidOperationException => StatusCodes.Status409Conflict,
            _ => StatusCodes.Status500InternalServerError
        };
        
        if (context.Response.StatusCode == StatusCodes.Status500InternalServerError)
        {
            _logger.LogError(exception, "Unhandled exception");
        }
        
        await context.Response.WriteAsJsonAsync(response);
    });
});

Problem Details (RFC 7807)

// Modern standard error format
var problemDetails = new ProblemDetails
{
    Type = "https://api.example.com/errors/validation-error",
    Title = "Validation Failed",
    Status = StatusCodes.Status400BadRequest,
    Detail = "The order contains invalid items",
    Instance = HttpContext.Request.Path
};

problemDetails.Extensions.Add("errors", new
{
    items = new[] { "Items list is empty" },
    customerId = new[] { "CustomerId is required" }
});

return BadRequest(problemDetails);

Best Practices

  1. Use appropriate status codes: Be consistent
  2. Include error details: But not internals
  3. Provide error codes: For programmatic handling
  4. Log server errors: But don't expose logs
  5. Document errors: Show expected errors in API docs

Related Concepts

  • API documentation
  • Validation frameworks
  • Logging and monitoring
  • Security best practices

Summary

Implement consistent error responses with appropriate status codes, clear messages, and error codes. Use global exception handling for uniform error responses.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours