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

Boas Práticas de Design de API

Paginação, filtros, CORS e um punhado de convenções que tornam uma API previsível — as escolhas de design que separam uma API "que funciona" de uma "boa de usar".

1. Paginação — nunca devolva tudo de uma vez

Um endpoint de listagem sem paginação funciona bem com 50 registros e vira um problema sério com 50 mil. O padrão mais comum é paginação por página/tamanho:

C# endpoint paginado
public record PaginaResultado<T>(List<T> Itens, int Pagina, int TamanhoPagina, int Total);

[HttpGet]
public async Task<ActionResult<PaginaResultado<ClienteDto>>> Listar(
    [FromQuery] int pagina = 1,
    [FromQuery] int tamanhoPagina = 20)
{
    if (tamanhoPagina > 100) tamanhoPagina = 100; // limite máximo, protege contra abuso

    var (itens, total) = await _repositorio.ListarPaginadoAsync(pagina, tamanhoPagina);
    return Ok(new PaginaResultado<ClienteDto>(
        itens.Select(ClienteMapper.ParaDto).ToList(), pagina, tamanhoPagina, total));
}
SQL a procedure por trás — OFFSET/FETCH
CREATE OR ALTER PROCEDURE dbo.usp_ClienteListarPaginado
    @Pagina        INT = 1,
    @TamanhoPagina INT = 20
AS
BEGIN
    SET NOCOUNT ON;

    SELECT Id, Nome, Email
    FROM dbo.Cliente
    ORDER BY Nome
    OFFSET (@Pagina - 1) * @TamanhoPagina ROWS
    FETCH NEXT @TamanhoPagina ROWS ONLY;

    SELECT COUNT(*) AS Total FROM dbo.Cliente;
END;
Dica

OFFSET/FETCH é a forma padrão do T-SQL de paginar diretamente no banco — muito mais eficiente que trazer todas as linhas para o C# e paginar em memória com LINQ, especialmente numa tabela grande.

2. Filtros e ordenação como query string

C# filtros consistentes
// GET /api/clientes?nome=ana&ativo=true&ordenarPor=nome&pagina=2
[HttpGet]
public async Task<ActionResult> Listar(
    [FromQuery] string? nome,
    [FromQuery] bool? ativo,
    [FromQuery] string ordenarPor = "nome",
    [FromQuery] int pagina = 1)
{
    ...
}

Isso mapeia diretamente para os parâmetros opcionais das procedures de listagem que você já escreveu no Módulo 2 (capítulo 3) e o SQL dinâmico de ordenação (Módulo 2, capítulo 10) — a API é, em boa parte, uma tradução direta desses conceitos para query string.

3. CORS — permitindo que um front-end separado consuma a API

Se a API é consumida por um front-end rodando em outro domínio/porta (um SPA React/Vue, por exemplo), o navegador bloqueia a chamada por padrão, a menos que o servidor autorize explicitamente via CORS (Cross-Origin Resource Sharing):

C# Program.cs — habilitando CORS
builder.Services.AddCors(opcoes =>
{
    opcoes.AddPolicy("FrontEnd", politica =>
        politica.WithOrigins("https://meusite.com")
                .AllowAnyMethod()
                .AllowAnyHeader());
});

var app = builder.Build();
app.UseCors("FrontEnd");
Atenção

Evite AllowAnyOrigin() em produção, especialmente combinado com AllowCredentials() — isso permite que qualquer site na internet faça requisições autenticadas à sua API em nome do usuário. Liste os domínios exatos que devem ter acesso.

4. Convenções que tornam uma API previsível

  • Nomes de recursos sempre no plural: /clientes, não /cliente.
  • camelCase consistente no JSON (o padrão já configurado, capítulo 3).
  • Datas em formato ISO 8601 (2026-01-15T10:30:00Z) — sem ambiguidade de fuso ou formato regional.
  • Erros sempre no mesmo formato (ProblemDetails, capítulo 6), em qualquer endpoint.
  • Filtros e paginação com nomes de parâmetro consistentes em toda a API (sempre pagina/tamanhoPagina, nunca page num endpoint e pagina em outro).

5. Rate limiting — protegendo contra abuso

C# limitando requisições por cliente
builder.Services.AddRateLimiter(opcoes =>
{
    opcoes.AddFixedWindowLimiter("padrao", janela =>
    {
        janela.PermitLimit = 100;
        janela.Window = TimeSpan.FromMinutes(1);
    });
});

app.UseRateLimiter();

6. Onde isso te leva

Com boas práticas de design cobertas, o último capítulo deste módulo junta tudo — paginação, autenticação, tratamento de erro, documentação — numa API completa sobre o sistema de Pedidos do Módulo 2.

📌 Resumo do capítulo

  • Sempre pagine listagens — nunca devolva um conjunto de dados ilimitado de uma vez.
  • OFFSET/FETCH pagina direto no SQL Server, mais eficiente que paginar em memória.
  • CORS precisa de configuração explícita para liberar um front-end de outro domínio — evite AllowAnyOrigin() em produção.
  • Convenções consistentes (plural, camelCase, ISO 8601, nomes de parâmetro) tornam a API previsível de usar.
  • Rate limiting protege a API contra uso excessivo ou abusivo.

✏️ Praticando

  1. Implemente usp_ClienteListarPaginado e o endpoint correspondente com paginação.
  2. Habilite CORS para um domínio específico e teste consumindo a API a partir de uma página HTML simples servida de outra porta.
  3. Adicione rate limiting ao endpoint de login, limitando a 5 tentativas por minuto.