Módulo 4 · APIs e sua Arquitetura — Capítulo 06

Tratamento Global de Erros e Middlewares

Garantindo que nenhuma exceção não tratada vaze um stack trace, uma connection string ou qualquer outro detalhe interno para quem consome a API.

1. O problema de deixar exceções escaparem

Sem tratamento centralizado, uma exceção não capturada em qualquer endpoint derruba a resposta com um 500 cujo corpo pode incluir, dependendo da configuração, detalhes internos do servidor — uma informação valiosa demais para expor a quem consome a API, e uma experiência ruim para quem só queria saber "algo deu errado".

2. Middleware de tratamento global de exceções

C# Program.cs — usando o middleware embutido
var app = builder.Build();

app.UseExceptionHandler(app =>
{
    app.Run(async context =>
    {
        var feature = context.Features.Get<IExceptionHandlerFeature>();
        var excecao = feature?.Error;

        var (status, mensagem) = MapearExcecaoParaResposta(excecao);

        context.Response.StatusCode = status;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsJsonAsync(new { erro = mensagem });
    });
});
C# traduzindo exceções específicas em respostas HTTP corretas
static (int Status, string Mensagem) MapearExcecaoParaResposta(Exception? excecao) => excecao switch
{
    SqlException { Number: 50010 } => (StatusCodes.Status409Conflict, "Estoque insuficiente"),
    SqlException { Number: 50001 } => (StatusCodes.Status400BadRequest, "Valor de transferência inválido"),
    ArgumentException                => (StatusCodes.Status400BadRequest, excecao.Message),
    InvalidOperationException        => (StatusCodes.Status409Conflict, excecao.Message),
    _                                 => (StatusCodes.Status500InternalServerError, "Erro interno do servidor"),
};

Repare o uso do pattern matching de switch expression com propriedade (SqlException {'{'} Number: 50010 {'}'}) — visto de relance no capítulo 3 do Módulo 1, e agora aplicado exatamente para traduzir os códigos de erro customizados que você definiu nas procedures do Módulo 2 em respostas HTTP com significado.

Regra de ouro

Só o default (qualquer coisa não mapeada explicitamente) devolve a mensagem genérica "Erro interno do servidor" com 500. Toda exceção de negócio conhecida (estoque insuficiente, validação, conflito) deveria ser mapeada para um código de status específico com uma mensagem que faça sentido para quem consome a API.

3. Problem Details — o formato padrão de erro em REST

ProblemDetails é um formato padronizado (RFC 7807) para descrever erros HTTP, já suportado nativamente pelo ASP.NET Core:

C# devolvendo ProblemDetails
await context.Response.WriteAsJsonAsync(new ProblemDetails
{
    Status = status,
    Title  = mensagem,
    Type   = $"https://httpstatuses.com/{status}",
    Instance = context.Request.Path
});

// gera algo como:
// {
//   "status": 409,
//   "title": "Estoque insuficiente",
//   "type": "https://httpstatuses.com/409",
//   "instance": "/api/pedidos/checkout"
// }
Dica

Quando o projeto usa [ApiController] (capítulo 2), falhas de validação de ModelState já são devolvidas automaticamente nesse formato — vale usar o mesmo padrão para os erros que você trata manualmente, para manter a API consistente.

4. Logando o erro antes de responder

Traduzir a exceção para uma resposta amigável não substitui registrá-la — o padrão do capítulo 6 do Módulo 2 (log em tabela) se aplica aqui também, ou mais comumente, um provedor de log estruturado:

C# registrando antes de responder
app.UseExceptionHandler(appErro =>
{
    appErro.Run(async context =>
    {
        var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
        var excecao = context.Features.Get<IExceptionHandlerFeature>()?.Error;

        logger.LogError(excecao, "Erro não tratado em {Path}", context.Request.Path);

        // ... resto do tratamento e resposta
    });
});

5. Onde isso te leva

Com erros tratados de forma consistente, o próximo (e penúltimo) capítulo deste módulo cobre boas práticas gerais de design de API — paginação, filtros, e convenções que tornam uma API previsível de usar.

📌 Resumo do capítulo

  • UseExceptionHandler centraliza o tratamento de qualquer exceção não capturada nos endpoints.
  • Mapeie exceções específicas (incluindo códigos customizados de SqlException) para códigos de status com significado — reserve 500 só para o realmente inesperado.
  • ProblemDetails é o formato padrão (RFC 7807) para descrever erros HTTP de forma consistente.
  • Sempre logue o erro original antes de traduzi-lo para a resposta amigável.

✏️ Praticando

  1. Implemente o middleware de tratamento global de exceções, mapeando pelo menos três tipos diferentes de exceção para códigos de status distintos.
  2. Adicione logging antes da resposta, usando ILogger<Program>.
  3. Teste chamando o endpoint de checkout (Módulo 3, capítulo 8) com estoque insuficiente através da API, e confirme o código 409 com uma mensagem clara.