APIs REST com FastAPI: Tutorial Completo
Aprenda a construir APIs REST profissionais com FastAPI em Python: rotas, modelos Pydantic, validação, autenticação e documentação automática.
FastAPI é o framework Python que mais cresce para criação de APIs. Ele combina alta performance, documentação automática e validação de dados nativa — tudo isso mantendo a simplicidade que a gente espera de Python. Neste tutorial, você vai construir uma API REST completa do zero.
Por que FastAPI?
Antes de colocar a mão na massa, veja por que FastAPI se destaca:
- Performance: Uma das mais rápidas em Python (baseado em Starlette e Uvicorn)
- Documentação automática: Swagger UI e ReDoc gerados automaticamente
- Validação: Pydantic valida dados de entrada automaticamente
- Tipagem: Usa type hints do Python para definir esquemas
- Async: Suporte nativo a async/await
- Fácil de aprender: Se você sabe Python, já sabe quase tudo
Configuração Inicial
# Instalação
# pip install fastapi uvicorn[standard]
# Para rodar o servidor:
# uvicorn main:app --reload
Hello World com FastAPI
from fastapi import FastAPI
app = FastAPI(
title="Minha API",
description="API de exemplo com FastAPI",
version="1.0.0",
)
@app.get("/")
def raiz():
return {"mensagem": "Olá, FastAPI!", "status": "online"}
@app.get("/saudacao/{nome}")
def saudacao(nome: str):
return {"mensagem": f"Olá, {nome}! Bem-vindo à nossa API."}
Acesse http://localhost:8000/docs para ver a documentação interativa automática.
Modelos com Pydantic
Pydantic é o coração da validação de dados no FastAPI:
from pydantic import BaseModel, Field, validator, EmailStr
from typing import Optional
from datetime import datetime
from enum import Enum
class StatusTarefa(str, Enum):
pendente = "pendente"
em_andamento = "em_andamento"
concluida = "concluida"
cancelada = "cancelada"
class TarefaBase(BaseModel):
titulo: str = Field(..., min_length=3, max_length=100,
description="Título da tarefa")
descricao: Optional[str] = Field(None, max_length=500)
prioridade: int = Field(default=1, ge=1, le=5,
description="Prioridade de 1 (baixa) a 5 (alta)")
class TarefaCriar(TarefaBase):
responsavel_email: Optional[str] = None
@validator("titulo")
def titulo_capitalizado(cls, v):
return v.strip().capitalize()
class TarefaAtualizar(BaseModel):
titulo: Optional[str] = Field(None, min_length=3, max_length=100)
descricao: Optional[str] = None
status: Optional[StatusTarefa] = None
prioridade: Optional[int] = Field(None, ge=1, le=5)
class TarefaResposta(TarefaBase):
id: int
status: StatusTarefa = StatusTarefa.pendente
criada_em: datetime
atualizada_em: Optional[datetime] = None
class Config:
from_attributes = True
CRUD Completo
Vamos construir uma API de gerenciamento de tarefas com todas as operações CRUD:
from fastapi import FastAPI, HTTPException, Query, Path, status
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
from enum import Enum
app = FastAPI(title="API de Tarefas", version="2.0.0")
# ---- Modelos ----
class StatusTarefa(str, Enum):
pendente = "pendente"
em_andamento = "em_andamento"
concluida = "concluida"
class TarefaCriar(BaseModel):
titulo: str = Field(..., min_length=3, max_length=100)
descricao: Optional[str] = None
prioridade: int = Field(default=1, ge=1, le=5)
class TarefaAtualizar(BaseModel):
titulo: Optional[str] = Field(None, min_length=3, max_length=100)
descricao: Optional[str] = None
status: Optional[StatusTarefa] = None
prioridade: Optional[int] = Field(None, ge=1, le=5)
class Tarefa(BaseModel):
id: int
titulo: str
descricao: Optional[str]
status: StatusTarefa
prioridade: int
criada_em: datetime
atualizada_em: Optional[datetime]
# ---- "Banco de dados" em memória ----
banco_tarefas: dict[int, dict] = {}
proximo_id = 1
# ---- Rotas ----
@app.post("/tarefas", response_model=Tarefa, status_code=status.HTTP_201_CREATED)
def criar_tarefa(tarefa: TarefaCriar):
"""Cria uma nova tarefa."""
global proximo_id
nova_tarefa = {
"id": proximo_id,
"titulo": tarefa.titulo,
"descricao": tarefa.descricao,
"status": StatusTarefa.pendente,
"prioridade": tarefa.prioridade,
"criada_em": datetime.now(),
"atualizada_em": None,
}
banco_tarefas[proximo_id] = nova_tarefa
proximo_id += 1
return nova_tarefa
@app.get("/tarefas", response_model=list[Tarefa])
def listar_tarefas(
status: Optional[StatusTarefa] = Query(None, description="Filtrar por status"),
prioridade_min: Optional[int] = Query(None, ge=1, le=5),
busca: Optional[str] = Query(None, description="Buscar no título"),
ordenar_por: str = Query("criada_em", enum=["criada_em", "prioridade"]),
limite: int = Query(10, ge=1, le=100),
offset: int = Query(0, ge=0),
):
"""Lista tarefas com filtros opcionais."""
tarefas = list(banco_tarefas.values())
# Aplicar filtros
if status:
tarefas = [t for t in tarefas if t["status"] == status]
if prioridade_min:
tarefas = [t for t in tarefas if t["prioridade"] >= prioridade_min]
if busca:
tarefas = [t for t in tarefas if busca.lower() in t["titulo"].lower()]
# Ordenar
reverse = ordenar_por == "prioridade"
tarefas.sort(key=lambda t: t[ordenar_por], reverse=reverse)
# Paginação
return tarefas[offset:offset + limite]
@app.get("/tarefas/{tarefa_id}", response_model=Tarefa)
def obter_tarefa(tarefa_id: int = Path(..., gt=0)):
"""Obtém uma tarefa pelo ID."""
if tarefa_id not in banco_tarefas:
raise HTTPException(
status_code=404,
detail=f"Tarefa {tarefa_id} não encontrada."
)
return banco_tarefas[tarefa_id]
@app.put("/tarefas/{tarefa_id}", response_model=Tarefa)
def atualizar_tarefa(tarefa_id: int, atualizacao: TarefaAtualizar):
"""Atualiza uma tarefa existente."""
if tarefa_id not in banco_tarefas:
raise HTTPException(status_code=404, detail="Tarefa não encontrada.")
tarefa = banco_tarefas[tarefa_id]
dados_atualizacao = atualizacao.model_dump(exclude_unset=True)
for campo, valor in dados_atualizacao.items():
tarefa[campo] = valor
tarefa["atualizada_em"] = datetime.now()
return tarefa
@app.delete("/tarefas/{tarefa_id}", status_code=status.HTTP_204_NO_CONTENT)
def deletar_tarefa(tarefa_id: int):
"""Deleta uma tarefa."""
if tarefa_id not in banco_tarefas:
raise HTTPException(status_code=404, detail="Tarefa não encontrada.")
del banco_tarefas[tarefa_id]
@app.patch("/tarefas/{tarefa_id}/concluir", response_model=Tarefa)
def concluir_tarefa(tarefa_id: int):
"""Marca uma tarefa como concluída."""
if tarefa_id not in banco_tarefas:
raise HTTPException(status_code=404, detail="Tarefa não encontrada.")
tarefa = banco_tarefas[tarefa_id]
tarefa["status"] = StatusTarefa.concluida
tarefa["atualizada_em"] = datetime.now()
return tarefa
Middleware e Tratamento de Erros
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import time
import logging
app = FastAPI()
# CORS - permitir requisições de outros domínios
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "https://meusite.com.br"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Middleware de logging e tempo de resposta
@app.middleware("http")
async def log_requests(request: Request, call_next):
inicio = time.time()
response = await call_next(request)
duracao = time.time() - inicio
logging.info(
f"{request.method} {request.url.path} "
f"- Status: {response.status_code} "
f"- Duração: {duracao:.3f}s"
)
response.headers["X-Process-Time"] = str(duracao)
return response
# Tratamento global de erros
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content={
"erro": "Valor inválido",
"detalhe": str(exc),
"path": str(request.url),
},
)
Dependências e Autenticação
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
app = FastAPI()
CHAVE_SECRETA = "minha-chave-super-secreta-mudar-em-producao"
ALGORITMO = "HS256"
security = HTTPBearer()
def criar_token(dados: dict, expiracao_minutos: int = 30):
"""Cria um token JWT."""
dados_token = dados.copy()
expira = datetime.utcnow() + timedelta(minutes=expiracao_minutos)
dados_token.update({"exp": expira})
return jwt.encode(dados_token, CHAVE_SECRETA, algorithm=ALGORITMO)
def verificar_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
"""Dependência que verifica o token JWT."""
try:
payload = jwt.decode(
credentials.credentials, CHAVE_SECRETA, algorithms=[ALGORITMO]
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expirado")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Token inválido")
# Simulação de banco de usuários
usuarios_db = {
"admin@email.com": {
"nome": "Admin",
"senha": "admin123", # Em produção, use hash!
"role": "admin",
}
}
class LoginRequest(BaseModel):
email: str
senha: str
@app.post("/auth/login")
def login(dados: LoginRequest):
usuario = usuarios_db.get(dados.email)
if not usuario or usuario["senha"] != dados.senha:
raise HTTPException(status_code=401, detail="Credenciais inválidas")
token = criar_token({
"sub": dados.email,
"nome": usuario["nome"],
"role": usuario["role"],
})
return {"access_token": token, "token_type": "bearer"}
@app.get("/perfil")
def meu_perfil(usuario: dict = Depends(verificar_token)):
"""Rota protegida - requer autenticação."""
return {
"email": usuario["sub"],
"nome": usuario["nome"],
"role": usuario["role"],
}
@app.get("/admin/dashboard")
def dashboard_admin(usuario: dict = Depends(verificar_token)):
"""Rota protegida - apenas admins."""
if usuario.get("role") != "admin":
raise HTTPException(status_code=403, detail="Acesso negado")
return {"mensagem": "Bem-vindo ao painel admin!", "usuario": usuario["nome"]}
Organizando o Projeto
Para projetos maiores, organize com routers:
# projeto/
# ├── main.py
# ├── routers/
# │ ├── __init__.py
# │ ├── tarefas.py
# │ └── usuarios.py
# ├── models/
# │ ├── __init__.py
# │ └── schemas.py
# ├── services/
# │ └── auth.py
# └── config.py
# routers/tarefas.py
from fastapi import APIRouter, Depends
router = APIRouter(
prefix="/tarefas",
tags=["Tarefas"],
responses={404: {"description": "Não encontrado"}},
)
@router.get("/")
def listar():
return {"tarefas": []}
@router.post("/")
def criar(tarefa: TarefaCriar):
return {"id": 1, **tarefa.model_dump()}
# main.py
from fastapi import FastAPI
from routers import tarefas, usuarios
app = FastAPI(title="API Organizada")
app.include_router(tarefas.router)
app.include_router(usuarios.router)
Integração com Banco de Dados
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from fastapi import Depends
from datetime import datetime
DATABASE_URL = "sqlite:///./tarefas.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Modelo do banco
class TarefaDB(Base):
__tablename__ = "tarefas"
id = Column(Integer, primary_key=True, index=True)
titulo = Column(String(100), nullable=False)
descricao = Column(String(500))
status = Column(String(20), default="pendente")
prioridade = Column(Integer, default=1)
criada_em = Column(DateTime, default=datetime.now)
Base.metadata.create_all(bind=engine)
# Dependência para sessão do banco
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Rota usando o banco
@app.get("/tarefas")
def listar_tarefas(db: Session = Depends(get_db)):
return db.query(TarefaDB).all()
@app.post("/tarefas", status_code=201)
def criar_tarefa(tarefa: TarefaCriar, db: Session = Depends(get_db)):
nova = TarefaDB(**tarefa.model_dump())
db.add(nova)
db.commit()
db.refresh(nova)
return nova
Deploy
Para colocar sua API em produção, você tem várias opções populares no Brasil:
# 1. Usando Gunicorn + Uvicorn (recomendado para produção)
# pip install gunicorn
# gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
# 2. Dockerfile
# FROM python:3.12-slim
# WORKDIR /app
# COPY requirements.txt .
# RUN pip install -r requirements.txt
# COPY . .
# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Conclusão
FastAPI é uma escolha excelente para construir APIs modernas em Python. Os principais pontos fortes são a validação automática com Pydantic, a documentação interativa gratuita e a performance de primeiro nível.
Para ir além, explore:
- WebSockets para comunicação em tempo real
- Background Tasks para processamento assíncrono
- Testes com pytest e TestClient do FastAPI
- Docker para containerização
- CI/CD com GitHub Actions
FastAPI está crescendo rapidamente no mercado brasileiro, especialmente em startups e fintechs. Investir nesse framework é investir no futuro da sua carreira como desenvolvedor Python.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português