Introdução ao desenvolvimento de APIs com FastAPI

1. O que é FastAPI e por que ele se destaca?

FastAPI é um framework moderno e rápido para construção de APIs com Python 3.7+, lançado em 2018 por Sebastián Ramírez. Sua principal característica é a alta performance — comparável a frameworks assíncronos como Node.js e Go — combinada com tipagem estática e validação automática de dados.

Características principais:
- Alta performance: utiliza Starlette (framework ASGI) como base, com suporte nativo a async/await
- Tipagem com Pydantic: modelos Pydantic validam automaticamente tipos, valores obrigatórios e formatos
- Documentação automática: gera interfaces Swagger (/docs) e ReDoc (/redoc) sem configuração
- Injeção de dependências: sistema elegante para reutilizar lógica (autenticação, banco, etc.)

Comparação rápida:
| Framework | Performance | Documentação automática | Validação | Curva de aprendizado |
|-----------|-------------|------------------------|-----------|----------------------|
| FastAPI | Muito alta | Sim | Nativa (Pydantic) | Média |
| Flask | Média | Não | Manual | Baixa |
| Django | Média | Parcial | Parcial (DRF) | Alta |

Casos de uso ideais: microsserviços, prototipagem rápida de MVPs, APIs com validação rigorosa de dados, sistemas que exigem alta concorrência e documentação viva.

2. Configuração do ambiente e primeiro projeto

Crie um ambiente virtual e instale as dependências:

python -m venv venv
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate     # Windows
pip install fastapi uvicorn

Estrutura básica de diretórios para o projeto:

meu_projeto/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   ├── routes.py
│   └── database.py
├── tests/
│   └── test_main.py
└── requirements.txt

Crie o arquivo app/main.py com o "Hello World" assíncrono:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

Execute com Uvicorn:

uvicorn app.main:app --reload

Acesse http://127.0.0.1:8000 e veja a resposta JSON. O --reload permite recarregamento automático durante o desenvolvimento.

3. Rotas, parâmetros e validação com Pydantic

Vamos expandir o main.py com diferentes tipos de rotas e parâmetros:

from fastapi import FastAPI, Path, Query
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# Modelo Pydantic para validação
class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None

# GET com parâmetro de caminho
@app.get("/items/{item_id}")
async def read_item(item_id: int = Path(..., title="ID do item", ge=1)):
    return {"item_id": item_id}

# GET com parâmetros de query
@app.get("/items/")
async def list_items(skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=100)):
    return {"skip": skip, "limit": limit}

# POST com corpo validado
@app.post("/items/")
async def create_item(item: Item):
    return {"item": item.dict(), "price_with_tax": item.price * 1.1}

# PUT para atualização
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    return {"item_id": item_id, "item": item.dict()}

# DELETE
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"message": f"Item {item_id} deleted"}

O Pydantic valida automaticamente: tipos (int, float, str), valores obrigatórios, valores opcionais e restrições como ge (greater or equal). Se os dados forem inválidos, o FastAPI retorna erro 422 com detalhes.

4. Documentação interativa automática (Swagger e ReDoc)

Sem qualquer configuração extra, acesse:

  • http://127.0.0.1:8000/docs — interface Swagger UI
  • http://127.0.0.1:8000/redoc — interface ReDoc

Ambas exibem todas as rotas, parâmetros, modelos de dados e permitem testar as requisições diretamente.

Para personalizar a documentação, configure o FastAPI:

app = FastAPI(
    title="Minha API de Exemplo",
    description="API para demonstração do FastAPI",
    version="1.0.0",
    contact={
        "name": "Seu Nome",
        "url": "https://seusite.com",
        "email": "email@exemplo.com",
    },
    license_info={
        "name": "MIT",
        "url": "https://opensource.org/licenses/MIT",
    }
)

# Agrupe rotas com tags
@app.get("/users/", tags=["Usuários"])
async def get_users():
    return [{"username": "joao"}, {"username": "maria"}]

@app.post("/items/", tags=["Itens"])
async def create_item(item: Item):
    return item

5. Tratamento de erros e respostas padronizadas

Crie respostas consistentes com modelos unificados:

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Any, Optional

app = FastAPI()

# Modelos de resposta padronizados
class SuccessResponse(BaseModel):
    success: bool = True
    data: Any = None
    message: str = "Operação realizada com sucesso"

class ErrorResponse(BaseModel):
    success: bool = False
    error_code: int
    message: str
    details: Optional[dict] = None

# Exceção personalizada
class NotFoundException(Exception):
    def __init__(self, entity: str, entity_id: int):
        self.entity = entity
        self.entity_id = entity_id

@app.exception_handler(NotFoundException)
async def not_found_handler(request: Request, exc: NotFoundException):
    return JSONResponse(
        status_code=404,
        content=ErrorResponse(
            error_code=404,
            message=f"{exc.entity} com ID {exc.entity_id} não encontrado"
        ).dict()
    )

# Middleware global para logging
@app.middleware("http")
async def log_requests(request: Request, call_next):
    print(f"Requisição: {request.method} {request.url}")
    response = await call_next(request)
    print(f"Resposta: {response.status_code}")
    return response

# Rota com tratamento de erro
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id > 100:
        raise NotFoundException(entity="Item", entity_id=item_id)
    return SuccessResponse(data={"item_id": item_id, "name": "Exemplo"})

O HTTPException do FastAPI também pode ser usado diretamente:

from fastapi import HTTPException

@app.get("/secure-data/")
async def secure_data(api_key: str = Query(...)):
    if api_key != "secret123":
        raise HTTPException(
            status_code=401,
            detail="API key inválida",
            headers={"X-Error": "Invalid API Key"}
        )
    return {"data": "informação secreta"}

6. Dependências e injeção de dependências

O sistema de dependências permite reutilizar lógica de forma limpa:

from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional

app = FastAPI()

# Dependência simples: validação de token
async def verify_token(authorization: Optional[str] = Header(None)):
    if authorization is None:
        raise HTTPException(status_code=401, detail="Token não fornecido")
    # Simulação de validação
    if authorization != "Bearer token_valido":
        raise HTTPException(status_code=403, detail="Token inválido")
    return {"user": "admin", "token": authorization}

# Dependência para conexão com banco (simulada)
async def get_database():
    db = {"connection": "simulada", "status": "conectado"}
    try:
        yield db  # yield permite cleanup após uso
    finally:
        print("Conexão fechada")

# Rota protegida usando dependências
@app.get("/users/me")
async def get_current_user(
    token_data: dict = Depends(verify_token),
    db: dict = Depends(get_database)
):
    return {
        "user": token_data["user"],
        "database_status": db["status"]
    }

# Dependência com classe
class CommonQueryParams:
    def __init__(self, skip: int = 0, limit: int = 100):
        self.skip = skip
        self.limit = limit

@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
    return {"skip": commons.skip, "limit": commons.limit}

7. Testes automatizados com FastAPI e pytest

Instale o pytest e o httpx (usado pelo TestClient):

pip install pytest httpx

Crie tests/test_main.py:

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

def test_create_item():
    response = client.post(
        "/items/",
        json={"name": "Notebook", "price": 2500.0}
    )
    assert response.status_code == 200
    data = response.json()
    assert data["item"]["name"] == "Notebook"
    assert data["price_with_tax"] == 2750.0

def test_create_item_invalid():
    response = client.post(
        "/items/",
        json={"name": "Teste"}  # Faltando price
    )
    assert response.status_code == 422  # Erro de validação

def test_item_not_found():
    response = client.get("/items/999")
    assert response.status_code == 404
    assert response.json()["error_code"] == 404

def test_authentication():
    response = client.get("/users/me")
    assert response.status_code == 401  # Sem token

    response = client.get(
        "/users/me",
        headers={"Authorization": "Bearer token_valido"}
    )
    assert response.status_code == 200

# Teste com dependência mockada
def test_with_mock(mocker):
    # Exemplo com pytest-mock
    mock_db = {"connection": "mock", "status": "teste"}
    mocker.patch("app.main.get_database", return_value=mock_db)

    response = client.get(
        "/users/me",
        headers={"Authorization": "Bearer token_valido"}
    )
    assert response.status_code == 200

Execute os testes:

pytest tests/ -v

8. Deploy e boas práticas finais

Execução em produção com Gunicorn + Uvicorn:

pip install gunicorn
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Variáveis de ambiente (arquivo .env):

DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=minha_chave_secreta
ENVIRONMENT=production

Carregue com python-dotenv ou pydantic-settings.

Checklist básico para produção:
- [ ] CORS: Configurar CORSMiddleware para origens permitidas
- [ ] Versionamento: Prefixo /v1/ nas rotas
- [ ] Segurança mínima: HTTPS, rate limiting, validação de entrada
- [ ] Logging: Estruturar logs com níveis (info, warning, error)
- [ ] Health check: Rota /health para monitoramento
- [ ] Documentação: Desabilitar /docs em produção ou proteger com autenticação

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://meudominio.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
async def health_check():
    return {"status": "healthy", "version": "1.0.0"}

FastAPI oferece performance excepcional, documentação viva e um ecossistema maduro para construção de APIs modernas. Com os conceitos apresentados — rotas, validação, dependências, testes e deploy — você está pronto para desenvolver APIs robustas e escaláveis.


Referências