FastAPI é o framework web que mais cresceu no ecossistema Python nos últimos anos — e por boas razões. Ele combina alta performance, tipagem estática, validação automática de dados, documentação interativa gerada automaticamente e suporte nativo a async/await. Se Flask é o canivete suíço, FastAPI é o bisturi: preciso, moderno e construído sobre os padrões mais atuais da linguagem.
Instalação
pip install fastapi uvicorn[standard]
uvicorn é o servidor ASGI que executa a aplicação FastAPI. ASGI (Asynchronous Server Gateway Interface) é o sucessor do WSGI, com suporte nativo a async.
Primeiro Endpoint
# main.py
from fastapi import FastAPI
app = FastAPI(
title="API Escolar",
description="Sistema de gestão de alunos",
version="1.0.0"
)
@app.get("/")
def raiz():
return {"mensagem": "API Escolar online"}
@app.get("/saude")
async def verificar_saude():
return {"status": "ok"}
uvicorn main:app --reload
Acesse: - http://localhost:8000 — API - http://localhost:8000/docs — Swagger UI interativo gerado automaticamente - http://localhost:8000/redoc — ReDoc alternativo
Pydantic: Validação e Serialização
FastAPI usa Pydantic para validar dados automaticamente. Você define modelos com tipos e o FastAPI cuida do resto:
from pydantic import BaseModel, EmailStr, Field, field_validator
from typing import Optional
from datetime import datetime
class AlunoBase(BaseModel):
nome: str = Field(..., min_length=2, max_length=100,
description="Nome completo do aluno")
email: EmailStr = Field(..., description="E-mail válido")
nota: float = Field(0.0, ge=0.0, le=10.0,
description="Nota de 0 a 10")
class AlunoCriar(AlunoBase):
senha: str = Field(..., min_length=6)
class AlunoAtualizar(BaseModel):
nome: Optional[str] = Field(None, min_length=2)
email: Optional[EmailStr] = None
nota: Optional[float] = Field(None, ge=0.0, le=10.0)
class AlunoResposta(AlunoBase):
id: int
ativo: bool
criado_em: datetime
model_config = {"from_attributes": True} # permite criar de ORM
class RespostaLista(BaseModel):
total: int
alunos: list[AlunoResposta]
# Validators customizados
class AlunoComValidacao(AlunoBase):
cpf: str
@field_validator("cpf")
@classmethod
def validar_cpf(cls, v):
apenas_numeros = "".join(c for c in v if c.isdigit())
if len(apenas_numeros) != 11:
raise ValueError("CPF deve ter 11 dígitos.")
return v
CRUD Completo com FastAPI
from fastapi import FastAPI, HTTPException, status, Query, Path
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
from datetime import datetime
app = FastAPI(title="API Escolar", version="1.0.0")
# Banco em memória
_alunos: dict = {}
_proximo_id: int = 1
class AlunoBase(BaseModel):
nome: str = Field(..., min_length=2, max_length=100)
email: EmailStr
nota: float = Field(0.0, ge=0.0, le=10.0)
class AlunoCriar(AlunoBase):
pass
class AlunoAtualizar(BaseModel):
nome: Optional[str] = Field(None, min_length=2)
email: Optional[EmailStr] = None
nota: Optional[float] = Field(None, ge=0.0, le=10.0)
class AlunoResposta(AlunoBase):
id: int
aprovado: bool
criado_em: str
# GET — listar com filtros e paginação
@app.get(
"/alunos",
response_model=dict,
summary="Listar alunos",
tags=["Alunos"]
)
async def listar_alunos(
nota_min: float = Query(0.0, ge=0.0, le=10.0, description="Nota mínima"),
nota_max: float = Query(10.0, ge=0.0, le=10.0, description="Nota máxima"),
pagina: int = Query(1, ge=1, description="Número da página"),
tamanho: int = Query(10, ge=1, le=100, description="Itens por página"),
):
alunos = [
a for a in _alunos.values()
if nota_min <= a["nota"] <= nota_max
]
inicio = (pagina - 1) * tamanho
fim = inicio + tamanho
return {
"total": len(alunos),
"pagina": pagina,
"tamanho": tamanho,
"alunos": alunos[inicio:fim]
}
# GET — buscar por ID
@app.get(
"/alunos/{aluno_id}",
response_model=AlunoResposta,
summary="Buscar aluno por ID",
tags=["Alunos"]
)
async def buscar_aluno(
aluno_id: int = Path(..., ge=1, description="ID do aluno")
):
aluno = _alunos.get(aluno_id)
if not aluno:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Aluno {aluno_id} não encontrado."
)
return aluno
# POST — criar
@app.post(
"/alunos",
response_model=AlunoResposta,
status_code=status.HTTP_201_CREATED,
summary="Criar aluno",
tags=["Alunos"]
)
async def criar_aluno(dados: AlunoCriar):
global _proximo_id
# Verifica e-mail duplicado
for aluno in _alunos.values():
if aluno["email"] == dados.email:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="E-mail já cadastrado."
)
aluno = {
"id": _proximo_id,
"nome": dados.nome,
"email": dados.email,
"nota": dados.nota,
"aprovado": dados.nota >= 6.0,
"criado_em": datetime.utcnow().isoformat()
}
_alunos[_proximo_id] = aluno
_proximo_id += 1
return aluno
# PATCH — atualizar parcialmente
@app.patch(
"/alunos/{aluno_id}",
response_model=AlunoResposta,
summary="Atualizar aluno",
tags=["Alunos"]
)
async def atualizar_aluno(
aluno_id: int,
dados: AlunoAtualizar
):
aluno = _alunos.get(aluno_id)
if not aluno:
raise HTTPException(status_code=404, detail="Aluno não encontrado.")
# Atualiza apenas campos enviados
atualizados = dados.model_dump(exclude_unset=True)
aluno.update(atualizados)
if "nota" in atualizados:
aluno["aprovado"] = aluno["nota"] >= 6.0
return aluno
# DELETE
@app.delete(
"/alunos/{aluno_id}",
status_code=status.HTTP_204_NO_CONTENT,
summary="Deletar aluno",
tags=["Alunos"]
)
async def deletar_aluno(aluno_id: int):
if aluno_id not in _alunos:
raise HTTPException(status_code=404, detail="Aluno não encontrado.")
del _alunos[aluno_id]
Dependências: Dependency Injection
O sistema de injeção de dependências do FastAPI é um de seus recursos mais poderosos:
from fastapi import Depends, Header, HTTPException
from typing import Annotated
# Dependência simples — autenticação por token
async def verificar_token(
authorization: Annotated[str | None, Header()] = None
):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Token de autenticação obrigatório."
)
token = authorization.replace("Bearer ", "")
if token != "token-secreto-123":
raise HTTPException(status_code=403, detail="Token inválido.")
return token
# Dependência de paginação — reutilizável
class Paginacao:
def __init__(
self,
pagina: int = Query(1, ge=1),
tamanho: int = Query(10, ge=1, le=100)
):
self.pagina = pagina
self.tamanho = tamanho
self.offset = (pagina - 1) * tamanho
# Usando dependências
@app.get("/admin/alunos", dependencies=[Depends(verificar_token)])
async def listar_admin(paginacao: Annotated[Paginacao, Depends()]):
alunos = list(_alunos.values())
inicio = paginacao.offset
fim = inicio + paginacao.tamanho
return {"alunos": alunos[inicio:fim], "pagina": paginacao.pagina}
# Dependência que fornece "banco de dados"
def get_db():
"""Em produção, retornaria uma sessão SQLAlchemy."""
db = _alunos # simulando
try:
yield db
finally:
pass # fechar conexão aqui
@app.get("/v2/alunos")
async def listar_v2(db: Annotated[dict, Depends(get_db)]):
return list(db.values())
Middleware e CORS
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
import time
# CORS — essencial para APIs consumidas por frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "https://meusite.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Compressão automática de respostas grandes
app.add_middleware(GZipMiddleware, minimum_size=1000)
# Middleware customizado
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
inicio = time.perf_counter()
resposta = await call_next(request)
duracao = time.perf_counter() - inicio
print(f"{request.method} {request.url.path} "
f"→ {resposta.status_code} "
f"({duracao*1000:.1f}ms)")
resposta.headers["X-Tempo-Ms"] = f"{duracao*1000:.2f}"
return resposta
app.add_middleware(LoggingMiddleware)
Lifespan: Startup e Shutdown
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup — executado ao iniciar
print("Iniciando aplicação...")
# Aqui: conectar ao banco, carregar modelos ML, etc.
_alunos.update({
1: {"id": 1, "nome": "Ana", "email": "ana@email.com", "nota": 9.5, "aprovado": True, "criado_em": "2024-01-01"},
2: {"id": 2, "nome": "Bruno", "email": "bruno@email.com", "nota": 7.0, "aprovado": True, "criado_em": "2024-01-02"},
3: {"id": 3, "nome": "Carla", "email": "carla@email.com", "nota": 5.5, "aprovado": False, "criado_em": "2024-01-03"},
})
print(f"Banco carregado com {len(_alunos)} alunos.")
yield # aplicação em execução
# Shutdown — executado ao encerrar
print("Encerrando aplicação...")
_alunos.clear()
app = FastAPI(title="API Escolar", lifespan=lifespan)
Background Tasks
from fastapi import BackgroundTasks
from datetime import datetime
def enviar_email_boas_vindas(email: str, nome: str):
"""Executado em background — não bloqueia a resposta."""
import time
time.sleep(2) # simula envio de e-mail
print(f"[Email] Boas-vindas enviadas para {nome} <{email}>")
def registrar_log(acao: str, dados: dict):
timestamp = datetime.utcnow().isoformat()
with open("auditoria.log", "a") as f:
f.write(f"{timestamp} | {acao} | {dados}\n")
@app.post("/alunos/registro", status_code=201)
async def registrar_aluno(
dados: AlunoCriar,
background_tasks: BackgroundTasks
):
global _proximo_id
aluno = {
"id": _proximo_id,
"nome": dados.nome,
"email": dados.email,
"nota": dados.nota,
"aprovado": dados.nota >= 6.0,
"criado_em": datetime.utcnow().isoformat()
}
_alunos[_proximo_id] = aluno
_proximo_id += 1
# Agendando tarefas em background
background_tasks.add_task(enviar_email_boas_vindas, dados.email, dados.nome)
background_tasks.add_task(registrar_log, "CRIAR_ALUNO", {"id": aluno["id"]})
return aluno # responde imediatamente
Exemplo Completo: Estrutura de Projeto FastAPI
escola-api/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── modelos/
│ │ ├── __init__.py
│ │ └── aluno.py ← modelos Pydantic
│ ├── routers/
│ │ ├── __init__.py
│ │ └── alunos.py ← endpoints
│ ├── servicos/
│ │ ├── __init__.py
│ │ └── aluno_servico.py ← lógica de negócio
│ └── dependencias.py ← injeção de dependências
├── tests/
│ └── test_alunos.py
├── .env
├── requirements.txt
└── pyproject.toml
# app/main.py
from fastapi import FastAPI
from app.routers import alunos
from app.config import configuracoes
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
print(f"Iniciando {configuracoes.nome_app} v{configuracoes.versao}")
yield
print("Encerrando aplicação.")
def criar_app() -> FastAPI:
app = FastAPI(
title=configuracoes.nome_app,
version=configuracoes.versao,
lifespan=lifespan
)
app.include_router(alunos.router)
return app
app = criar_app()
# Testando com pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_listar_alunos():
resposta = client.get("/alunos")
assert resposta.status_code == 200
assert "alunos" in resposta.json()
def test_criar_aluno():
resposta = client.post("/alunos", json={
"nome": "Eduardo",
"email": "eduardo@email.com",
"nota": 8.5
})
assert resposta.status_code == 201
assert resposta.json()["nome"] == "Eduardo"
def test_email_duplicado():
dados = {"nome": "Teste", "email": "duplicado@email.com", "nota": 7.0}
client.post("/alunos", json=dados)
resposta = client.post("/alunos", json=dados)
assert resposta.status_code == 409
Flask vs FastAPI
| Característica | Flask | FastAPI |
|---|---|---|
| Curva de aprendizado | Baixa | Média |
| Performance | Boa | Excelente (ASGI) |
| Tipagem | Opcional | Nativa |
| Validação | Manual | Automática (Pydantic) |
| Documentação | Manual | Automática (OpenAPI) |
| Async nativo | Não | Sim |
| Maturidade | Alta | Alta (desde 2018) |
| Ecossistema | Imenso | Crescendo rápido |
Resumo
- FastAPI combina tipagem, validação automática e documentação interativa em um único framework
- Pydantic valida e serializa dados automaticamente — erros são retornados com detalhes precisos
Query,Path,HeadereBodydeclaram e validam parâmetros de forma expressiva- O sistema de injeção de dependências (
Depends) é reutilizável, testável e elegante - CORS, GZip e middlewares customizados são adicionados com
add_middleware lifespansubstituistartupeshutdownevents — gerencia recursos da aplicaçãoBackgroundTasksexecuta tarefas pesadas sem bloquear a resposta ao clienteTestClientpermite testar endpoints sem subir o servidor — integrado com pytest
Referências e Leituras Complementares
- FastAPI — documentação oficial — https://fastapi.tiangolo.com/
- Pydantic v2 — documentação — https://docs.pydantic.dev/latest/
- Uvicorn — servidor ASGI — https://www.uvicorn.org/
- OpenAPI Specification — https://spec.openapis.org/oas/v3.1.0
- Starlette — base do FastAPI — https://www.starlette.io/
- GRINBERG, Miguel. Flask Web Development. 2. ed. O'Reilly Media, 2018. — contexto histórico e comparativo com Flask.
- LATHKAR, Malhar. FastAPI Essentials. Packt, 2024. — guia prático de FastAPI do básico ao avançado.