API REST: O que É e Como Funciona | Python Brasil
Guia completo sobre API REST: princípios REST, HATEOAS, Richardson Maturity Model, autenticação JWT, paginação, versionamento e OpenAPI.
O que é uma API REST?
Uma API REST (Representational State Transfer) é um estilo de arquitetura para comunicação entre sistemas via protocolo HTTP. O termo foi definido por Roy Fielding em sua tese de doutorado em 2000, descrevendo um conjunto de restrições que, quando aplicadas, resultam em sistemas escaláveis, simples e interoperáveis.
APIs REST são o padrão mais usado para comunicação entre frontend e backend, aplicações mobile, microsserviços e integrações entre sistemas.
Os 6 Princípios REST
1. Cliente-Servidor
A interface de usuário é separada do armazenamento de dados. O cliente não precisa conhecer os detalhes do servidor, e o servidor não conhece o estado da interface do cliente.
2. Stateless (Sem Estado)
Cada requisição deve conter todas as informações necessárias para ser processada. O servidor não guarda estado do cliente entre requisições. Isso facilita escalonamento horizontal, pois qualquer servidor pode atender qualquer requisição.
3. Cacheable (Cacheável)
Respostas devem indicar se podem ser armazenadas em cache. Isso reduz carga no servidor e melhora a performance do cliente.
4. Interface Uniforme
Quatro sub-restrições: identificação de recursos via URI, manipulação via representações, mensagens autodescritivas e HATEOAS.
5. Sistema em Camadas
O cliente não precisa saber se está falando diretamente com o servidor ou com um intermediário (proxy, load balancer, CDN).
6. Code-On-Demand (Opcional)
Servidores podem enviar código executável ao cliente (como JavaScript), mas isso é opcional e raramente aplicado em APIs REST modernas.
Richardson Maturity Model
O modelo de maturidade de Richardson classifica APIs REST em 4 níveis:
- Nível 0: Uso do HTTP apenas como transporte (RPC via POST)
- Nível 1: Recursos (URIs diferentes para cada entidade)
- Nível 2: Métodos HTTP corretos (GET, POST, PUT, DELETE) e status codes
- Nível 3: HATEOAS — respostas incluem links para ações possíveis
A maioria das “APIs REST” na prática atinge o nível 2. O nível 3 é o REST “maduro” segundo Fielding, mas é raramente implementado.
HATEOAS
HATEOAS (Hypermedia as the Engine of Application State) significa que a resposta da API inclui links para as próximas ações possíveis:
{
"id": 1,
"titulo": "Aprendendo Python",
"status": "ativo",
"_links": {
"self": { "href": "/artigos/1" },
"editar": { "href": "/artigos/1", "method": "PUT" },
"deletar": { "href": "/artigos/1", "method": "DELETE" },
"comentarios": { "href": "/artigos/1/comentarios" }
}
}
Métodos HTTP e Status Codes
Métodos HTTP
- GET: busca dados, sem efeitos colaterais (idempotente)
- POST: cria um novo recurso
- PUT: substitui um recurso completo (idempotente)
- PATCH: atualiza parcialmente um recurso
- DELETE: remove um recurso (idempotente)
Status Codes Mais Comuns
- 200 OK: sucesso genérico
- 201 Created: recurso criado com sucesso
- 204 No Content: sucesso sem corpo de resposta (ex: DELETE)
- 400 Bad Request: dados inválidos enviados pelo cliente
- 401 Unauthorized: não autenticado
- 403 Forbidden: autenticado, mas sem permissão
- 404 Not Found: recurso não encontrado
- 409 Conflict: conflito (ex: e-mail já cadastrado)
- 422 Unprocessable Entity: dados sintaticamente corretos, mas semanticamente inválidos
- 429 Too Many Requests: rate limit atingido
- 500 Internal Server Error: erro no servidor
Exemplo Completo com FastAPI
from fastapi import FastAPI, HTTPException, Query, Header
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
app = FastAPI(title="API de Artigos", version="1.0.0")
class ArtigoCreate(BaseModel):
titulo: str
conteudo: str
categoria: str
class ArtigoResponse(BaseModel):
id: int
titulo: str
categoria: str
criado_em: datetime
visualizacoes: int
class RespostaPaginada(BaseModel):
dados: list[ArtigoResponse]
total: int
pagina: int
tamanho: int
@app.get("/api/v1/artigos", response_model=RespostaPaginada)
async def listar_artigos(
pagina: int = Query(default=1, ge=1, description="Número da página"),
tamanho: int = Query(default=20, ge=1, le=100, description="Itens por página"),
categoria: Optional[str] = Query(default=None),
busca: Optional[str] = Query(default=None),
):
# Simulação de paginação
artigos = buscar_artigos(pagina, tamanho, categoria, busca)
return RespostaPaginada(
dados=artigos,
total=200,
pagina=pagina,
tamanho=tamanho
)
@app.post("/api/v1/artigos", response_model=ArtigoResponse, status_code=201)
async def criar_artigo(artigo: ArtigoCreate):
novo = salvar_artigo(artigo)
return novo
Autenticação
API Key
from fastapi import Security, HTTPException, status
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X-API-Key")
async def verificar_api_key(api_key: str = Security(api_key_header)):
if api_key != CHAVE_VALIDA:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Chave de API inválida"
)
return api_key
JWT (JSON Web Token)
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
async def usuario_atual(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
email = payload.get("sub")
if not email:
raise HTTPException(status_code=401, detail="Token inválido")
return email
except JWTError:
raise HTTPException(status_code=401, detail="Token inválido ou expirado")
Paginação
Existem três estratégias principais de paginação:
Baseada em página (offset): simples, mas pode ter resultados inconsistentes em dados que mudam rapidamente.
GET /artigos?pagina=2&tamanho=20
Baseada em cursor: mais eficiente para grandes volumes de dados.
GET /artigos?cursor=eyJpZCI6MTAwfQ&tamanho=20
Baseada em keyset: usa o valor do último item para buscar o próximo lote.
GET /artigos?ultimo_id=100&tamanho=20
Versionamento
Estratégias de versionamento de APIs:
URI: mais comum e explícito.
GET /api/v1/artigos
GET /api/v2/artigos
Header: mais “puro” em termos REST, mas menos visível.
Accept: application/vnd.minhapi.v2+json
Query param: simples, mas mistura versão com dados.
GET /artigos?version=2
Rate Limiting
Rate limiting protege a API de abusos. A resposta deve incluir headers informativos:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1710000000
Retry-After: 60
Implementação simples com Redis (usando slowapi):
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/artigos")
@limiter.limit("100/minute")
async def listar_artigos(request: Request):
return {"artigos": []}
OpenAPI e Swagger
O OpenAPI (antigo Swagger) é a especificação padrão para documentar APIs REST. O FastAPI gera o schema OpenAPI automaticamente:
- Acesse
/docspara a interface Swagger UI interativa - Acesse
/redocpara a documentação ReDoc - Acesse
/openapi.jsonpara o schema em JSON
Você pode enriquecer a documentação:
@app.get(
"/artigos/{artigo_id}",
response_model=ArtigoResponse,
summary="Buscar artigo por ID",
description="Retorna os detalhes completos de um artigo.",
responses={
404: {"description": "Artigo não encontrado"},
200: {"description": "Artigo encontrado com sucesso"}
}
)
async def buscar_artigo(artigo_id: int):
...
REST vs GraphQL
| Aspecto | REST | GraphQL |
|---|---|---|
| Estrutura | Múltiplos endpoints | Um único endpoint |
| Dados | Resposta fixa | Cliente define os campos |
| Over-fetching | Comum | Resolvido |
| Under-fetching | Requer múltiplas chamadas | Resolvido |
| Cache | Simples (HTTP cache) | Mais complexo |
| Adoção | Universal | Crescente |
REST é mais simples e universal. GraphQL brilha quando há muitos tipos de clientes (mobile, web, TV) com necessidades diferentes de dados.
Boas Práticas
- Use substantivos no plural nos endpoints:
/usuarios, não/getUsuario - Retorne status codes corretos — 201 para criação, 204 para deleção sem corpo
- Inclua paginação em todas as rotas que retornam listas
- Use versionamento desde o início:
/api/v1/ - Documente com OpenAPI e mantenha a documentação atualizada
- Implemente rate limiting para proteger a API
- Use HTTPS sempre em produção
Erros Comuns
- Usar POST para todas as operações (nível 0 de maturidade)
- Retornar sempre 200 mesmo em erros
- Colocar verbos na URL:
/getArtigos,/deletarUsuario - Não implementar paginação em listas que podem crescer