Comece com Python FastAPI: seu primeiro passo concreto hoje

Python FastAPI: o essencial em um artigo — código real, diagramas e etapas concretas, extraídos de um curso de 32 lições.

Comece com Python FastAPI: seu primeiro passo concreto hoje

A melhor forma de aprender Python FastAPI é fazendo. Este artigo te dá o pontapé inicial com trechos práticos extraídos de um curso de 32 lições — o suficiente para obter um primeiro resultado já hoje.

tl;dr
  • Introdução e instalação
  • Rotas e métodos
  • Pydantic e validação
  • Injeção de dependências
  • Banco de dados
~$ cat ./parcours.md # Python FastAPI — 10 capítulos
01
Introdução e instalação
→ Por que FastAPI ?→ Instalar FastAPI e uvicorn+ 1 mais lições
02
Rotas e métodos
→ Path params e query params→ POST, PUT, PATCH, DELETE+ 1 mais lições
03
Pydantic e validação
→ BaseModel para as requisições→ Validators e constraints+ 2 mais lições
04
Injeção de dependência
→ Depends - o princípio→ Dependências com yield+ 1 mais lições
05
Banco de dados
→ SQLAlchemy com FastAPI→ CRUD completo+ 1 mais lições
06
Autenticação JWT
→ OAuth2 + JWT→ Roles e permissões+ 1 mais lições
07
Async e performance
→ async vs sync→ BackgroundTasks+ 1 mais lições
08
Testes
→ TestClient e pytest→ Override dependencies+ 1 mais lições
🏁
Projeto final (+ 2 capítulos no caminho)
→ Você sai com um projeto concreto e demonstrável

Por que FastAPI?

NOTEObjetivo — Entender o que diferencia o FastAPI dos outros frameworks Python e quando escolhê-lo.

FastAPI em uma frase

Framework Python moderno para construir APIs REST rápidas, tipadas e com documentação automática, baseado em Starlette (ASGI) e Pydantic.

Os 4 superpoderes

PoderDetalhe
PerformanceAsync nativo via ASGI/uvicorn, comparável a Node/Go
Type safetyType hints Python -> validação em runtime via Pydantic
Docs autoSwagger UI + ReDoc gerados automaticamente
Dev experienceAuto-completar no IDE, erros claros, dependências injetáveis

Comparação Flask vs FastAPI vs Django

CritérioFlaskFastAPIDjango
EstiloMicroMicro (foco em API)Full-stack
Async nativoNão (extensões)SIMParcial (DRF não)
Type hintsNãoSIM (Pydantic)Não
Docs autoNãoSIM (Swagger)Via DRF (manual)
ORM inclusoNãoNãoSIM (Django ORM)
AprendizadoMuito fácilFácilModerado
Performance (req/s)~5k~20-30k~3k

Pequeno exemplo: "Hello World"

output
# Flask
from flask import Flask
app = Flask(__name__)

@app.route("/items/<int:item_id>")
def get_item(item_id):
    return {"id": item_id}
output
# FastAPI
from fastapi import FastAPI
app = FastAPI()

@app.get("/items/{item_id}")
def get_item(item_id: int):
    return {"id": item_id}
# + validação automática, doc automática, type hints

Quando escolher FastAPI?

TIPSIM
  • Backend API REST (microsserviços)
  • Servir um modelo de ML
  • Necessidade de performance (async)
  • Equipe que aprecia tipagem
  • Documentação OpenAPI exigida
WARNINGNÃO
  • App full-stack com templates HTML pesados (Django)
  • Pequeno script pontual (Flask basta)
  • Equipe já produtiva com outro framework

Quem usa FastAPI?

O ecossistema

ComponentePapel
StarletteFramework ASGI subjacente
PydanticValidação e serialização
uvicornServidor ASGI (dev)
gunicornProcess manager (prod)
SQLAlchemyORM (comum)
AlembicMigrações de BD

Resumo

NOTEPara lembrar
  • FastAPI = performance + type safety + docs automáticas
  • Baseado em Starlette (ASGI) e Pydantic
  • Ideal para APIs e microsserviços modernos
  • Async nativo, ~5x mais rápido que Flask

Próxima etapa: Parte 2 — Instalar o FastAPI

Deploy Docker e testes

Projeto final • Docker • testes • CI/CD

NOTEObjetivo — Finalizar o projeto: testes com pytest, Dockerfile, docker-compose, CI GitHub Actions.

Testes de integração chave

output
# tests/test_posts.py
import pytest

def test_create_post_requires_auth(client):
    r = client.post("/posts", json={"title": "Test",
                                  "content": "Hello world"})
    assert r.status_code == 401

def test_create_post(client, auth_headers):
    r = client.post("/posts", headers=auth_headers, json={
        "title": "Meu primeiro post",
        "content": "Conteúdo interessante aqui",
        "tags": ["python", "fastapi"],
        "published": True
    })
    assert r.status_code == 201
    data = r.json()
    assert data["slug"] == "meu-primeiro-post"
    assert len(data["tags"]) == 2

def test_list_posts_only_published(client, auth_headers):
    client.post("/posts", headers=auth_headers,
                json={"title": "Rascunho", "content": "...", "published": False})
    client.post("/posts", headers=auth_headers,
                json={"title": "Publicado", "content": "...", "published": True})
    
    r = client.get("/posts")
    assert len(r.json()["items"]) == 1

def test_only_author_can_delete(client):
    h1 = make_user_and_login(client, "alice", "alice@x.com")
    h2 = make_user_and_login(client, "bob", "bob@x.com")
    
    r = client.post("/posts", headers=h1,
                     json={"title": "Meu", "content": "...", "published": True})
    slug = r.json()["slug"]
    
    # Bob tenta excluir o post da Alice
    r = client.delete(f"/posts/{slug}", headers=h2)
    assert r.status_code == 403
    
    # Alice consegue
    r = client.delete(f"/posts/{slug}", headers=h1)
    assert r.status_code == 204

conftest.py final

output
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.db import Base, get_db

TestEngine = create_engine("sqlite:///./test.db",
                            connect_args={"check_same_thread": False})
TestSession = sessionmaker(bind=TestEngine)

def override_db():
    db = TestSession()
    try:
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_db

@pytest.fixture(autouse=True)
def reset_db():
    Base.metadata.create_all(bind=TestEngine)
    yield
    Base.metadata.drop_all(bind=TestEngine)

@pytest.fixture
def client():
    return TestClient(app)

@pytest.fixture
def auth_headers(client):
    client.post("/auth/register", json={
        "email": "test@x.com", "username": "test",
        "password": "secret123"
    })
    r = client.post("/auth/login", data={
        "username": "test@x.com", "password": "secret123"
    })
    return {"Authorization": f'Bearer {r.json()["access_token"]}'}

Dockerfile

output
FROM python:3.11-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.11-slim
RUN useradd -m app
USER app
WORKDIR /home/app
ENV PATH=/home/app/.local/bin:$PATH

COPY --from=builder /root/.local /home/app/.local
COPY --chown=app:app . .

EXPOSE 8000
HEALTHCHECK CMD curl -fsS http://localhost:8000/health || exit 1

ENTRYPOINT ["./entrypoint.sh"]
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app.main:app"]

docker-compose.yml

output
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://blog:pwd@db/blogdb
      SECRET_KEY: change-me-in-prod
      REFRESH_SECRET_KEY: change-me-too
    depends_on:
      db: {condition: service_healthy}
    restart: unless-stopped
  
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: blog
      POSTGRES_PASSWORD: pwd
      POSTGRES_DB: blogdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blog"]
      interval: 5s
      retries: 5

volumes:
  pgdata:

CI/CD GitHub Actions

output
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: {python-version: "3.11"}
      - run: pip install -r requirements.txt
      - run: pip install pytest pytest-cov httpx
      - run: pytest --cov=app --cov-report=xml
      - uses: codecov/codecov-action@v4
  
  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == "refs/heads/main"
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest

Executar em produção

output
# Local completo
docker-compose up -d
docker-compose logs -f api
docker-compose exec api alembic upgrade head

# Visitar
#   http://localhost:8000/docs
#   http://localhost:8000/redoc

WebSockets com FastAPI

NOTEObjetivo — Construir um chat em tempo real com WebSocket nativo do FastAPI.

Hello WebSocket

output
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    try:
        while True:
            data = await ws.receive_text()
            await ws.send_text(f"Echo: {data}")
    except WebSocketDisconnect:
        print("Cliente desconectado")

Teste rápido com wscat

output
npm install -g wscat
wscat -c ws://localhost:8000/ws
> Hello
< Echo: Hello

Connection Manager

output
class ConnectionManager:
    def __init__(self):
        self.active: list[WebSocket] = []
    
    async def connect(self, ws: WebSocket):
        await ws.accept()
        self.active.append(ws)
    
    def disconnect(self, ws: WebSocket):
        self.active.remove(ws)
    
    async def broadcast(self, message: str):
        for connection in self.active:
            try:
                await connection.send_text(message)
            except:
                pass

manager = ConnectionManager()

Chat broadcast

output
@app.websocket("/chat/{username}")
async def chat(ws: WebSocket, username: str):
    await manager.connect(ws)
    await manager.broadcast(f">> {username} entrou")
    
    try:
        while True:
            msg = await ws.receive_text()
            await manager.broadcast(f"[{username}] {msg}")
    except WebSocketDisconnect:
        manager.disconnect(ws)
        await manager.broadcast(f"<< {username} saiu")

Enviar JSON

output
@app.websocket("/json")
async def json_ws(ws: WebSocket):
    await ws.accept()
    try:
        while True:
            data = await ws.receive_json()
            await ws.send_json({"echo": data, "server_time": str(datetime.now())})
    except WebSocketDisconnect:
        pass

Autenticação WebSocket

output
from fastapi import WebSocketException, status

@app.websocket("/ws")
async def ws(ws: WebSocket, token: str):
    try:
        payload = decode_token(token)
        user_id = payload["sub"]
    except:
        raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
    
    await ws.accept()
    await ws.send_text(f"Conectado como {user_id}")
    ...

# Cliente: ws://localhost:8000/ws?token=eyJ...
va-plus-loin

Este artigo cobre os trechos mais úteis — o curso completo Python FastAPI (10 capítulos, 32 lições, exercícios corrigidos e projeto final) leva você até o fim.

./acessar-o-curso-completo curso gratuito: Vibe Coding

FAQ

Quanto tempo para aprender Python FastAPI?
Com uma progressão estruturada (10 capítulos, 32 lições curtas e práticas), você atinge um nível operacional em algumas semanas dedicando 30 a 60 minutos por dia. O importante é praticar cada conceito imediatamente.
Precisa de pré-requisitos?
Básicos de informática são suficientes. Se você sabe usar um terminal e ler código simples, está pronto.
Por onde começar na prática?
Reproduza os comandos deste artigo e depois siga o curso completo Python FastAPI: ele encadeia as 32 lições em ordem, com exercícios e projeto final.

📬 Quer receber este tipo de guia toda semana? Inscreva-se gratuitamente — código real, zero enrolação.