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 depois | Onde |
|---|---|
| Implementar a API, com validação e documentação automática | Módulo 2 — NestJS |
| Consumir a API no cliente web | Módulo 3 — Next.js |
| Consumir a mesma API no cliente mobile | Mó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:
| Escala | Exemplo | Como é acessada |
|---|---|---|
| Biblioteca | A API de Array do JavaScript, a java.util.List | Chamada de método no mesmo processo |
| Sistema operacional | POSIX, a API do Android | Chamada de sistema |
| Rede | A API do GitHub, a API do IBGE | Requisiçã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:
| Quando | O quê |
|---|---|
| Anos 1940 | Maurice 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 |
| 1951 | O livro The Preparation of Programs for an Electronic Digital Computer, dos mesmos autores, traz a primeira especificação de API publicada |
| 1968 | O termo application program interface aparece pela primeira vez em um artigo apresentado em uma conferência AFIPS, sobre computação gráfica remota |
| 1990 | Carl Malamud registra a definição que ficou: "um conjunto de serviços disponíveis para um programador executar determinadas tarefas" |
| 2000 | Roy 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.
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 é 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ças | Limitações |
|---|---|
| Segurança em nível de mensagem, além do transporte | Muito verboso: consome mais banda e processamento |
| Transações distribuídas com garantias formais | Complexo demais para integrações simples |
| Contrato formal (WSDL) com geração automática de cliente | Ferramental 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ças | Limitações |
|---|---|
| O cliente pede só o que precisa — resolve overfetching | Curva de aprendizado maior, dos dois lados |
| Uma requisição pode reunir dados de vários recursos | Cache HTTP não funciona: tudo é POST no mesmo endereço |
| O esquema é fortemente tipado e autodocumentado | Consultas 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ças | Limitações |
|---|---|
| Muito rápido: binário compacto e multiplexação do HTTP/2 | Não é diretamente consumível pelo navegador (exige um proxy) |
| Contrato formal, com geração de cliente e servidor em várias linguagens | Payload binário não é legível — depurar exige ferramenta própria |
| Suporte nativo a streaming bidirecional | Ferramental 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ças | Limitações |
|---|---|
| Comunicação bidirecional em tempo real, com baixa latência | Conexões abertas consomem recursos do servidor e complicam a escala |
| Elimina o polling repetitivo | O 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.
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ças | Limitações |
|---|---|
| Entrega quase imediata, sem polling | Exige que o receptor tenha um endereço público e acessível |
Simples: é só um POST HTTP | Sem confirmação e reenvio, eventos se perdem em falhas |
| Desacopla os sistemas no tempo | Precisa 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:
| SOAP | REST | GraphQL | gRPC | WebSocket | WebHooks | |
|---|---|---|---|---|---|---|
| Tipo | Protocolo | Estilo | Linguagem de consulta | Framework RPC | Protocolo | Padrão de integração |
| Formato | XML | JSON (em geral) | JSON | Binário (protobuf) | Livre | JSON (em geral) |
| Quem inicia | Cliente | Cliente | Cliente | Cliente | Ambos | Servidor |
| Contrato formal | WSDL | OpenAPI (opcional) | Schema (obrigatório) | .proto (obrigatório) | — | — |
| Cache HTTP | Não | Sim | Difícil | Não | Não | — |
| Direto no navegador | Sim | Sim | Sim | Não | Sim | — |
Três razões, nesta ordem:
- 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.
- É 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.
- 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.
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"
}
| Parte | O que é | Exemplo acima |
|---|---|---|
| Método | A ação pretendida | POST |
| Caminho | Identifica o recurso alvo | /api/alunos |
| Cabeçalhos | Metadados da requisição | Content-Type, Accept, Authorization |
| Corpo (opcional) | Representação enviada | O 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"
}
| Parte | O que é |
|---|---|
| Código de status | Resultado da operação (201 Created) |
| Cabeçalhos | Metadados da resposta (Content-Type, Location) |
| Corpo (opcional) | Representação do recurso resultante |
Cabeçalhos mais relevantes
| Cabeçalho | Direção | Função |
|---|---|---|
Content-Type | Ambos | Formato do corpo (application/json) |
Accept | Requisição | Formatos que o cliente aceita receber |
Authorization | Requisição | Credencial de acesso (Bearer <token>) |
Location | Resposta | URI do recurso recém-criado |
ETag / If-None-Match | Resposta / Requisição | Validação de cache por versão do recurso |
Cache-Control | Ambos | Política de cache |
Accept-Language | Requisição | Idioma 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.
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ória | Cliente envia token a cada requisição |
| Requisições dependem da ordem | Cada requisição é autossuficiente |
| Escalar exige sticky sessions | Qualquer instância atende qualquer requisição |
| Reiniciar o servidor derruba usuários | Reiniciar é transparente |
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.
/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
| Parte | Papel | Quem decide |
|---|---|---|
| Esquema | Protocolo e segurança do transporte — em produção, sempre https | Infraestrutura |
| Host | Onde o serviço está publicado | Infraestrutura |
| Caminho (path) | Identifica o recurso: o quê, e qual | Projeto da API |
| Query string | Refina a consulta: filtro, ordenação, paginação, projeção | Projeto da API |
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ática | Correto | Evitar |
|---|---|---|
| Substantivos, não verbos | GET /alunos/42 | GET /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.
| URI | GET | POST | PUT | DELETE |
|---|---|---|---|---|
/alunos(coleção) | Lista os alunos | Cria um aluno | Substituição em lote — não recomendado | Remove todos — não recomendado |
/alunos/42(item) | Retorna o aluno 42 | Erro — o identificador é gerado na criação | Substitui o aluno 42 | Remove o aluno 42 |
/alunos/42/matriculas(subcoleção) | Lista as matrículas do aluno 42 | Cria uma matrícula para o aluno 42 | Substituição em lote — não recomendado | Remove todas — não recomendado |
/matriculas/1024(item) | Retorna a matrícula 1024 | Erro | Substitui a matrícula 1024 | Remove a matrícula 1024 |
Três leituras dessa tabela:
POSTsó faz sentido em coleção. Ele diz "acrescente algo aqui", e quem atribui o identificador é o servidor.POST /alunos/42não tem significado.PUTeDELETEsó 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:
- Modelar a ação como recurso. "Cancelar uma matrícula" vira criar um
cancelamento:
POST /matriculas/1024/cancelamento. - Modelar como mudança de estado.
PATCH /matriculas/1024com{"status": "cancelada"}. - Usar um sub-recurso de ação, quando as opções acima ficarem artificiais:
POST /matriculas/1024/reenviar-comprovante.
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) | Verbo | Sobre o quê | Exemplo |
|---|---|---|---|
| Create | POST | Coleção | POST /alunos |
| Read | GET | Coleção ou item | GET /alunos, GET /alunos/42 |
| Update | PUT (completo) ou PATCH (parcial) | Item | PATCH /alunos/42 |
| Delete | DELETE | Item | DELETE /alunos/42 |
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
| Verbo | Ação | Seguro | Idempotente | Corpo na requisição |
|---|---|---|---|---|
GET | Ler | Sim | Sim | Não |
POST | Criar / processar | Não | Não | Sim |
PUT | Substituir por completo | Não | Sim | Sim |
PATCH | Atualizar parcialmente | Não | Não (em geral) | Sim |
DELETE | Remover | Não | Sim | Não |
HEAD | Como GET, só cabeçalhos | Sim | Sim | Não |
OPTIONS | Consultar capacidades | Sim | Sim | Não |
Duas propriedades organizam essa tabela:
- Seguro — não altera o estado do servidor. Um
GETjamais 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/42repetido continua deixando o aluno 42 inexistente. JáPOST /alunosrepetido cria dois alunos.
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.
| Faixa | Significado |
|---|---|
| 1xx | Informativo (raro em APIs) |
| 2xx | Sucesso |
| 3xx | Redirecionamento |
| 4xx | Erro do cliente — a requisição está errada |
| 5xx | Erro do servidor — a requisição estava certa, o servidor falhou |
Os que você realmente vai usar
| Código | Nome | Quando usar |
|---|---|---|
200 | OK | Sucesso com corpo (GET, PUT, PATCH) |
201 | Created | Recurso criado (POST) — inclua o cabeçalho Location |
204 | No Content | Sucesso sem corpo (DELETE, às vezes PUT) |
400 | Bad Request | Requisição malformada (JSON inválido, tipo errado) |
401 | Unauthorized | Não autenticado — credencial ausente ou inválida |
403 | Forbidden | Autenticado, mas sem permissão para esta operação |
404 | Not Found | Recurso inexistente |
409 | Conflict | Conflito de estado (e-mail já cadastrado, edição concorrente) |
422 | Unprocessable Content | Sintaxe correta, mas regra de negócio violada |
429 | Too Many Requests | Limite de requisições excedido |
500 | Internal Server Error | Falha não tratada no servidor |
503 | Service Unavailable | Serviço temporariamente indisponível |
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.
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ção | Recomendação |
|---|---|
| Nomes de campos | camelCase (padrão de fato em JSON) — consistente em toda a API |
| Datas e horários | ISO 8601 com fuso, em UTC: 2026-03-10T14:32:00Z |
| Valores monetários | Inteiro em centavos, ou string decimal — nunca float |
| Booleanos | Nome afirmativo: ativo, não naoInativo |
| Ausência de valor | null explícito ou omissão do campo — escolha uma regra e mantenha |
| Identificadores | Números ou UUIDs; sempre do mesmo tipo em toda a API |
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:
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.
- Offset / página
- Cursor
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.
GET /alunos?tamanho=20&depoisDe=eyJpZCI6NDJ9
{
"dados": [ /* ... */ ],
"proximoCursor": "eyJpZCI6NjJ9"
}
Vantagens: desempenho constante independentemente da profundidade; estável diante de inserções — ideal para listas com rolagem infinita, o padrão no mobile.
Limitações: não permite saltar para uma página arbitrária nem exibir o total facilmente.
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" }
]
}
| Campo | Função |
|---|---|
type | URI que identifica o tipo do problema (estável, documentada) |
title | Resumo legível, constante para aquele type |
status | Código HTTP, repetido no corpo |
detail | Explicação específica desta ocorrência |
instance | Identifica 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 resposta | Remover ou renomear um campo |
| Acrescentar um endpoint novo | Mudar o tipo de um campo |
| Acrescentar um parâmetro opcional | Tornar obrigatório um parâmetro opcional |
| Acrescentar um valor a um enum de saída | Mudar o significado de um campo |
| Relaxar uma validação | Endurecer uma validação |
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égia | Exemplo | Prós | Contras |
|---|---|---|---|
| Na URI | /api/v1/alunos | Visível, trivial de testar e rotear | URI deixa de identificar unicamente o recurso |
| Em cabeçalho | Accept: application/vnd.exemplo.v1+json | Preserva a URI do recurso | Difícil de testar no navegador; menos evidente |
| Em query string | /api/alunos?versao=1 | Simples | Mistura versão com filtros |
| Sem versão | Só mudanças compatíveis | Máxima simplicidade | Exige 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ção — quem é você. Falha →
401. - Autorização — o 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-*.
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:
| Nível | Característica | Exemplo |
|---|---|---|
| 0 | HTTP como túnel; um único endpoint, tudo via POST | POST /api com {"acao": "buscarAluno", "id": 42} |
| 1 | Recursos identificados por URIs, mas um só verbo | POST /alunos/buscar, POST /alunos/criar |
| 2 | Verbos e status codes usados corretamente | GET /alunos/42 → 200; DELETE /alunos/42 → 204 |
| 3 | HATEOAS: respostas trazem links para as próximas transições | resposta 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ças | O que a sustenta |
|---|---|
| Simplicidade | O vocabulário é o do próprio HTTP: recursos, verbos, códigos de status. Nada a aprender além do protocolo |
| Cacheabilidade | GET é seguro e idempotente, então navegadores, proxies e CDNs podem cachear respostas sem saber nada do domínio |
| Escalabilidade | Sendo stateless, qualquer instância atende qualquer requisição — escalar é adicionar máquinas |
| Independência de plataforma | Qualquer cliente que fale HTTP consome a API: navegador, Flutter, curl, outro serviço |
| Visibilidade | Requisições e respostas são texto legível, o que torna depuração e monitoramento triviais |
| Limitações | Como se manifesta | Mitigação |
|---|---|---|
| Overfetching | O endpoint devolve mais campos do que a tela precisa, gastando banda — problema real no mobile | Projeção de campos (?campos=id,nome) ou endpoints de resumo |
| Underfetching | A tela precisa de dados de três recursos e faz três requisições em sequência | Endpoints compostos para casos frequentes, ou aninhamento (/alunos/42/matriculas) |
| Verbos limitados | Nem toda operação de negócio é criar/ler/atualizar/remover | Modelar a ação como recurso ou como mudança de estado (Parte 3) |
| Custo de design | Escolhas ruins de recurso ou verbo são caras de corrigir depois de publicadas | Especificar em OpenAPI antes de implementar |
| Versionamento | Clientes móveis antigos continuam chamando o contrato antigo | Versionar na URI e priorizar mudanças compatíveis (Parte 9) |
| Estado da aplicação | REST é stateless, mas a aplicação tem estado (carrinho, sessão, rascunho) | O estado vira recurso no servidor, ou fica no cliente |
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ção | Requisição | Sucesso | Erros possíveis |
|---|---|---|---|
| Listar alunos | GET /api/v1/alunos?curso=ADS&pagina=1&tamanho=20 | 200 + lista paginada | 401 |
| Obter aluno | GET /api/v1/alunos/42 | 200 + aluno | 401, 404 |
| Criar aluno | POST /api/v1/alunos + corpo | 201 + Location + aluno | 400, 401, 409, 422 |
| Atualizar aluno | PATCH /api/v1/alunos/42 + corpo parcial | 200 + aluno | 400, 401, 403, 404, 422 |
| Substituir aluno | PUT /api/v1/alunos/42 + corpo completo | 200 + aluno | 400, 401, 403, 404, 422 |
| Remover aluno | DELETE /api/v1/alunos/42 | 204 sem corpo | 401, 403, 404, 409 |
| Matrículas do aluno | GET /api/v1/alunos/42/matriculas | 200 + lista | 401, 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
| Erro | Consequê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ção | Apaga 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 dados | Quebra cache, prefetch e a garantia de segurança do método |
Sempre responder 200 | O cliente precisa inspecionar o corpo de toda resposta |
| Expor a entidade do banco | Vaza dados sensíveis e acopla contrato ao esquema |
| Listagem sem paginação | Funciona em desenvolvimento, derruba o serviço em produção |
| Mensagem de erro sem estrutura | O cliente não consegue destacar o campo inválido no formulário |
Confundir 401 com 403 | O cliente redireciona para login quando deveria negar permissão |
| Mudança incompatível sem versão | Quebra aplicativos móveis já instalados |
Exercícios (Checkpoints)
-
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.
-
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.
-
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/42com{"tentativas": 3};PATCH /alunos/42com{"tentativas": "+1"}.b. Justifique, com base na matriz recurso × verbo: por que
POST /alunos/42não faz sentido, e por queDELETE /alunosé tecnicamente válido mas quase sempre um erro de projeto? -
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.
-
Indique o código de status mais adequado para cada situação e explique: (a) criação bem-sucedida de um aluno; (b)
DELETEde 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) campodataNascimentocom valor"31/02/2010"; (g) falha de conexão com o banco de dados. -
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" } -
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ê.
-
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.
-
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.
-
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
- MDN — Visão geral do HTTP — protocolo, métodos e cabeçalhos
- MDN — Códigos de status HTTP — referência completa
- RFC 9110 — HTTP Semantics — especificação vigente do HTTP
- RFC 9457 — Problem Details for HTTP APIs — formato padronizado de erros
- OpenAPI Specification — descrição formal de contratos REST
Aprofundamento
- FIELDING, Roy T. Architectural Styles and the Design of Network-based Software Architectures. Tese de doutorado, University of California, Irvine, 2000. — Capítulo 5 define REST.
- Richardson Maturity Model — Martin Fowler
- MDN — CORS — compartilhamento de recursos entre origens
- JSON Web Tokens — Introdução — estrutura e uso de JWT
- NestJS — Controllers — como estes conceitos são implementados no Módulo 2
Outros estilos de API
- SOAP 1.2 — W3C Recommendation — especificação oficial
- GraphQL — guia de introdução oficial
- gRPC — o que é e quando usar
- MDN — WebSockets — protocolo e API do navegador
- MDN — Server-Sent Events — alternativa unidirecional ao WebSocket
- REST vs. GraphQL vs. gRPC — comparação prática (AWS)