O que é FastAPI?
FastAPI é um framework web moderno em Python para a construção de APIs. Sua ideia central é que seus type hints são o contrato. Declare um parâmetro como int, um corpo como um modelo Pydantic, ou um tipo de retorno como ItemOut, e o framework valida a requisição, serializa a resposta e escreve a documentação — tudo a partir dessa única declaração.
Ele é construído sobre duas bibliotecas que você deve conhecer. Starlette fornece o toolkit ASGI: roteamento, middleware e o ciclo de requisição/resposta. Pydantic fornece a validação e a serialização. FastAPI é a camada que os combina, adiciona injeção de dependência e gera um esquema OpenAPI na inicialização.
O resultado é que uma função pequena e legível entrega a você um endpoint validado com documentação interativa. A mágica é real, mas é mecânica, e entender essa mecânica é o objetivo deste guia.
Operações de caminho
Uma operação de caminho (path operation) é uma função vinculada a um método HTTP e a uma URL. O decorador nomeia o método, e a assinatura da função informa ao FastAPI o que esperar.
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, world"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
O segmento {item_id} é um parâmetro de caminho, e sua anotação int significa que o FastAPI converte a string e retorna automaticamente um erro 422 caso não consiga. Qualquer coisa que não esteja no caminho é tratada como um parâmetro de query, e um valor padrão de None o torna opcional. Não há parsing manual nem request.args.
Agrupe operações relacionadas com um APIRouter, que pode conter um prefixo e tags para a documentação.
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["items"])
@router.get("")
async def list_items():
return []
@router.post("", status_code=201)
async def create_item():
return {}
Depois, inclua o router no app: app.include_router(router). É assim que um projeto FastAPI se mantém modular.
Modelos Pydantic
Um modelo Pydantic é uma classe que herda de BaseModel. Ele analisa a entrada, converte tipos compatíveis, aplica restrições e gera um erro de validação claro quando algo está errado.
# app/schemas.py
from datetime import datetime
from pydantic import BaseModel, Field
class ItemBase(BaseModel):
name: str = Field(min_length=1, max_length=120)
price: float = Field(gt=0)
tags: list[str] = []
class ItemCreate(ItemBase):
pass
class ItemOut(ItemBase):
id: int
created_at: datetime
model_config = {"from_attributes": True}
Field adiciona restrições e metadados. model_config = {"from_attributes": True} permite que o Pydantic leia atributos de um objeto ORM, para que você possa retornar uma linha do banco de dados diretamente e tê-la serializada. A separação entre ItemCreate e ItemOut é deliberada: o modelo de entrada não possui id, e o modelo de saída pode expor campos que o cliente não pode definir.
Use um modelo Pydantic como o tipo do corpo (body) e o FastAPI analisa, valida e entrega a você um objeto tipado.
@app.post("/items", response_model=ItemOut, status_code=201)
async def create_item(item: ItemCreate):
return await repo.create(item)
Se o corpo for inválido, o FastAPI retorna um erro 422 com uma lista dos campos exatos que falharam. Você não precisou escrever sequer uma linha de validação.
Corpos de requisição e validação
O FastAPI distingue as fontes de dados por posição e tipo:
- Parâmetros de path vêm da URL.
- Parâmetros de query são todo o restante na assinatura.
- Um modelo Pydantic é o corpo da requisição (request body).
Header,Cookie,FormeFilemarcam essas entradas especiais.
from fastapi import Header, Query
@app.get("/search")
async def search(
q: str = Query(min_length=1, max_length=50),
limit: int = Query(default=20, ge=1, le=100),
user_agent: str | None = Header(default=None),
):
return {"q": q, "limit": limit, "user_agent": user_agent}
As restrições nos parâmetros de query são aplicadas da mesma forma que nos modelos, e a documentação gerada exibe esses limites. Essa consistência é a razão pela qual o código do FastAPI é tão compacto.
Documentação automática
Como o schema é gerado a partir do código, a documentação nunca fica desatualizada. Inicie o app e abra /docs para o Swagger UI, onde você pode chamar os endpoints diretamente, ou /redoc para uma referência mais limpa.
app = FastAPI(
title="Inventory API",
version="1.0.0",
description="Items, stock levels and orders.",
)
Você pode adicionar resumos, descrições e exemplos às operações de path e aos campos, e eles aparecerão na documentação. Para equipes de frontend, o mesmo schema pode gerar clientes tipados, mantendo a API e seus consumidores em sincronia. Esse é o benefício de tratar os tipos como o contrato.
Dependências
Uma dependência é uma função que o FastAPI chama antes do seu endpoint e cujo valor de retorno é injetado. Depends() é o mecanismo, e ele é composto.
from fastapi import Depends, HTTPException
async def get_session() -> AsyncSession:
async with SessionLocal() as session:
yield session
async def get_current_user(
token: str = Depends(get_token),
session: AsyncSession = Depends(get_session),
) -> User:
user = await authenticate(session, token)
if user is None:
raise HTTPException(status_code=401, detail="Not authenticated")
return user
@app.get("/me", response_model=UserOut)
async def read_me(user: User = Depends(get_current_user)):
return user
As dependências podem ser síncronas ou assíncronas, podem usar yield para executar a limpeza após a resposta e são armazenadas em cache dentro de uma requisição, portanto get_session é chamado apenas uma vez, mesmo que várias dependências precisem dele. Elas são o local idiomático para sessões de banco de dados, autenticação, paginação e feature flags.
Async versus sync
O FastAPI suporta endpoints def e async def, e essa escolha é importante.
- Um endpoint
async defé executado no event loop. Toda chamada de I/O dentro dele deve ser aguardada (awaited) por meio de uma biblioteca async, caso contrário, ele bloqueará o loop para todas as outras requisições. - Um endpoint
defcomum é executado em um thread pool, portanto, chamadas bloqueantes são seguras, mas cada requisição ocupa uma thread.
# Async: use an async driver and await it
@app.get("/items")
async def list_items(session: AsyncSession = Depends(get_session)):
result = await session.execute(select(Item))
return result.scalars().all()
# Sync: blocking code is fine; FastAPI offloads it
@app.get("/report")
def build_report():
return generate_report() # CPU or blocking I/O
A regra é simples: async do início ao fim, ou sync do início ao fim. Misturar uma chamada bloqueante em um endpoint async def é o bug de performance mais comum no FastAPI, e ele permanece invisível até que o tráfego aumente.
Erros e códigos de status
Lance HTTPException para retornar um erro com um código de status e detalhes em JSON. O FastAPI o transforma em uma resposta e documenta o código de status.
from fastapi import HTTPException, status
@app.get("/items/{item_id}", response_model=ItemOut)
async def read_item(item_id: int):
item = await repo.get(item_id)
if item is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
return item
Para manter um formato de erro consistente em toda a API, registre exception handlers que convertam erros do framework e do domínio em um único envelope. Mantenha o campo detail estável; os clientes irão parseá-lo.
Tarefas em segundo plano (Background tasks)
BackgroundTasks executa tarefas após a resposta ser enviada, no mesmo processo. É a opção leve para enviar um e-mail ou gravar uma linha de auditoria.
from fastapi import BackgroundTasks
def send_receipt(email: str) -> None:
...
@app.post("/orders", status_code=201)
async def create_order(
order: OrderCreate,
tasks: BackgroundTasks,
):
saved = await repo.create(order)
tasks.add_task(send_receipt, saved.email)
return saved
Tarefas em segundo plano não são uma fila de jobs. Elas rodam no mesmo processo, portanto, são perdidas se o worker reiniciar e competem com o processamento de requisições. Para qualquer coisa que exija durabilidade, utilize Celery, RQ ou uma fila gerenciada.
Bancos de Dados
O FastAPI não possui uma camada de banco de dados, então você deve escolher a sua. As escolhas mais comuns são SQLAlchemy com um engine async ou SQLModel, que combina SQLAlchemy com Pydantic. O padrão de dependência fornece a sessão e a fecha após a requisição.
# app/db.py
from collections.abc import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
engine = create_async_engine(settings.database_url, pool_pre_ping=True)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
async def get_session() -> AsyncIterator[AsyncSession]:
async with SessionLocal() as session:
yield session
@router.post("", response_model=ItemOut, status_code=201)
async def create_item(
payload: ItemCreate,
session: AsyncSession = Depends(get_session),
):
item = Item(**payload.model_dump())
session.add(item)
await session.commit()
await session.refresh(item)
return item
Use expire_on_commit=False para que os objetos permaneçam utilizáveis após o commit e mantenha as transações dentro da requisição. Se preferir um driver síncrono, use endpoints def simples para que o FastAPI possa delegá-los ao thread pool.
Segurança
O FastAPI fornece os blocos fundamentais para OAuth2 e JWT sem impor um sistema de autenticação completo. OAuth2PasswordBearer extrai o token, e python-jose ou pyjwt o verifica.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/users/me", response_model=UserOut)
async def read_me(
token: str = Depends(oauth2_scheme),
session: AsyncSession = Depends(get_session),
):
user = await get_user_from_token(session, token)
if user is None:
raise HTTPException(status_code=401, detail="Invalid token")
return user
Armazene hashes de senhas com passlib ou argon2-cffi, nunca em texto simples. Faça o hash e a verificação fora do endpoint, mantenha os segredos nas configurações e adicione rate limiting e CORS de forma deliberada. O FastAPI oferece as ferramentas; a política ainda cabe a você definir.
Configuração e definições
Mantenha a configuração em um único objeto tipado em vez de ler variáveis de ambiente em todo o código. pydantic-settings valida o ambiente na inicialização e oferece a mesma segurança de tipos que seus modelos.
# app/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
database_url: str
secret_key: str
access_token_minutes: int = 30
settings = Settings()
from functools import lru_cache
from app.config import Settings
@lru_cache
def get_settings() -> Settings:
return Settings()
@app.get("/info")
async def info(settings: Settings = Depends(get_settings)):
return {"token_minutes": settings.access_token_minutes}
Como as definições são injetadas como qualquer outra dependência, os testes podem sobrescrevê-las com app.dependency_overrides[get_settings]. A ausência de variáveis obrigatórias causa falha na inicialização, e não na primeira requisição, que é exatamente quando você deseja descobrir o problema.
Testes
TestClient envolve o app em um cliente similar ao WSGI e faz requisições reais no mesmo processo, incluindo validação e substituição de dependências (dependency overrides).
# tests/test_items.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_create_item():
response = client.post("/items", json={"name": "Cup", "price": 9.5})
assert response.status_code == 201
assert response.json()["name"] == "Cup"
def test_invalid_price():
response = client.post("/items", json={"name": "Cup", "price": -1})
assert response.status_code == 422
Use app.dependency_overrides para trocar a sessão do banco de dados por uma de teste, e escreva testes assíncronos com httpx.AsyncClient quando precisar testar endpoints assíncronos diretamente. Como as dependências são explícitas, substituí-las nos testes é simples.
Melhores práticas
- Declare explicitamente os modelos de request e response; nunca aceite ou retorne dicionários puros.
- Mantenha os endpoints
async deftotalmente async, ou usedefpara que o FastAPI delegue o trabalho bloqueante. - Coloque a lógica compartilhada em dependências
Depends()em vez de repeti-la em cada rota. - Divida o app com
APIRoutere atribua a cada router um prefixo e tags. - Adicione restrições com
FieldeQueryem vez de validar manualmente. - Lance
HTTPExceptioncom o status code correto e umdetailestável. - Sobrescreva dependências em testes em vez de fazer patch de componentes internos.
- Mantenha segredos em objetos de configurações lidos do ambiente.
Erros comuns
- Chamar código bloqueante dentro de um endpoint
async defe travar o event loop. - Retornar objetos ORM sem um
response_model, expondo colunas internas. - Esquecer o
from_attributes = Truee questionar por que a serialização falha. - Escrever um único
main.pygigante em vez de utilizar routers e módulos. - Tratar
BackgroundTaskscomo uma fila de jobs durável. - Compartilhar uma única sessão entre requisições ou entre tarefas assíncronas.
- Validar a mesma entrada duas vezes porque o modelo Pydantic foi ignorado.
- Capturar todas as exceções e retornar 200 com um corpo de erro.
Próximos passos
O FastAPI transforma type hints em uma API validada e documentada, e é por isso que ele se tornou o padrão para novos serviços em Python. Para comparar as vantagens e desvantagens, leia o guia do Flask para a abordagem síncrona minimalista e o do Django para um framework completo com ORM e admin. Depois, aprofunde-se no contrato de API com OpenAPI e nas regras de design de [REST APIs](/guides/rest], e certifique-se de que seus endpoints valham a pena ser documentados definindo corretamente o modelo de recurso.