HTTP — Como a Web Funciona
O HTTP (HyperText Transfer Protocol) é o protocolo de comunicação da web. É baseado em requisição e resposta — o cliente pede, o servidor responde. Como backend Python, você cria o servidor que processa essas requisições.
Fluxo de uma Requisição HTTP
Cliente (app, curl, browser)
↓ 1. DNS resolve "api.exemplo.com" → IP
↓ 2. TCP handshake (conexão)
↓ 3. Envia requisição HTTP
Servidor (sua API Python)
↓ 4. Processa a requisição
↓ 5. Consulta banco, aplica lógica
↓ 6. Retorna resposta HTTP
Cliente
↓ 7. Processa a resposta (JSON, HTML...)
Métodos HTTP
| Método | Ação | Tem body? | Idempotente? | Uso típico |
| GET | Buscar/ler dados | Não | Sim | Listar usuários, buscar produto |
| POST | Criar novo recurso | Sim | Não | Criar usuário, fazer login |
| PUT | Atualizar completo | Sim | Sim | Substituir recurso inteiro |
| PATCH | Atualizar parcial | Sim | Não | Atualizar só o nome de um usuário |
| DELETE | Remover recurso | Não | Sim | Deletar usuário, remover item |
⚠ Idempotência
Requisição
idempotente = chamar N vezes tem o mesmo efeito que chamar 1 vez. GET busca sempre o mesmo dado. DELETE deleta uma vez, chamar de novo não muda nada. POST cria um novo registro a cada chamada — não é idempotente.
Códigos de Status HTTP
| Faixa | Significado | Exemplos importantes |
| 2xx — Sucesso | Requisição processada com sucesso | 200 OK, 201 Created, 204 No Content |
| 3xx — Redirecionamento | Cliente deve seguir outro endereço | 301 Moved, 302 Found, 304 Not Modified |
| 4xx — Erro do Cliente | Problema na requisição enviada | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable |
| 5xx — Erro do Servidor | Problema no servidor ao processar | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
Status Codes mais usados em APIs
200 — sucesso com body de resposta (GET, PUT, PATCH)
201 — recurso criado com sucesso (POST)
204 — sucesso sem body (DELETE)
400 — dados inválidos enviados pelo cliente
401 — não autenticado (precisa de token/login)
403 — autenticado mas sem permissão
404 — recurso não encontrado
500 — erro interno — nunca exponha detalhes ao cliente!
Headers HTTP
# Headers comuns em APIs REST
Content-Type: application/json # formato do body enviado
Accept: application/json # formato esperado na resposta
Authorization: Bearer eyJhbG... # token de autenticação JWT
X-Request-ID: abc123 # ID único da requisição (rastreamento)
Cache-Control: no-cache # controle de cache
# Headers de CORS (Cross-Origin Resource Sharing)
Access-Control-Allow-Origin: * # permite qualquer origem
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Exercício 1: Qual a diferença entre 401 e 403?
→ 401: não autenticado (sem token / token inválido). 403: autenticado mas sem permissão.
401 = "Quem é você?" → faça login. 403 = "Sei quem você é, mas não pode fazer isso" → usuário comum tentando acessar rota de admin.
REST e Design de APIs
REST (Representational State Transfer) é um estilo arquitetural para APIs web. Define princípios de como organizar rotas, usar métodos HTTP e estruturar respostas.
Princípios REST
Stateless (sem estado)
Cada requisição contém toda informação necessária. O servidor não guarda estado da sessão. O cliente envia o token em cada requisição.
Recursos e URIs
Recursos são identificados por URIs. Use substantivos no plural, não verbos.
Bom: /usuarios, /produtos/42
Ruim: /getUsuario, /deletarProduto
Representações
Recursos são transferidos em formatos como JSON (padrão atual), XML (legado). O cliente especifica o formato com o header Accept.
Interface Uniforme
Mesmos métodos HTTP para todos os recursos. GET sempre lê, POST sempre cria, etc. Consistência facilita o uso da API.
Design de Rotas REST
| Operação | Método | Rota | Body | Resposta |
| Listar todos | GET | /usuarios | — | 200 + lista |
| Buscar um | GET | /usuarios/42 | — | 200 + objeto |
| Criar | POST | /usuarios | JSON | 201 + criado |
| Atualizar tudo | PUT | /usuarios/42 | JSON completo | 200 + atualizado |
| Atualizar parcial | PATCH | /usuarios/42 | JSON parcial | 200 + atualizado |
| Deletar | DELETE | /usuarios/42 | — | 204 |
JSON — Formato padrão de APIs
// Resposta típica de uma API REST
{
"id": 42,
"nome": "Ana Silva",
"email": "ana@exemplo.com",
"criado_em": "2024-01-15T10:30:00Z",
"ativo": true,
"endereco": {
"cidade": "Campo Grande",
"uf": "MS"
},
"tags": ["admin", "premium"]
}
// Resposta de erro padronizada
{
"erro": "USUARIO_NAO_ENCONTRADO",
"mensagem": "Usuário com ID 42 não existe",
"status": 404,
"timestamp": "2024-01-15T10:30:00Z"
}
⚠ Versionamento de API
Sempre versione sua API para não quebrar clientes existentes ao evoluir. Ex:
/api/v1/usuarios, /api/v2/usuarios. Quando mudar a estrutura de resposta, crie v2 mantendo v1 funcionando por um período de deprecação.
Exercício 2: Por que usar /produtos e não /getProdutos na rota REST?
→ O método HTTP já define a ação. A URI identifica o recurso, não a operação.
GET /produtos = listar. POST /produtos = criar. O verbo na URL é redundante e vai contra o princípio REST de interface uniforme.
FastAPI — Backend Python Moderno
FastAPI é o framework Python mais moderno para APIs REST. É rápido (async nativo), tem validação automática com Pydantic e gera documentação Swagger automaticamente.
pip install fastapi uvicorn[standard]
API Completa com FastAPI
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import Optional
app = FastAPI(title="API de Usuários", version="1.0.0")
# Schema de validação (Pydantic)
class UsuarioCriar(BaseModel):
nome: str
email: str
idade: Optional[int] = None
class UsuarioResposta(BaseModel):
id: int
nome: str
email: str
# Banco de dados simulado
db: dict[int, dict] = {}
proximo_id = 1
# GET /usuarios — listar todos
@app.get("/usuarios", response_model=list[UsuarioResposta])
def listar_usuarios():
return list(db.values())
# GET /usuarios/{id} — buscar um
@app.get("/usuarios/{usuario_id}", response_model=UsuarioResposta)
def buscar_usuario(usuario_id: int):
if usuario_id not in db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Usuário {usuario_id} não encontrado"
)
return db[usuario_id]
# POST /usuarios — criar
@app.post("/usuarios", response_model=UsuarioResposta,
status_code=status.HTTP_201_CREATED)
def criar_usuario(usuario: UsuarioCriar):
global proximo_id
novo = {"id": proximo_id, **usuario.dict()}
db[proximo_id] = novo
proximo_id += 1
return novo
# DELETE /usuarios/{id}
@app.delete("/usuarios/{usuario_id}", status_code=204)
def deletar_usuario(usuario_id: int):
if usuario_id not in db:
raise HTTPException(status_code=404, detail="Não encontrado")
del db[usuario_id]
# Rodar: uvicorn main:app --reload
# Docs: http://localhost:8000/docs (Swagger automático!)
Query Params, Path Params e Body
# Path param — parte da URL: /produtos/42
@app.get("/produtos/{produto_id}")
def buscar_produto(produto_id: int): # FastAPI valida e converte o tipo
...
# Query param — após ?: /produtos?pagina=2&limite=10
@app.get("/produtos")
def listar_produtos(pagina: int = 1, limite: int = 10, busca: str = ""):
...
# Body — JSON no corpo da requisição (POST/PUT/PATCH)
@app.post("/produtos")
def criar_produto(produto: ProdutoSchema): # Pydantic valida automaticamente
...
Middleware e Dependências
from fastapi import Depends
from fastapi.middleware.cors import CORSMiddleware
# CORS — permitir frontend acessar a API
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
# Dependency Injection — reutilizar lógica entre rotas
def obter_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/usuarios")
def listar(db=Depends(obter_db)):
return db.query(Usuario).all()
Exercício 3: Por que FastAPI gera documentação automática?
→ Usa os type hints do Python e schemas Pydantic para gerar OpenAPI spec automaticamente.
Ao declarar tipos nos parâmetros e usar BaseModel, FastAPI sabe exatamente o formato esperado e gera o Swagger UI em /docs sem nenhuma configuração extra.
Autenticação e Segurança em APIs
JWT — JSON Web Token
JWT é o padrão de autenticação mais usado em APIs REST. O servidor gera um token assinado após o login — o cliente envia esse token em cada requisição.
Estrutura do JWT: header.payload.signature
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "42", "nome": "Ana", "exp": 1716000000}
Signature: HMACSHA256(base64(header)+"."+base64(payload), secret)
# No header da requisição:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.abc123
pip install python-jose[cryptography] passlib[bcrypt]
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
SECRET_KEY = "sua-chave-secreta-muito-longa"
ALGORITHM = "HS256"
EXPIRA_EM = 30 # minutos
pwd_context = CryptContext(schemes=["bcrypt"])
def hash_senha(senha: str) -> str:
return pwd_context.hash(senha)
def verificar_senha(senha: str, hash: str) -> bool:
return pwd_context.verify(senha, hash)
def criar_token(dados: dict) -> str:
dados["exp"] = datetime.utcnow() + timedelta(minutes=EXPIRA_EM)
return jwt.encode(dados, SECRET_KEY, algorithm=ALGORITHM)
def verificar_token(token: str) -> dict:
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTError:
raise HTTPException(status_code=401, detail="Token inválido")
Fluxo de Autenticação JWT
1. POST /auth/login {email, senha}
↓ servidor verifica senha (bcrypt)
↓ gera JWT com user_id e exp
← 200 {access_token: "eyJ..."}
2. GET /usuarios/perfil
→ Header: Authorization: Bearer eyJ...
↓ servidor valida JWT (assinatura + expiração)
↓ extrai user_id do payload
← 200 {id: 42, nome: "Ana"}
Boas Práticas de Segurança em APIs
| Problema | Solução |
| Senhas em texto puro | Sempre hash com bcrypt — nunca armazene senha pura |
| Token sem expiração | JWT com exp curto (15-30min) + refresh token |
| SQL Injection | Usar ORM (SQLAlchemy) ou queries parametrizadas |
| Dados sensíveis na URL | Dados sensíveis no body (POST), não em query params |
| CORS aberto demais | allow_origins com domínios específicos em produção |
| Erros com stack trace | Em produção, retornar mensagem genérica ao cliente |
| Rate limit inexistente | Limitar requisições por IP (slowapi no FastAPI) |
SQLAlchemy — ORM para Python
pip install sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import DeclarativeBase, Session
engine = create_engine("sqlite:///./banco.db")
class Base(DeclarativeBase): pass
class Usuario(Base):
__tablename__ = "usuarios"
id = Column(Integer, primary_key=True)
nome = Column(String(100), nullable=False)
email = Column(String(100), unique=True)
Base.metadata.create_all(engine) # cria tabelas
# CRUD com sessão
with Session(engine) as session:
# Create
usuario = Usuario(nome="Ana", email="ana@ex.com")
session.add(usuario)
session.commit()
# Read
ana = session.query(Usuario).filter_by(email="ana@ex.com").first()
# Update
ana.nome = "Ana Silva"
session.commit()
# Delete
session.delete(ana)
session.commit()
⚠ ORM vs SQL puro
ORM (SQLAlchemy): mais seguro, mais produtivo, independente de banco. SQL puro: mais performance em queries complexas. Recomendação: use ORM por padrão, SQL puro só quando necessário por performance.
Exercício 4: Por que nunca armazenar senhas em texto puro?
→ Se o banco vazar, todas as senhas são expostas. Com bcrypt, o atacante só vê o hash — irreversível.
bcrypt é lento por design (fator de custo configurável) — dificulta ataques de força bruta. Sempre: hash = bcrypt(senha). Login: bcrypt.verify(senha_digitada, hash_armazenado).
📝 Cola — Web e HTTP para Backend Python
Métodos HTTP × CRUD
| CRUD | Método HTTP | Status sucesso | Exemplo |
| Read (listar) | GET | 200 | GET /usuarios |
| Read (um) | GET | 200 | GET /usuarios/42 |
| Create | POST | 201 | POST /usuarios |
| Update (total) | PUT | 200 | PUT /usuarios/42 |
| Update (parcial) | PATCH | 200 | PATCH /usuarios/42 |
| Delete | DELETE | 204 | DELETE /usuarios/42 |
Status Codes Essenciais
| Code | Significado | Quando usar |
| 200 | OK | GET, PUT, PATCH com sucesso |
| 201 | Created | POST com sucesso |
| 204 | No Content | DELETE com sucesso |
| 400 | Bad Request | Dados inválidos no body |
| 401 | Unauthorized | Sem token / token inválido |
| 403 | Forbidden | Sem permissão |
| 404 | Not Found | Recurso não existe |
| 422 | Unprocessable | Validação falhou (FastAPI padrão) |
| 500 | Server Error | Bug no servidor |
⭐ Regras de Ouro
Regra
URI = substantivo plural. Método = verbo.
GET /usuarios, nunca /getUsuarios
Regra
401 = não autenticado. 403 = autenticado mas
sem permissão
Regra
Senhas sempre com
bcrypt — nunca texto puro, nunca MD5/SHA1
Regra
JWT no header:
Authorization: Bearer token — nunca na URL
Regra
REST é
stateless — servidor não guarda sessão. Token em cada requisição
Regra
POST cria = 201. DELETE sem body = 204. GET/PUT bem sucedido = 200
Regra
ORM protege de SQL Injection. Nunca concatenar string em query SQL
Regra
FastAPI: /docs = Swagger automático. Uvicorn serve a API.
Pydantic valida automaticamente