Pular para o conteúdo principal

Aula 4: Introdução a APIs REST

Esta aula abre o Módulo 2. A aula anterior deu a linguagem; esta dá o contrato — o acordo que mantém integrados um backend e dois clientes escritos em tecnologias diferentes.

O conteúdo é deliberadamente independente de tecnologia: nada aqui é específico de NestJS. Essa separação é proposital, porque o mesmo contrato será implementado no Módulo 2 e consumido nos Módulos 3 (Next.js) e 4 (Flutter). Os conceitos precisam valer nos três lugares.

O que vem depoisOnde
Implementar a API, com validação e documentação automáticaMódulo 2 — NestJS
Consumir a API no cliente webMódulo 3 — Next.js
Consumir a mesma API no cliente mobileMódulo 4 — Flutter

Objetivos

Ao final desta aula, você deve ser capaz de:

  • Definir o que é uma API e situar as APIs de rede em relação às APIs de biblioteca e de sistema operacional.
  • Comparar os principais estilos de API — SOAP, REST, GraphQL, gRPC, WebSocket e WebHooks — e justificar a escolha de um deles para um cenário dado.
  • Descrever o modelo requisição–resposta do HTTP e identificar as partes de uma requisição, de uma resposta e de uma URL.
  • Explicar as restrições do estilo arquitetural REST e o que cada uma implica no projeto de um serviço.
  • Modelar recursos e URIs a partir de um domínio, distinguindo coleções de itens e evitando verbos na URI.
  • Escolher o verbo HTTP e o código de status adequados a cada operação, justificando com base em segurança e idempotência.
  • Projetar representações JSON, formatos de erro, filtros e paginação consistentes para uma API.
  • Comparar estratégias de versionamento e avaliar o nível de maturidade de uma API segundo o modelo de Richardson.
  • Interpretar um contrato descrito em OpenAPI.

O que é uma API

Uma API (Application Programming Interface) é uma interface de comunicação entre componentes de software: um contrato que expõe um conjunto de operações e esconde como elas são implementadas.

A definição é deliberadamente ampla, porque APIs existem em várias escalas:

EscalaExemploComo é acessada
BibliotecaA API de Array do JavaScript, a java.util.ListChamada de método no mesmo processo
Sistema operacionalPOSIX, a API do AndroidChamada de sistema
RedeA API do GitHub, a API do IBGERequisição HTTP entre máquinas

Esta aula trata do terceiro caso — APIs de rede, também chamadas de web APIs. A ideia essencial, porém, é a mesma nos três: quem usa a interface depende do contrato, não da implementação. É isso que permite trocar o banco de dados por trás de um serviço sem que nenhum cliente perceba.

Uma linha do tempo curta

A ideia é bem mais antiga que a web:

QuandoO quê
Anos 1940Maurice Wilkes e David Wheeler organizam, para o computador EDSAC, uma biblioteca de sub-rotinas em fita perfurada, guardada em um arquivo com um "catálogo" de notas descrevendo cada rotina e como usá-la — a ideia de interface documentada, antes do nome
1951O livro The Preparation of Programs for an Electronic Digital Computer, dos mesmos autores, traz a primeira especificação de API publicada
1968O termo application program interface aparece pela primeira vez em um artigo apresentado em uma conferência AFIPS, sobre computação gráfica remota
1990Carl Malamud registra a definição que ficou: "um conjunto de serviços disponíveis para um programador executar determinadas tarefas"
2000Roy Fielding descreve o REST em sua tese de doutorado, contrastando APIs baseadas em rede com as tradicionais APIs baseadas em biblioteca

O ponto da linha do tempo não é a data: é notar que o problema — descrever uma interface para que outra pessoa a use sem ler o código — é o mesmo desde o catálogo de fitas perfuradas do EDSAC. O que mudou foi a distância entre quem chama e quem responde.


Contexto: por que um contrato importa

Nesta disciplina, um único backend atende dois clientes diferentes: uma aplicação web em Next.js e um aplicativo mobile em Flutter. Os dois são escritos em linguagens distintas (TypeScript e Dart), rodam em ambientes distintos e evoluem em ritmos distintos.

O que os mantém integrados não é código compartilhado — é um contrato: um acordo explícito sobre quais endereços existem, o que se pode fazer com cada um, que dados são enviados, que dados retornam e o que significa cada resposta.

Arquitetura da disciplina: os clientes Next.js e Flutter consomem, via HTTP e JSON, um contrato REST implementado por um serviço NestJS apoiado em PostgreSQL

REST (Representational State Transfer) é o estilo arquitetural dominante para esse tipo de contrato. Foi descrito por Roy Fielding em sua tese de doutorado, em 2000, não como uma tecnologia a inventar, mas como uma descrição dos princípios que já faziam a Web funcionar em escala planetária.

REST não é um protocolo nem uma biblioteca

REST é um conjunto de restrições arquiteturais. Não existe "instalar REST". Uma API é RESTful na medida em que respeita essas restrições — e, na prática, a maioria das APIs do mercado respeita algumas e ignora outras. Entender quais e por quê é o objetivo desta aula.


Estilos arquiteturais de API

REST é a escolha desta disciplina, mas não é a única forma de expor um serviço na rede. Conhecer as alternativas serve a dois propósitos: entender por que REST é adequado ao nosso cenário e reconhecer as situações em que ele não é a melhor resposta.

SOAP

Simple Object Access Protocol. Um protocolo — não um estilo — baseado em troca de mensagens XML sobre HTTP ou outros transportes. Cada mensagem tem envelope, cabeçalho e corpo, e o contrato é descrito formalmente em WSDL.

Traz um conjunto de especificações complementares (WS-Security, WS-Trust, WS-Policy) que padronizam assinatura digital, criptografia de trechos da mensagem e transações distribuídas com garantias ACID.

ForçasLimitações
Segurança em nível de mensagem, além do transporteMuito verboso: consome mais banda e processamento
Transações distribuídas com garantias formaisComplexo demais para integrações simples
Contrato formal (WSDL) com geração automática de clienteFerramental pesado e ecossistema em declínio fora do legacy

Onde ainda se encontra: sistemas bancários, gateways de pagamento, integrações governamentais e corporativas antigas. Você provavelmente vai consumir um SOAP em algum momento da carreira — dificilmente vai criar um novo.

REST

Representational State Transfer. Um estilo arquitetural que expõe recursos identificados por URIs e manipulados pelos verbos do próprio HTTP. É o assunto do restante desta aula.

Onde brilha: APIs públicas, operações majoritariamente CRUD, cenários que se beneficiam de cache HTTP e de um vocabulário que qualquer desenvolvedor já conhece.

GraphQL

Uma linguagem de consulta para APIs. Em vez de vários endpoints com formato fixo, existe geralmente um único endpoint e o cliente descreve exatamente os campos que quer receber.

query {
aluno(id: 42) {
nome
matriculas {
disciplina { nome }
nota
}
}
}
ForçasLimitações
O cliente pede só o que precisa — resolve overfetchingCurva de aprendizado maior, dos dois lados
Uma requisição pode reunir dados de vários recursosCache HTTP não funciona: tudo é POST no mesmo endereço
O esquema é fortemente tipado e autodocumentadoConsultas mal formuladas podem sobrecarregar o servidor

Onde brilha: aplicações em que telas diferentes precisam de recortes muito diferentes dos mesmos dados, e em que a equipe de frontend itera mais rápido que a de backend.

gRPC

Google Remote Procedure Call. O cliente chama procedimentos no servidor, como se fossem funções locais. O contrato é declarado em Protocol Buffers (protobuf) e os dados trafegam em formato binário sobre HTTP/2.

service AlunoService {
rpc BuscarAluno (BuscarAlunoRequest) returns (Aluno);
}
ForçasLimitações
Muito rápido: binário compacto e multiplexação do HTTP/2Não é diretamente consumível pelo navegador (exige um proxy)
Contrato formal, com geração de cliente e servidor em várias linguagensPayload binário não é legível — depurar exige ferramenta própria
Suporte nativo a streaming bidirecionalFerramental e curva de adoção próprios

Onde brilha: comunicação entre serviços internos, onde latência importa e o navegador não está envolvido.

WebSocket

Um protocolo que estabelece um canal full-duplex persistente sobre uma única conexão TCP. Depois do handshake inicial (feito por HTTP), os dois lados podem enviar mensagens a qualquer momento.

A diferença essencial em relação a tudo acima: nos outros estilos, o cliente sempre pergunta primeiro. Com WebSocket, o servidor pode falar sem ser consultado.

ForçasLimitações
Comunicação bidirecional em tempo real, com baixa latênciaConexões abertas consomem recursos do servidor e complicam a escala
Elimina o polling repetitivoO estado da conexão precisa ser gerenciado, inclusive em quedas de rede

Onde brilha: chat, notificações ao vivo, edição colaborativa, painéis que se atualizam sozinhos, jogos.

Alternativa mais simples: SSE

Quando o fluxo é só do servidor para o cliente — notificações, progresso de um processamento longo —, Server-Sent Events resolve com muito menos complexidade: é HTTP comum, com uma resposta que não termina.

WebHooks

Uma inversão de papéis: em vez de o cliente perguntar repetidamente "já aconteceu?", ele registra uma URL e o servidor faz uma requisição HTTP para ela quando o evento ocorre.

1. O cliente registra: "quando uma matrícula for confirmada,
chame https://meu-sistema.com/hooks/matricula"
2. O evento acontece no servidor
3. O servidor faz POST na URL registrada, com os dados do evento
4. O cliente processa e responde 2xx
ForçasLimitações
Entrega quase imediata, sem pollingExige que o receptor tenha um endereço público e acessível
Simples: é só um POST HTTPSem confirmação e reenvio, eventos se perdem em falhas
Desacopla os sistemas no tempoPrecisa de assinatura da requisição — senão qualquer um forja um evento

Onde se encontra: notificações de pagamento aprovado, eventos de repositório Git, confirmações de entrega de e-mail.

Comparando

Por trás da variedade de nomes há apenas quatro desenhos de conversa. Reconhecer qual deles um estilo implementa é mais útil do que decorar suas siglas:

Quatro padrões de interação: requisição-resposta usada por SOAP, REST, GraphQL e gRPC; canal persistente bidirecional do WebSocket; fluxo unidirecional do servidor nos Server-Sent Events; e a inversão de papéis dos WebHooks, em que o servidor chama o cliente
SOAPRESTGraphQLgRPCWebSocketWebHooks
TipoProtocoloEstiloLinguagem de consultaFramework RPCProtocoloPadrão de integração
FormatoXMLJSON (em geral)JSONBinário (protobuf)LivreJSON (em geral)
Quem iniciaClienteClienteClienteClienteAmbosServidor
Contrato formalWSDLOpenAPI (opcional)Schema (obrigatório).proto (obrigatório)
Cache HTTPNãoSimDifícilNãoNão
Direto no navegadorSimSimSimNãoSim
Por que REST nesta disciplina

Três razões, nesta ordem:

  1. O cenário pede. Um backend, dois clientes heterogêneos, operações em sua maioria CRUD — exatamente o caso em que REST é mais confortável.
  2. É o vocabulário comum. Verbos, códigos de status e recursos são conhecimento transferível: você vai encontrá-los em qualquer API que consumir, inclusive ao integrar sistemas que usam os outros estilos.
  3. Ensina HTTP de verdade. Aprender REST bem obriga a entender o protocolo — e esse entendimento é pré-requisito para os outros estilos, que rodam sobre ele.

Isso não torna REST superior. Se o estudo de caso do semestre precisar de atualização ao vivo, WebSocket ou SSE entram como complemento — não como substituto.


Parte 1 — HTTP: a base de tudo

REST se apoia no HTTP. Antes das restrições, é preciso conhecer o protocolo.

O modelo requisição–resposta

O HTTP é um protocolo cliente-servidor e sem estado. O cliente envia uma requisição; o servidor devolve uma resposta; a conexão se encerra logicamente ali. O servidor não guarda memória de quem pediu o quê — cada requisição precisa ser autossuficiente.

Anatomia de uma requisição e de uma resposta

Requisição e resposta têm a mesma estrutura: uma linha inicial, cabeçalhos, uma linha em branco e um corpo opcional. Muda apenas o que ocupa a primeira linha — a intenção, de um lado; o resultado, do outro.

Requisição e resposta HTTP com cada parte rotulada: linha inicial com método, caminho e versão; cabeçalhos; linha em branco separadora; e corpo com a representação em JSON

Detalhando a requisição:

POST /api/alunos HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

{
"nome": "Ana Souza",
"email": "ana@exemplo.com",
"curso": "ADS"
}
ParteO que éExemplo acima
MétodoA ação pretendidaPOST
CaminhoIdentifica o recurso alvo/api/alunos
CabeçalhosMetadados da requisiçãoContent-Type, Accept, Authorization
Corpo (opcional)Representação enviadaO objeto JSON

Anatomia de uma resposta

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/alunos/42

{
"id": 42,
"nome": "Ana Souza",
"email": "ana@exemplo.com",
"curso": "ADS",
"criadoEm": "2026-03-10T14:32:00Z"
}
ParteO que é
Código de statusResultado da operação (201 Created)
CabeçalhosMetadados da resposta (Content-Type, Location)
Corpo (opcional)Representação do recurso resultante

Cabeçalhos mais relevantes

CabeçalhoDireçãoFunção
Content-TypeAmbosFormato do corpo (application/json)
AcceptRequisiçãoFormatos que o cliente aceita receber
AuthorizationRequisiçãoCredencial de acesso (Bearer <token>)
LocationRespostaURI do recurso recém-criado
ETag / If-None-MatchResposta / RequisiçãoValidação de cache por versão do recurso
Cache-ControlAmbosPolítica de cache
Accept-LanguageRequisiçãoIdioma preferido

A escolha de formato a partir do Accept chama-se negociação de conteúdo: o mesmo recurso pode ter várias representações (JSON, XML, CSV), e o cliente declara qual prefere.


Parte 2 — As restrições REST

Fielding define seis restrições. Cinco são obrigatórias; a sexta é opcional. Cada uma existe porque habilita alguma coisa — e é por essa consequência que vale a pena lembrá-las.

As cinco restrições obrigatórias do REST e o que cada uma habilita, com o desdobramento da interface uniforme em identificação de recursos, representações, mensagens autodescritivas e HATEOAS, mais a sexta restrição opcional, código sob demanda

1. Cliente–servidor

Separação clara de responsabilidades: o servidor cuida de dados e regras de negócio; o cliente cuida da interface e da experiência do usuário. Os dois evoluem de forma independente, desde que o contrato se mantenha.

É exatamente essa restrição que torna possível a arquitetura da disciplina — um backend, dois clientes.

2. Sem estado (stateless)

Cada requisição contém toda a informação necessária para ser processada. O servidor não guarda contexto entre requisições.

Consequência prática: não há "sessão do usuário" na memória do servidor. Se uma requisição precisa saber quem é o usuário, ela carrega essa informação — em geral um token no cabeçalho Authorization, enviado em toda requisição.

Com estado (evitar em REST)Sem estado (REST)
Servidor guarda sessão em memóriaCliente envia token a cada requisição
Requisições dependem da ordemCada requisição é autossuficiente
Escalar exige sticky sessionsQualquer instância atende qualquer requisição
Reiniciar o servidor derruba usuáriosReiniciar é transparente
Por que isso importa no mobile

O aplicativo Flutter pode ficar horas em segundo plano, perder e recuperar a rede várias vezes. Um serviço sem estado não é afetado por isso: a requisição que chega depois de duas horas é tratada como qualquer outra.

3. Cacheável

As respostas devem indicar, explicitamente ou implicitamente, se podem ser armazenadas em cache e por quanto tempo. Cache bem usado elimina requisições inteiras — o ganho de desempenho mais barato que existe.

4. Interface uniforme

A restrição central, e a que mais distingue REST. Desdobra-se em quatro pontos:

  • Identificação de recursos — cada recurso tem uma URI própria.
  • Manipulação por representações — o cliente não altera o recurso diretamente; envia uma representação dele (por exemplo, um JSON).
  • Mensagens autodescritivas — cada mensagem traz o que é preciso para interpretá-la (método, Content-Type, status).
  • HATEOAS — a resposta pode incluir links indicando as próximas transições possíveis.

5. Sistema em camadas

O cliente não sabe (nem precisa saber) se fala com o servidor de aplicação diretamente ou através de um proxy, gateway, CDN ou balanceador. Isso permite inserir cache, autenticação e limitação de taxa sem alterar o cliente.

6. Código sob demanda (opcional)

O servidor pode enviar código executável ao cliente. Na prática, é a restrição menos usada em APIs — mas é o que a Web faz ao enviar JavaScript para o navegador.


Parte 3 — Recursos e URIs

O que é um recurso

Um recurso é qualquer coisa nomeável do domínio: um aluno, uma disciplina, uma matrícula, um relatório. Recursos são substantivos, não ações.

A URI identifica o recurso; o verbo HTTP diz o que fazer com ele. Essa divisão é o coração do design REST.

Hierarquia de recursos: da raiz para a coleção /alunos, dela para o item /alunos/42 e daí para a subcoleção /alunos/42/matriculas, com uma matriz mostrando como GET, POST e DELETE mudam de sentido conforme o alvo seja a coleção ou o item
/alunos → coleção de alunos
/alunos/42 → o aluno de identificador 42
/alunos/42/matriculas → matrículas do aluno 42
/disciplinas/5/alunos → alunos da disciplina 5

Anatomia de uma URL

Cada parte da URL tem um papel bem definido no projeto da API:

GET https://api.exemplo.com/v1/alunos/42/matriculas?status=ativa&ordenar=-data
└─┬──┘ └───────┬───────┘└─────────┬───────────┘ └────────────┬──────────┘
esquema host caminho query string
PartePapelQuem decide
EsquemaProtocolo e segurança do transporte — em produção, sempre httpsInfraestrutura
HostOnde o serviço está publicadoInfraestrutura
Caminho (path)Identifica o recurso: o quê, e qualProjeto da API
Query stringRefina a consulta: filtro, ordenação, paginação, projeçãoProjeto da API
A regra que resolve a maior parte das dúvidas

Se o parâmetro identifica um recurso, ele vai no caminho. Se modifica o resultado de uma consulta, vai na query string.

  • /alunos/42 — 42 identifica um aluno específico → caminho
  • /alunos?curso=ADS — o curso filtra a coleção → query string

Por isso /alunos/42 e /alunos?id=42 não são equivalentes: o primeiro nomeia um recurso; o segundo é uma busca que por acaso devolve um resultado.

Boas práticas de nomenclatura

PráticaCorretoEvitar
Substantivos, não verbosGET /alunos/42GET /buscarAluno?id=42
Plural para coleções/alunos/aluno
Minúsculas com hífen/itens-avaliacao/itensAvaliacao, /Itens_Avaliacao
Hierarquia para pertencimento/alunos/42/notas/notasDoAluno?id=42
Sem extensão de formato/alunos + Accept/alunos.json
Sem barra final/alunos/alunos/

Coleção e item

A distinção mais importante do modelo: o mesmo verbo significa coisas diferentes conforme aponte para uma coleção ou para um item.

URIGETPOSTPUTDELETE
/alunos
(coleção)
Lista os alunosCria um alunoSubstituição em lote — não recomendadoRemove todos — não recomendado
/alunos/42
(item)
Retorna o aluno 42Erro — o identificador é gerado na criaçãoSubstitui o aluno 42Remove o aluno 42
/alunos/42/matriculas
(subcoleção)
Lista as matrículas do aluno 42Cria uma matrícula para o aluno 42Substituição em lote — não recomendadoRemove todas — não recomendado
/matriculas/1024
(item)
Retorna a matrícula 1024ErroSubstitui a matrícula 1024Remove a matrícula 1024

Três leituras dessa tabela:

  • POST só faz sentido em coleção. Ele diz "acrescente algo aqui", e quem atribui o identificador é o servidor. POST /alunos/42 não tem significado.
  • PUT e DELETE só fazem sentido em item. Aplicados a uma coleção, apagam ou reescrevem tudo — quase sempre um acidente, não uma intenção. Se a operação em lote for mesmo necessária, modele-a como recurso explícito.
  • A matriz é previsível. Conhecendo o padrão, qualquer outro recurso da API se deduz sozinho. É esse o objetivo.

Aninhamento

O aninhamento expressa pertencimento, mas deve ser raso. Uma boa regra: no máximo um nível.

✓ /alunos/42/matriculas claro e útil
✗ /cursos/3/turmas/7/alunos/42/notas/9 frágil e difícil de manter

Quando um recurso tem identidade própria, exponha-o também no nível raiz:

/matriculas/1024 acesso direto
/alunos/42/matriculas listagem no contexto do aluno

E as ações que não são CRUD?

Nem toda operação se encaixa em criar/ler/atualizar/remover. Há três saídas aceitáveis, em ordem de preferência:

  1. Modelar a ação como recurso. "Cancelar uma matrícula" vira criar um cancelamento: POST /matriculas/1024/cancelamento.
  2. Modelar como mudança de estado. PATCH /matriculas/1024 com {"status": "cancelada"}.
  3. Usar um sub-recurso de ação, quando as opções acima ficarem artificiais: POST /matriculas/1024/reenviar-comprovante.
observação

O objetivo não é pureza teórica, e sim previsibilidade. Uma API em que o cliente consegue adivinhar a URI e o verbo é uma API bem projetada.


Parte 4 — Verbos HTTP

O mapeamento com o CRUD

O ponto de partida mais simples é reconhecer que os quatro verbos principais correspondem às quatro operações básicas sobre dados:

Operação (CRUD)VerboSobre o quêExemplo
CreatePOSTColeçãoPOST /alunos
ReadGETColeção ou itemGET /alunos, GET /alunos/42
UpdatePUT (completo) ou PATCH (parcial)ItemPATCH /alunos/42
DeleteDELETEItemDELETE /alunos/42
O mapeamento é um ponto de partida, não uma regra

Pensar só em CRUD leva a modelar a API como se fosse a tabela do banco de dados. Nem toda operação de negócio é um CRUD — "cancelar uma matrícula", "aprovar um pedido", "reenviar um comprovante" não têm verbo próprio. A saída está na Parte 3: modelar a ação como recurso ou como mudança de estado.

A tabela completa

VerboAçãoSeguroIdempotenteCorpo na requisição
GETLerSimSimNão
POSTCriar / processarNãoNãoSim
PUTSubstituir por completoNãoSimSim
PATCHAtualizar parcialmenteNãoNão (em geral)Sim
DELETERemoverNãoSimNão
HEADComo GET, só cabeçalhosSimSimNão
OPTIONSConsultar capacidadesSimSimNão

Duas propriedades organizam essa tabela:

  • Seguro — não altera o estado do servidor. Um GET jamais deve criar, modificar ou remover algo. Navegadores, crawlers e proxies assumem isso e podem repetir requisições seguras livremente.
  • Idempotente — executar n vezes tem o mesmo efeito que executar uma vez. DELETE /alunos/42 repetido continua deixando o aluno 42 inexistente. Já POST /alunos repetido cria dois alunos.
Idempotência e redes instáveis

Essa distinção é prática, não acadêmica. No mobile, uma requisição pode ser enviada, processada pelo servidor e ter a resposta perdida pela rede. O cliente não sabe se deu certo. Se a operação for idempotente, ele pode simplesmente repetir. Se for um POST, repetir pode duplicar um cadastro — daí o uso de chaves de idempotência em operações críticas.

PUT × PATCH

// PUT /alunos/42 — substitui o recurso inteiro
// campos omitidos são removidos ou voltam ao padrão
{ "nome": "Ana Souza", "email": "ana@exemplo.com", "curso": "BCC" }
// PATCH /alunos/42 — altera apenas o que foi enviado
{ "curso": "BCC" }

Na prática, PATCH é o mais usado em formulários de edição, porque o cliente raramente tem o recurso completo em mãos.


Parte 5 — Códigos de status

O código de status é a primeira coisa que o cliente lê. Usá-lo corretamente elimina a necessidade de o cliente interpretar mensagens de texto.

FaixaSignificado
1xxInformativo (raro em APIs)
2xxSucesso
3xxRedirecionamento
4xxErro do cliente — a requisição está errada
5xxErro do servidor — a requisição estava certa, o servidor falhou

Os que você realmente vai usar

CódigoNomeQuando usar
200OKSucesso com corpo (GET, PUT, PATCH)
201CreatedRecurso criado (POST) — inclua o cabeçalho Location
204No ContentSucesso sem corpo (DELETE, às vezes PUT)
400Bad RequestRequisição malformada (JSON inválido, tipo errado)
401UnauthorizedNão autenticado — credencial ausente ou inválida
403ForbiddenAutenticado, mas sem permissão para esta operação
404Not FoundRecurso inexistente
409ConflictConflito de estado (e-mail já cadastrado, edição concorrente)
422Unprocessable ContentSintaxe correta, mas regra de negócio violada
429Too Many RequestsLimite de requisições excedido
500Internal Server ErrorFalha não tratada no servidor
503Service UnavailableServiço temporariamente indisponível
401 não é 403

401 Unauthorized significa "não sei quem você é" — falta autenticação ou o token expirou. 403 Forbidden significa "sei quem você é e você não pode fazer isso". O cliente reage de formas completamente diferentes: no primeiro caso, redireciona para o login; no segundo, mostra uma mensagem de permissão negada.

400 × 422

A distinção é sutil e há divergência na prática. Uma convenção útil: 400 quando o servidor não conseguiu interpretar a requisição (JSON quebrado, campo com tipo incompatível); 422 quando entendeu perfeitamente, mas o conteúdo viola uma regra (e-mail com formato inválido, data de término anterior à de início). Escolha uma convenção e aplique-a de forma consistente em toda a API.

O antipadrão mais comum

// HTTP 200 OK ← errado: houve um erro, mas o status diz sucesso
{ "sucesso": false, "erro": "Aluno não encontrado" }

Retornar 200 para tudo obriga o cliente a inspecionar o corpo de toda resposta e quebra cache, monitoramento, retry automático e toda a infraestrutura que depende do status. Use os códigos.


Parte 6 — Representações

O cliente nunca acessa o recurso: acessa uma representação dele. Em APIs modernas, essa representação é quase sempre JSON.

Convenções de payload

{
"id": 42,
"nome": "Ana Souza",
"email": "ana@exemplo.com",
"curso": "ADS",
"ativo": true,
"criadoEm": "2026-03-10T14:32:00Z",
"notaMedia": 8.5
}
ConvençãoRecomendação
Nomes de camposcamelCase (padrão de fato em JSON) — consistente em toda a API
Datas e horáriosISO 8601 com fuso, em UTC: 2026-03-10T14:32:00Z
Valores monetáriosInteiro em centavos, ou string decimal — nunca float
BooleanosNome afirmativo: ativo, não naoInativo
Ausência de valornull explícito ou omissão do campo — escolha uma regra e mantenha
IdentificadoresNúmeros ou UUIDs; sempre do mesmo tipo em toda a API
Nunca exponha o modelo interno

A entidade do banco de dados e a representação da API são coisas diferentes. Expor a entidade diretamente vaza campos sensíveis (senha, tokenReset), acopla o contrato ao esquema do banco e torna qualquer refatoração uma quebra de compatibilidade. Por isso existem os DTOs (Data Transfer Objects): tipos específicos para entrada e saída da API. Este é um dos primeiros padrões que implementaremos com NestJS.

Envelope ou não?

// resposta direta
[ { "id": 1, "nome": "Ana" }, { "id": 2, "nome": "Bruno" } ]
// com envelope — necessário quando há metadados
{
"dados": [ { "id": 1, "nome": "Ana" } ],
"paginacao": { "pagina": 1, "tamanho": 20, "total": 137 }
}

Listas paginadas praticamente exigem envelope. O importante é que a API seja consistente: não alterne entre os dois formatos sem critério.


Parte 7 — Consultas: filtros, ordenação e paginação

Tudo o que refina uma consulta pertence à query string, não ao caminho.

GET /alunos?curso=ADS&ativo=true filtragem
GET /alunos?ordenar=nome,-notaMedia ordenação (o "-" indica decrescente)
GET /alunos?campos=id,nome projeção de campos
GET /alunos?busca=souza busca textual
GET /alunos?pagina=2&tamanho=20 paginação

Duas estratégias de paginação

A diferença entre elas só fica evidente quando algo é inserido entre duas requisições — que é o caso normal em produção, não a exceção:

Comparação entre paginação por offset e por cursor: com offset, um registro inserido antes da página 1 desloca a lista e faz um item se repetir na página 2; com cursor, a segunda página continua de onde a primeira parou e nada se repete nem se perde

Na figura o cursor aparece como o próprio nome, para deixar a ideia visível. Na prática ele é opaco — uma string codificada, como no exemplo abaixo — justamente para que o cliente não dependa de como o servidor o constrói.

GET /alunos?pagina=3&tamanho=20
{
"dados": [ /* ... */ ],
"paginacao": { "pagina": 3, "tamanho": 20, "total": 137, "totalPaginas": 7 }
}

Vantagens: simples de implementar e de exibir ("página 3 de 7"); permite saltar para qualquer página.

Limitações: fica lenta em tabelas muito grandes (o banco precisa percorrer os registros pulados) e pode repetir ou omitir itens se dados forem inseridos entre duas requisições.

dica

Defina sempre um tamanho padrão e um máximo para a paginação. Uma API sem limite superior é um convite a uma requisição que devolve a tabela inteira e derruba o serviço.


Parte 8 — Tratamento de erros

Um erro é uma resposta como outra qualquer — e precisa ser tão bem projetado quanto o caso de sucesso, porque é dele que o cliente depende para orientar o usuário.

Um bom corpo de erro responde a quatro perguntas: o que aconteceu, por que, onde e o que fazer.

A RFC 9457 — Problem Details for HTTP APIs padroniza esse formato:

// HTTP 422 Unprocessable Content
// Content-Type: application/problem+json
{
"type": "https://api.exemplo.com/erros/validacao",
"title": "Falha de validação",
"status": 422,
"detail": "Um ou mais campos são inválidos.",
"instance": "/api/alunos",
"erros": [
{ "campo": "email", "mensagem": "Formato de e-mail inválido" },
{ "campo": "nome", "mensagem": "Deve ter ao menos 3 caracteres" }
]
}
CampoFunção
typeURI que identifica o tipo do problema (estável, documentada)
titleResumo legível, constante para aquele type
statusCódigo HTTP, repetido no corpo
detailExplicação específica desta ocorrência
instanceIdentifica a ocorrência (URI ou identificador de rastreio)

Regras de ouro:

  • Erros de validação devem listar todos os campos inválidos de uma vez, não o primeiro.
  • A mensagem deve ser útil ao desenvolvedor e segura para o usuário final.
  • Nunca devolva stack trace, consulta SQL ou nome de tabela em produção — é informação para o atacante.
  • Inclua um identificador de correlação para casar o erro do cliente com o log do servidor.

Parte 9 — Evolução e versionamento

Toda API muda. A questão é como mudar sem quebrar clientes já publicados — e no mobile isso é crítico: versões antigas do aplicativo continuam instaladas por meses.

Mudanças compatíveis e incompatíveis

Compatível (não quebra)Incompatível (quebra)
Acrescentar um campo opcional na respostaRemover ou renomear um campo
Acrescentar um endpoint novoMudar o tipo de um campo
Acrescentar um parâmetro opcionalTornar obrigatório um parâmetro opcional
Acrescentar um valor a um enum de saídaMudar o significado de um campo
Relaxar uma validaçãoEndurecer uma validação
O cliente deve tolerar campos desconhecidos

Um cliente bem escrito ignora campos que não conhece. Isso é o que permite ao servidor acrescentar informação sem coordenar um lançamento simultâneo dos aplicativos.

Estratégias de versionamento

EstratégiaExemploPrósContras
Na URI/api/v1/alunosVisível, trivial de testar e rotearURI deixa de identificar unicamente o recurso
Em cabeçalhoAccept: application/vnd.exemplo.v1+jsonPreserva a URI do recursoDifícil de testar no navegador; menos evidente
Em query string/api/alunos?versao=1SimplesMistura versão com filtros
Sem versãoSó mudanças compatíveisMáxima simplicidadeExige disciplina rigorosa

Na prática, versionamento na URI é o mais comum, e é o que adotaremos: é explícito, fácil de depurar e adequado ao porte do estudo de caso.


Parte 10 — Segurança e cabeçalhos operacionais

Estes temas serão aprofundados nas aulas de autenticação. Aqui, o essencial para ler e projetar um contrato.

Autenticação e autorização

Como REST é sem estado, a credencial viaja em cada requisição:

GET /api/alunos/42 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

O padrão mais usado é o JWT (JSON Web Token): um token assinado que carrega a identidade e as permissões do usuário. O servidor valida a assinatura sem consultar nenhuma sessão em memória — coerente com a restrição stateless.

Distinção fundamental:

  • Autenticaçãoquem é você. Falha → 401.
  • Autorizaçãoo que você pode fazer. Falha → 403.

CORS

Por padrão, o navegador bloqueia requisições de uma página em um domínio para uma API em outro domínio. Para permitir, o servidor precisa declarar explicitamente as origens autorizadas por meio dos cabeçalhos Access-Control-Allow-*.

CORS é do navegador, não da API

O aplicativo Flutter e ferramentas como curl não são afetados por CORS. Quando algo funciona no curl mas falha no navegador com erro de CORS, o problema está na configuração do servidor, não no código do cliente web. É um dos tropeços mais frequentes ao integrar frontend e backend pela primeira vez.

Cache e concorrência

GET /api/alunos/42
If-None-Match: "a1b2c3"

HTTP/1.1 304 Not Modified ← nada mudou; o cliente usa a cópia local

O mesmo mecanismo evita sobrescritas acidentais: o cliente envia If-Match: "a1b2c3" em um PUT, e o servidor responde 412 Precondition Failed se o recurso tiver mudado nesse intervalo. É o controle de concorrência otimista aplicado ao HTTP.

Limitação de taxa

APIs públicas limitam requisições por cliente e informam o estado do limite em cabeçalhos, respondendo 429 Too Many Requests quando ele é excedido.


Parte 11 — O contrato documentado: OpenAPI

REST é uma especificação? Não. É um estilo arquitetural — um conjunto de restrições. Não há documento normativo que diga se sua API "passa" ou "não passa".

Então não existe nada formal? Existe, mas em outra camada: especificações para descrever uma API concreta construída no estilo REST. A mais adotada é a OpenAPI, antes conhecida como Swagger Specification.

A distinção importa: REST diz como projetar; OpenAPI diz como documentar o que foi projetado, em um formato que máquinas leem. Um contrato só é plenamente útil quando está escrito assim.

A OpenAPI Specification é hoje o padrão de fato:

openapi: 3.1.0
info:
title: API Acadêmica
version: 1.0.0
paths:
/alunos/{id}:
get:
summary: Retorna um aluno pelo identificador
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200':
description: Aluno encontrado
content:
application/json:
schema: { $ref: '#/components/schemas/Aluno' }
'404':
description: Aluno não encontrado
components:
schemas:
Aluno:
type: object
required: [id, nome, email]
properties:
id: { type: integer }
nome: { type: string }
email: { type: string, format: email }

A partir desse documento é possível gerar automaticamente documentação navegável (Swagger UI), clientes tipados para TypeScript e Dart, dados de teste e verificações de contrato. No Módulo 2, o NestJS gerará esse documento a partir dos próprios decorators e DTOs do código — o contrato deixa de ser um arquivo que envelhece à parte e passa a acompanhar a implementação.


Parte 12 — Quão RESTful é uma API?

O modelo de maturidade de Richardson classifica APIs em quatro níveis:

Escada de maturidade de Richardson: nível 0 usa o HTTP como túnel com um endpoint só, nível 1 identifica recursos mas mantém o verbo na URI, nível 2 usa verbos e códigos de status corretamente, e nível 3 acrescenta HATEOAS; a API da disciplina opera no nível 2
NívelCaracterísticaExemplo
0HTTP como túnel; um único endpoint, tudo via POSTPOST /api com {"acao": "buscarAluno", "id": 42}
1Recursos identificados por URIs, mas um só verboPOST /alunos/buscar, POST /alunos/criar
2Verbos e status codes usados corretamenteGET /alunos/42200; DELETE /alunos/42204
3HATEOAS: respostas trazem links para as próximas transiçõesresposta com _links

A maioria das APIs de mercado — e a que construiremos nesta disciplina — opera no nível 2. O nível 3 é elegante, mas raramente compensa o custo em APIs de porte pequeno e médio, e é sobre isso que Fielding reclama quando diz que quase nada do que se chama REST é realmente REST.

Forças e limitações do REST

Agora que o estilo está detalhado, vale voltar ao balanço com mais precisão do que era possível no início da página:

ForçasO que a sustenta
SimplicidadeO vocabulário é o do próprio HTTP: recursos, verbos, códigos de status. Nada a aprender além do protocolo
CacheabilidadeGET é seguro e idempotente, então navegadores, proxies e CDNs podem cachear respostas sem saber nada do domínio
EscalabilidadeSendo stateless, qualquer instância atende qualquer requisição — escalar é adicionar máquinas
Independência de plataformaQualquer cliente que fale HTTP consome a API: navegador, Flutter, curl, outro serviço
VisibilidadeRequisições e respostas são texto legível, o que torna depuração e monitoramento triviais
LimitaçõesComo se manifestaMitigação
OverfetchingO endpoint devolve mais campos do que a tela precisa, gastando banda — problema real no mobileProjeção de campos (?campos=id,nome) ou endpoints de resumo
UnderfetchingA tela precisa de dados de três recursos e faz três requisições em sequênciaEndpoints compostos para casos frequentes, ou aninhamento (/alunos/42/matriculas)
Verbos limitadosNem toda operação de negócio é criar/ler/atualizar/removerModelar a ação como recurso ou como mudança de estado (Parte 3)
Custo de designEscolhas ruins de recurso ou verbo são caras de corrigir depois de publicadasEspecificar em OpenAPI antes de implementar
VersionamentoClientes móveis antigos continuam chamando o contrato antigoVersionar na URI e priorizar mudanças compatíveis (Parte 9)
Estado da aplicaçãoREST é stateless, mas a aplicação tem estado (carrinho, sessão, rascunho)O estado vira recurso no servidor, ou fica no cliente
Overfetching e underfetching são o argumento do GraphQL

Esses dois problemas são exatamente o que o GraphQL se propõe a resolver, ao deixar o cliente descrever a consulta. O preço é perder o cache HTTP — que é uma das maiores forças do REST. Não existe escolha gratuita: existe a escolha adequada ao problema.


Um contrato de exemplo

Reunindo tudo, um recurso completo de um sistema acadêmico:

OperaçãoRequisiçãoSucessoErros possíveis
Listar alunosGET /api/v1/alunos?curso=ADS&pagina=1&tamanho=20200 + lista paginada401
Obter alunoGET /api/v1/alunos/42200 + aluno401, 404
Criar alunoPOST /api/v1/alunos + corpo201 + Location + aluno400, 401, 409, 422
Atualizar alunoPATCH /api/v1/alunos/42 + corpo parcial200 + aluno400, 401, 403, 404, 422
Substituir alunoPUT /api/v1/alunos/42 + corpo completo200 + aluno400, 401, 403, 404, 422
Remover alunoDELETE /api/v1/alunos/42204 sem corpo401, 403, 404, 409
Matrículas do alunoGET /api/v1/alunos/42/matriculas200 + lista401, 404

Note que a tabela inteira é previsível: conhecendo o padrão, um desenvolvedor consegue deduzir a chamada para qualquer outro recurso da API. Essa previsibilidade é o produto final de um bom design REST.


Erros comuns

ErroConsequência
Verbos na URI (/criarAluno)Abandona a interface uniforme; a API vira RPC sobre HTTP
POST em um item (POST /alunos/42)Não tem significado: quem gera o identificador na criação é o servidor
DELETE ou PUT em coleçãoApaga ou reescreve tudo; quase sempre um acidente
Identificador na query em vez do caminho (/alunos?id=42)A URL deixa de nomear o recurso, e o cache deixa de funcionar por recurso
GET que altera dadosQuebra cache, prefetch e a garantia de segurança do método
Sempre responder 200O cliente precisa inspecionar o corpo de toda resposta
Expor a entidade do bancoVaza dados sensíveis e acopla contrato ao esquema
Listagem sem paginaçãoFunciona em desenvolvimento, derruba o serviço em produção
Mensagem de erro sem estruturaO cliente não consegue destacar o campo inválido no formulário
Confundir 401 com 403O cliente redireciona para login quando deveria negar permissão
Mudança incompatível sem versãoQuebra aplicativos móveis já instalados

Exercícios (Checkpoints)

  1. Escolha o estilo mais adequado para cada cenário, justificando em uma frase: (a) um painel que mostra a fila de atendimento se atualizando sozinha; (b) a integração de um sistema de matrículas com o gateway de pagamento da universidade, que precisa avisar quando o boleto for compensado; (c) a comunicação entre dois microsserviços internos que trocam milhares de mensagens por segundo; (d) a API pública que o aplicativo Flutter e o site em Next.js vão consumir.

  2. Explique por que a restrição stateless facilita a escala horizontal de um serviço, e descreva uma consequência prática dela no projeto da autenticação.

  3. Sobre verbos:

    a. Classifique cada operação como segura, idempotente, ambas ou nenhuma, justificando: GET /alunos; DELETE /alunos/42; POST /alunos; PUT /alunos/42; PATCH /alunos/42 com {"tentativas": 3}; PATCH /alunos/42 com {"tentativas": "+1"}.

    b. Justifique, com base na matriz recurso × verbo: por que POST /alunos/42 não faz sentido, e por que DELETE /alunos é tecnicamente válido mas quase sempre um erro de projeto?

  4. Modele as URIs e os verbos de um domínio de biblioteca com os recursos livro, exemplar, usuário e empréstimo, cobrindo: listar livros com filtro por autor, registrar um empréstimo, devolver um exemplar e consultar o histórico de um usuário. Para cada consulta, indique o que vai no caminho e o que vai na query string, e justifique.

  5. Indique o código de status mais adequado para cada situação e explique: (a) criação bem-sucedida de um aluno; (b) DELETE de um aluno inexistente; (c) tentativa de cadastro com e-mail já usado; (d) token expirado; (e) aluno tentando acessar o recurso de outro aluno; (f) campo dataNascimento com valor "31/02/2010"; (g) falha de conexão com o banco de dados.

  6. Critique o contrato abaixo, listando pelo menos quatro problemas e propondo a versão corrigida:

    POST /api/getAlunosPorCurso
    { "curso": "ADS", "senhaAdmin": "1234" }

    → HTTP 200
    { "ok": false, "msg": "curso nao encontrado" }
  7. Compare paginação por offset e por cursor: descreva um cenário do aplicativo Flutter em que a segunda é claramente superior e explique por quê.

  8. Projete o corpo de erro (seguindo a RFC 9457) para a tentativa de criar um aluno com nome vazio e e-mail inválido simultaneamente, e explique por que os dois erros devem vir na mesma resposta.

  9. Analise uma API pública de sua escolha (por exemplo, a API do GitHub ou a API do IBGE) e classifique-a no modelo de maturidade de Richardson, apontando as evidências que sustentam sua classificação.

  10. Identifique um caso de overfetching e um de underfetching em uma tela que você já usou (um app de banco, uma rede social, o portal do aluno). Para cada um, proponha uma mitigação dentro do estilo REST — sem recorrer ao GraphQL.


Referências

Principais

Aprofundamento

Outros estilos de API