Python

FastAPI em Python: Fundamentos, Roteamento e Validação com Pydantic na Prática

16 min de leitura

FastAPI em Python: Fundamentos, Roteamento e Validação com Pydantic na Prática

Introdução ao FastAPI FastAPI é um framework web moderno para construir APIs REST em Python, lançado em 2018 por Sebastián Ramírez. Diferentemente de frameworks mais antigos como Flask ou Django, FastAPI foi construído sobre especificações modernas (ASGI) e aproveita type hints do Python 3.6+ para validação automática de dados, geração de documentação interativa e melhor performance. A grande vantagem do FastAPI está em sua abordagem zero boilerplate: você escreve código simples e declarativo, e o framework cuida automaticamente de validação, serialização, documentação OpenAPI e muito mais. Se você já usou frameworks anteriores, vai se surpreender com a quantidade de trabalho que FastAPI elimina. A velocidade de desenvolvimento é comparável à do Flask, mas com segurança e validação de nível enterprise. Fundamentos: Setup e Sua Primeira Aplicação Instalação e Configuração Inicial Para começar, você precisa instalar o FastAPI e um servidor ASGI. A forma mais comum é usar Uvicorn, que é um servidor ASGI de alta performance escrito em Python: Após

<h2>Introdução ao FastAPI</h2>

<p>FastAPI é um framework web moderno para construir APIs REST em Python, lançado em 2018 por Sebastián Ramírez. Diferentemente de frameworks mais antigos como Flask ou Django, FastAPI foi construído sobre especificações modernas (ASGI) e aproveita type hints do Python 3.6+ para validação automática de dados, geração de documentação interativa e melhor performance.</p>

<p>A grande vantagem do FastAPI está em sua abordagem <em>zero boilerplate</em>: você escreve código simples e declarativo, e o framework cuida automaticamente de validação, serialização, documentação OpenAPI e muito mais. Se você já usou frameworks anteriores, vai se surpreender com a quantidade de trabalho que FastAPI elimina. A velocidade de desenvolvimento é comparável à do Flask, mas com segurança e validação de nível enterprise.</p>

<h2>Fundamentos: Setup e Sua Primeira Aplicação</h2>

<h3>Instalação e Configuração Inicial</h3>

<p>Para começar, você precisa instalar o FastAPI e um servidor ASGI. A forma mais comum é usar Uvicorn, que é um servidor ASGI de alta performance escrito em Python:</p>

<pre><code class="language-bash">pip install fastapi uvicorn[standard]</code></pre>

<p>Após a instalação, crie um arquivo chamado <code>main.py</code> com o seguinte código:</p>

<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)

def read_root():

return {&quot;mensagem&quot;: &quot;Olá, mundo!&quot;}</code></pre>

<p>Para executar a aplicação:</p>

<pre><code class="language-bash">uvicorn main:app --reload</code></pre>

<p>O parâmetro <code>--reload</code> reinicia o servidor automaticamente quando você modifica o código (útil durante desenvolvimento). A aplicação estará disponível em <code>http://localhost:8000</code>. Acesse também <code>http://localhost:8000/docs</code> para ver a documentação interativa Swagger gerada automaticamente.</p>

<h3>Entendendo a Estrutura Básica</h3>

<p>Um projeto FastAPI típico é bastante simples: você cria uma instância da classe <code>FastAPI</code>, depois decora funções Python com decoradores HTTP (<code>@app.get</code>, <code>@app.post</code>, etc.). Cada função decorada é uma operação (endpoint) que pode receber parâmetros e retornar dados. O retorno é automaticamente convertido para JSON.</p>

<p>Quando você acessa um endpoint, o FastAPI executa a função correspondente e serializa o retorno. Isso é possível porque Python 3.6+ suporta type hints, permitindo que o framework saiba exatamente que tipo de dado esperar e retornar.</p>

<pre><code class="language-python">from fastapi import FastAPI

from datetime import datetime

app = FastAPI()

@app.get(&quot;/info&quot;)

def get_info():

return {

&quot;timestamp&quot;: datetime.now(),

&quot;status&quot;: &quot;online&quot;

}</code></pre>

<p>Quando você faz uma requisição GET para <code>/info</code>, FastAPI serializa automaticamente o <code>datetime</code> para string ISO 8601. Sem type hints explícitos, isso ainda funcionaria, mas você perderia validação e documentação automática.</p>

<h2>Roteamento Avançado</h2>

<h3>Parâmetros de Rota e Query</h3>

<p>FastAPI diferencia automaticamente parâmetros de rota (path parameters) de query parameters baseado em como você declara a função. Um parâmetro declarado na string da rota é um path parameter; um parâmetro da função que não está na rota é um query parameter.</p>

<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

/usuarios/123 -&gt; id=123 (path parameter)

@app.get(&quot;/usuarios/{id}&quot;)

def get_usuario(id: int):

return {&quot;usuario_id&quot;: id, &quot;nome&quot;: &quot;João&quot;}

/produtos?categoria=eletrônicos&amp;limite=10

@app.get(&quot;/produtos&quot;)

def listar_produtos(categoria: str, limite: int = 10):

return {

&quot;categoria&quot;: categoria,

&quot;limite&quot;: limite,

&quot;produtos&quot;: []

}</code></pre>

<p>Note que <code>limite</code> tem um valor padrão (10). Isso o torna opcional na query string. Se o cliente não enviar <code>?limite=...</code>, o valor padrão será usado. FastAPI valida automaticamente: se o cliente enviar <code>?limite=abc</code>, você receberá um erro 422 (Unprocessable Entity) com mensagem clara sobre o tipo esperado.</p>

<h3>Múltiplos Métodos HTTP no Mesmo Caminho</h3>

<p>É comum ter diferentes operações no mesmo caminho, usando métodos HTTP diferentes. FastAPI suporta todos os métodos padrão:</p>

<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

Simulando um banco de dados em memória

usuarios = [

{&quot;id&quot;: 1, &quot;nome&quot;: &quot;Alice&quot;, &quot;email&quot;: &quot;alice@example.com&quot;},

{&quot;id&quot;: 2, &quot;nome&quot;: &quot;Bob&quot;, &quot;email&quot;: &quot;bob@example.com&quot;}

]

@app.get(&quot;/usuarios/{id}&quot;)

def obter_usuario(id: int):

for usuario in usuarios:

if usuario[&quot;id&quot;] == id:

return usuario

return {&quot;erro&quot;: &quot;Usuário não encontrado&quot;}

@app.put(&quot;/usuarios/{id}&quot;)

def atualizar_usuario(id: int, nome: str, email: str):

for usuario in usuarios:

if usuario[&quot;id&quot;] == id:

usuario[&quot;nome&quot;] = nome

usuario[&quot;email&quot;] = email

return usuario

return {&quot;erro&quot;: &quot;Usuário não encontrado&quot;}

@app.delete(&quot;/usuarios/{id}&quot;)

def deletar_usuario(id: int):

global usuarios

usuarios = [u for u in usuarios if u[&quot;id&quot;] != id]

return {&quot;mensagem&quot;: &quot;Usuário deletado&quot;}</code></pre>

<h3>Path Parameters com Validação</h3>

<p>Você pode adicionar validação diretamente aos parâmetros usando <code>Path</code>:</p>

<pre><code class="language-python">from fastapi import FastAPI, Path

app = FastAPI()

@app.get(&quot;/posts/{post_id}&quot;)

def obter_post(

post_id: int = Path(..., gt=0, description=&quot;ID do post deve ser positivo&quot;)

):

return {&quot;post_id&quot;: post_id, &quot;titulo&quot;: &quot;Meu Post&quot;}</code></pre>

<p>O parâmetro <code>gt=0</code> (greater than) valida que <code>post_id</code> deve ser maior que zero. Se o cliente enviar <code>/posts/-1</code>, receberá um erro 422 com mensagem automática. O <code>...</code> (Ellipsis) indica que o parâmetro é obrigatório.</p>

<h2>Validação com Pydantic</h2>

<h3>Modelos Pydantic para Request Bodies</h3>

<p>Quando você precisa aceitar dados complexos no corpo da requisição (POST, PUT), use modelos Pydantic. Um modelo Pydantic é uma classe que herda de <code>BaseModel</code> e define a estrutura esperada dos dados:</p>

<pre><code class="language-python">from fastapi import FastAPI

from pydantic import BaseModel

app = FastAPI()

class Usuario(BaseModel):

nome: str

email: str

idade: int

ativo: bool = True

@app.post(&quot;/usuarios&quot;)

def criar_usuario(usuario: Usuario):

return {

&quot;mensagem&quot;: &quot;Usuário criado com sucesso&quot;,

&quot;usuario&quot;: usuario

}</code></pre>

<p>Quando um cliente faz uma requisição POST para <code>/usuarios</code> com JSON no corpo, FastAPI automaticamente:</p>

<ol>

<li>Valida que o JSON contém <code>nome</code> (string), <code>email</code> (string) e <code>idade</code> (inteiro)</li>

<li>Valida que <code>ativo</code> é boolean, ou usa o padrão <code>True</code> se não foi enviado</li>

<li>Cria uma instância de <code>Usuario</code> com os dados</li>

<li>Passa essa instância para a função</li>

</ol>

<p>Se o cliente enviar dados inválidos (por exemplo, <code>idade: &quot;abc&quot;</code>), recebe imediatamente um erro 422 com detalhes sobre qual campo está errado.</p>

<h3>Validação Avançada com Field</h3>

<p>Para validações mais complexas, use <code>Field</code> do Pydantic:</p>

<pre><code class="language-python">from fastapi import FastAPI

from pydantic import BaseModel, Field

app = FastAPI()

class Produto(BaseModel):

nome: str = Field(..., min_length=3, max_length=100)

preco: float = Field(..., gt=0, le=1000000)

descricao: str = Field(None, max_length=500)

estoque: int = Field(default=0, ge=0)

@app.post(&quot;/produtos&quot;)

def criar_produto(produto: Produto):

return {

&quot;mensagem&quot;: &quot;Produto criado&quot;,

&quot;produto&quot;: produto

}</code></pre>

<p>Aqui:</p>

<ul>

<li><code>nome</code> deve ter entre 3 e 100 caracteres</li>

<li><code>preco</code> deve ser maior que 0 e no máximo 1.000.000</li>

<li><code>descricao</code> é opcional (pode ser <code>None</code>) e até 500 caracteres</li>

<li><code>estoque</code> começa em 0 se não for fornecido, e deve ser &gt;= 0</li>

</ul>

<h3>Modelos Aninhados e Listas</h3>

<p>Seus modelos Pydantic podem conter outros modelos Pydantic, criando estruturas hierárquicas:</p>

<pre><code class="language-python">from fastapi import FastAPI

from pydantic import BaseModel

from typing import List

app = FastAPI()

class Endereco(BaseModel):

rua: str

cidade: str

cep: str

class Usuario(BaseModel):

nome: str

email: str

endereco: Endereco

class Pedido(BaseModel):

usuarios: List[Usuario]

total: float

@app.post(&quot;/pedidos&quot;)

def criar_pedido(pedido: Pedido):

return {

&quot;mensagem&quot;: &quot;Pedido recebido&quot;,

&quot;quantidade_usuarios&quot;: len(pedido.usuarios),

&quot;total&quot;: pedido.total

}</code></pre>

<p>Quando um cliente envia um JSON como este, FastAPI valida recursivamente toda a hierarquia:</p>

<pre><code class="language-json">{

&quot;usuarios&quot;: [

{

&quot;nome&quot;: &quot;Alice&quot;,

&quot;email&quot;: &quot;alice@example.com&quot;,

&quot;endereco&quot;: {

&quot;rua&quot;: &quot;Rua A, 123&quot;,

&quot;cidade&quot;: &quot;São Paulo&quot;,

&quot;cep&quot;: &quot;01000-000&quot;

}

}

],

&quot;total&quot;: 99.99

}</code></pre>

<h3>Validadores Customizados</h3>

<p>Às vezes validação simples não é suficiente. Pydantic permite definir validadores customizados:</p>

<pre><code class="language-python">from fastapi import FastAPI

from pydantic import BaseModel, validator

import re

app = FastAPI()

class Usuario(BaseModel):

nome: str

email: str

telefone: str

@validator(&#039;email&#039;)

def validar_email(cls, v):

if &#039;@&#039; not in v or &#039;.&#039; not in v:

raise ValueError(&#039;Email inválido&#039;)

return v

@validator(&#039;telefone&#039;)

def validar_telefone(cls, v):

apenas_numeros = re.sub(r&#039;\D&#039;, &#039;&#039;, v)

if len(apenas_numeros) &lt; 10:

raise ValueError(&#039;Telefone deve ter pelo menos 10 dígitos&#039;)

return v

@app.post(&quot;/usuarios&quot;)

def criar_usuario(usuario: Usuario):

return usuario</code></pre>

<p>O decorador <code>@validator</code> recebe o nome do campo e a função é chamada automaticamente durante validação. Se você raise uma <code>ValueError</code>, Pydantic converte em erro 422 automático.</p>

<h2>Resposta Estruturada e Documentação</h2>

<h3>Modelos de Resposta</h3>

<p>É boa prática definir modelos Pydantic também para as respostas, garantindo que a documentação automática seja precisa:</p>

<pre><code class="language-python">from fastapi import FastAPI

from pydantic import BaseModel

app = FastAPI()

class Usuario(BaseModel):

id: int

nome: str

email: str

class UsuarioResposta(BaseModel):

mensagem: str

usuario: Usuario

@app.get(&quot;/usuarios/{id}&quot;, response_model=UsuarioResposta)

def obter_usuario(id: int):

usuario = {&quot;id&quot;: id, &quot;nome&quot;: &quot;João&quot;, &quot;email&quot;: &quot;joao@example.com&quot;}

return {

&quot;mensagem&quot;: &quot;Usuário encontrado&quot;,

&quot;usuario&quot;: usuario

}</code></pre>

<p>O parâmetro <code>response_model</code> informa a FastAPI exatamente que estrutura será retornada. Isso:</p>

<ol>

<li>Valida a resposta antes de enviar (se houver inconsistência, você descobre no desenvolvimento)</li>

<li>Documenta automaticamente a resposta no Swagger</li>

<li>Permite exclusão automática de campos sensíveis</li>

</ol>

<h3>Documentação Interativa</h3>

<p>FastAPI gera documentação automática baseada em seus modelos e type hints. Existem dois formatos padrão:</p>

<ul>

<li><strong>Swagger UI</strong>: <code>http://localhost:8000/docs</code> (mais visual)</li>

<li><strong>ReDoc</strong>: <code>http://localhost:8000/redoc</code> (mais clara para leitura)</li>

</ul>

<p>Para adicionar descrições às suas operações:</p>

<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI(

title=&quot;API de Usuários&quot;,

description=&quot;Gerencia usuários da aplicação&quot;,

version=&quot;1.0.0&quot;

)

@app.get(

&quot;/usuarios/{id}&quot;,

summary=&quot;Obter um usuário&quot;,

description=&quot;Retorna os dados de um usuário específico pelo ID&quot;,

tags=[&quot;Usuários&quot;]

)

def obter_usuario(id: int):

return {&quot;id&quot;: id, &quot;nome&quot;: &quot;João&quot;}</code></pre>

<p>Os parâmetros <code>summary</code>, <code>description</code> e <code>tags</code> aparecem na documentação interativa. Você também pode documentar parâmetros:</p>

<pre><code class="language-python">from fastapi import FastAPI, Query

app = FastAPI()

@app.get(&quot;/buscar&quot;)

def buscar(

q: str = Query(..., min_length=3, description=&quot;Termo de busca com mínimo 3 caracteres&quot;)

):

return {&quot;busca&quot;: q}</code></pre>

<h2>Conclusão</h2>

<p>Nesta aula, cobrimos os fundamentos essenciais para dominar FastAPI. Primeiro, aprendemos que FastAPI é um framework ASGI moderno que elimina boilerplate através de type hints, oferecendo validação, documentação e serialização automáticas—o que o diferencia fundamentalmente de frameworks mais antigos. Segundo, entendemos que o roteamento no FastAPI é intuitivo e expressivo: parâmetros de rota e query são declarados naturalmente na assinatura da função, e validação acontece automaticamente. Por último, dominamos Pydantic não apenas para receber dados (request bodies), mas como forma de documentar e validar suas APIs de maneira declarativa, criando modelos reutilizáveis, aninhados, com validadores customizados e resposta estruturada.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener noreferrer">Documentação oficial do FastAPI</a></li>

<li><a href="https://docs.pydantic.dev/" target="_blank" rel="noopener noreferrer">Documentação do Pydantic v2</a></li>

<li><a href="https://www.uvicorn.org/" target="_blank" rel="noopener noreferrer">Uvicorn - ASGI Server</a></li>

<li><a href="https://www.python.org/dev/peps/pep-0593/" target="_blank" rel="noopener noreferrer">PEP 593 - Flexible function and variable annotations</a></li>

<li><a href="https://realpython.com/fastapi-modern-python-web-apis/" target="_blank" rel="noopener noreferrer">Real Python - FastAPI by Example</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado
Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado

Funções como Objetos de Primeira Classe Em Python, tudo é um objeto — incluin...

Guia Completo de Variáveis de Ambiente em Python: python-dotenv e pydantic-settings
Guia Completo de Variáveis de Ambiente em Python: python-dotenv e pydantic-settings

Por que Variáveis de Ambiente Importam Variáveis de ambiente são valores conf...

Testes Parametrizados e Property-Based Testing com Hypothesis: Do Básico ao Avançado
Testes Parametrizados e Property-Based Testing com Hypothesis: Do Básico ao Avançado

O Problema Tradicional dos Testes Unitários Quando começamos a escrever teste...