SQLModel na Prática: Como Criar uma API CRUD Completa com FastAPI e Banco de Dados do Zero

Published on: 2026-01-07
Post image
pt sqlmodel fastapi api-crud python-backend desenvolvimento-backend fastapi-tutorial sqlmodel-tutorial api-rest python-web banco-de-dados-sqlite orm-python pydantic sqlalchemy swagger backend-moderno api-em-python web-api crud-api desenvolv

SQLModel é uma biblioteca Python que une duas ideias importantes: a modelagem e persistência de dados do SQLAlchemy (mapeamento objeto-relacional, ou ORM) e a validação/serialização do Pydantic (modelos de dados com tipos e validação automática). Em conjunto com o FastAPI, torna-se simples criar uma API web conectada a um banco de dados, com validação de entrada e documentação automática.

Um objetivo comum nesse cenário é construir uma API CRUD, sigla para Create, Read, Update e Delete (criar, ler, atualizar e apagar registros). O exemplo a seguir organiza um pequeno aplicativo que gerencia uma tabela de “heróis” em um banco SQLite, incluindo instalação de dependências, definição de modelo, criação do banco, rotas HTTP e exemplos de uso.

Visão geral da arquitetura: API, modelo e banco de dados

Uma API (Interface de Programação de Aplicações) baseada em HTTP expõe rotas, como “/heroes/”, para receber e devolver dados em formato JSON. O FastAPI cuida do servidor web, do roteamento e da documentação automática, enquanto o SQLModel define as estruturas de dados e a forma como elas viram tabelas no banco. O banco SQLite é um banco de dados leve, que funciona em um arquivo local, adequado para exemplos e protótipos. A aplicação fica organizada em módulos, normalmente separados em arquivos para modelo, banco e aplicação principal.

Nesse tipo de projeto, o fluxo de dados costuma seguir um padrão: a requisição chega ao FastAPI, o corpo JSON é validado pelo modelo, e então uma sessão de banco executa operações como inserir, buscar ou atualizar registros. A sessão é uma unidade de trabalho do ORM que controla transações e sincronização com o banco. Ao final, a resposta é devolvida como JSON, geralmente com o objeto criado ou encontrado. Essa organização reduz erros e mantém o código previsível.

Instalação das dependências e papel de cada biblioteca

Para executar a API, são necessárias bibliotecas para o framework web, para o servidor ASGI e para a camada de dados. Uvicorn é um servidor ASGI (interface assíncrona de servidor) que executa o FastAPI em desenvolvimento e produção simples. FastAPI oferece rotas, validação, documentação e integração com tipos Python. SQLModel fornece o modelo e integração com banco via SQLAlchemy, mantendo validação semelhante ao Pydantic.

A instalação normalmente é feita com o gerenciador de pacotes do Python. A linha a seguir lista as dependências básicas para o exemplo. Em ambientes reais, é comum fixar versões em um arquivo de requisitos, mas o essencial é ter as três bibliotecas instaladas. Com isso, já é possível iniciar um projeto mínimo com banco e rotas.

pip install fastapi uvicorn sqlmodel

Definição do modelo com SQLModel: campos, tipos e tabela

O modelo descreve a estrutura dos dados e também a tabela no banco quando configurado com table=True. O termo ORM significa que uma classe Python representa uma linha (registro) em uma tabela SQL. No exemplo, a classe Hero representa heróis com id, nome, nome secreto e idade. Tipos como Optional indicam que um campo pode ser nulo (ausente), algo importante para chaves primárias geradas automaticamente e campos não obrigatórios.

O campo id usa Field com primary_key=True para indicar que é a chave primária, que identifica unicamente cada registro. O default=None permite que o banco gere o valor na inserção, o que é comum em IDs auto incrementais. Campos como name e secret_name são obrigatórios por serem strings sem Optional. Já age é opcional, permitindo heróis sem idade cadastrada.

A seguir está um arquivo de modelo completo e funcional. A importação de SQLModel e Field é central para mapear a classe em tabela e definir metadados de coluna. Ao manter a definição em um módulo separado, o projeto tende a ficar mais organizado e reutilizável. Essa classe também é usada como esquema de entrada e saída, pois o SQLModel carrega a validação de dados.

from typing import Optional
from sqlmodel import SQLModel, Field

class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None

Configuração do banco SQLite e criação automática de tabelas

O SQLite armazena o banco em um arquivo, simplificando a configuração por dispensar servidor separado. Para conectar, usa-se uma URL no formato sqlite:///arquivo.db, em que três barras indicam caminho relativo local. O engine (motor) é o componente do SQLAlchemy/SQLModel que gerencia conexões e executa SQL por baixo. O parâmetro echo=True imprime as instruções SQL no console, útil para entender o que está acontecendo.

A função de criação do banco chama SQLModel.metadata.create_all(engine). “Metadata” é um catálogo interno com a descrição das tabelas derivada das classes com table=True. Ao executar create_all, o sistema cria as tabelas que não existem, sem apagar dados existentes. Isso é adequado para exemplos e pequenos projetos, enquanto sistemas maiores costumam usar migrações formais.

O trecho abaixo mostra uma configuração típica em um arquivo dedicado ao banco. A separação do engine e da função de inicialização facilita a importação no aplicativo principal. Além disso, centraliza a definição do banco para que outras partes do sistema usem o mesmo engine. Em aplicações reais, esse módulo também pode concentrar configurações como pool de conexões e parâmetros adicionais.

from sqlmodel import SQLModel, create_engine

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

Aplicação FastAPI: evento de inicialização e sessão de banco

A aplicação principal instancia o FastAPI e registra um evento de inicialização para garantir que as tabelas existam antes de atender requisições. O decorador @app.on_event("startup") executa uma função quando o servidor inicia, ideal para preparar o banco. Outro conceito essencial é a Session, que cria um contexto de trabalho para ler e gravar no banco com segurança. O bloco with Session(engine) as session garante fechamento correto da sessão ao final.

As rotas são funções Python decoradas com métodos HTTP como POST, GET, PUT e DELETE. O parâmetro response_model define o formato de resposta e força validação/serialização, o que deixa a API mais consistente. Em caso de erro, como buscar um herói inexistente, usa-se HTTPException para devolver status HTTP e mensagem. Esse padrão separa os fluxos de sucesso e falha de forma clara e previsível.

O arquivo principal a seguir reúne inicialização, importações e rotas CRUD. A função de startup cria tabelas, e cada rota abre uma sessão para executar operações. O uso de select cria uma consulta que busca dados do banco pelo modelo. Operações de escrita exigem commit para persistir e, em alguns casos, refresh para atualizar o objeto com valores gerados pelo banco (como o id).

from fastapi import FastAPI, HTTPException
from sqlmodel import Session, select
from models import Hero
from database import engine, create_db_and_tables

app = FastAPI()

@app.on_event("startup")
def on_startup():
    create_db_and_tables()

@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero

@app.get("/heroes/", response_model=list[Hero])
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

@app.get("/heroes/{hero_id}", response_model=Hero)
def read_hero(hero_id: int):
    with Session(engine) as session:
        hero = session.get(Hero, hero_id)
        if not hero:
            raise HTTPException(status_code=404, detail="Hero not found")
        return hero

@app.put("/heroes/{hero_id}", response_model=Hero)
def update_hero(hero_id: int, hero_update: Hero):
    with Session(engine) as session:
        hero = session.get(Hero, hero_id)
        if not hero:
            raise HTTPException(status_code=404, detail="Hero not found")
        hero.name = hero_update.name
        hero.secret_name = hero_update.secret_name
        hero.age = hero_update.age
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero

@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int):
    with Session(engine) as session:
        hero = session.get(Hero, hero_id)
        if not hero:
            raise HTTPException(status_code=404, detail="Hero not found")
        session.delete(hero)
        session.commit()
        return {"ok": True}

Detalhamento do CRUD: criação, leitura, atualização e remoção

A operação de criação usa o método HTTP POST e recebe um corpo JSON que vira uma instância do modelo validada automaticamente. Ao chamar session.add, o objeto é preparado para inserção e, com commit, a transação é gravada no banco. Em seguida, session.refresh recarrega o objeto para incluir dados gerados no banco, como o id. O retorno do endpoint inclui o herói criado, já com a identificação persistida.

A leitura geral usa GET e executa select(Hero) para buscar todos os registros da tabela. O método session.exec executa a consulta, e all() materializa a lista completa em memória. Em bancos grandes, seria comum paginar resultados, mas o exemplo mantém simplicidade. A leitura de um único item usa session.get por chave primária, o que é direto e eficiente.

A atualização usa PUT e primeiro verifica se o registro existe, evitando atualizar um item inexistente. Em seguida, copia campos do objeto recebido para o objeto persistido, mantendo o mesmo id. Depois, commit grava as alterações e refresh garante que a instância retornada representa o estado final no banco. A remoção usa DELETE, valida existência, chama session.delete e conclui com commit, retornando um JSON simples para indicar sucesso.

Execução do servidor e documentação automática da API

Para iniciar o servidor de desenvolvimento, usa-se o Uvicorn apontando para o objeto “app” no módulo principal. A opção --reload reinicia automaticamente o servidor quando arquivos mudam, acelerando o ciclo de desenvolvimento. Ao iniciar, o evento de startup cria as tabelas e o engine passa a registrar SQL no console se echo estiver habilitado. O servidor então passa a ouvir requisições HTTP em um endereço local.

O FastAPI gera automaticamente uma interface de documentação interativa baseada em OpenAPI, frequentemente exibida como Swagger UI. Essa documentação reflete rotas, modelos de entrada/saída e códigos de resposta. Como os modelos são tipados, o FastAPI consegue montar exemplos e validações de forma consistente. Isso reduz divergências entre implementação e documentação.

O comando abaixo inicia a aplicação no modo comum de desenvolvimento. Com o servidor em execução, a documentação padrão fica disponível no caminho “/docs” do endereço local do servidor. Essa interface permite enviar requisições e observar respostas sem necessidade de ferramentas externas. O comportamento da API permanece o mesmo independentemente do uso da interface ou de chamadas diretas.

uvicorn main:app --reload

Exemplos de requisições e respostas JSON para testar as rotas

Os testes básicos de CRUD podem ser descritos como chamadas HTTP com corpo JSON, retornando JSON como resposta. O endpoint de criação recebe “name”, “secret_name” e opcionalmente “age”, e devolve o registro persistido com “id”. O endpoint de listagem devolve um array de heróis. O endpoint de busca por id devolve um único herói, ou erro 404 quando não existe.

Os exemplos a seguir mostram corpos típicos usados nas operações. Em POST, um JSON sem “id” permite que o banco gere o identificador. Em PUT, um JSON com os campos atualizados substitui os valores, mantendo o id da rota como referência do registro alvo. Em DELETE, não há corpo e a resposta costuma ser uma confirmação simples.

Os exemplos abaixo representam o conteúdo JSON esperado em cada operação principal. Eles ajudam a entender o formato das mensagens trocadas, que é a parte central de uma API REST. Em sistemas mais robustos, é comum separar modelos de criação e atualização para controlar campos permitidos, mas o formato demonstrado já é funcional. A consistência dos nomes de campos é garantida pelo modelo do SQLModel.

POST /heroes/

{ "name": "Iron Man", "secret_name": "Tony Stark", "age": 45 }

GET /heroes/

(sem corpo)

GET /heroes/1

(sem corpo)

PUT /heroes/1

{ "name": "Iron Man", "secret_name": "Anthony Stark", "age": 46 }

DELETE /heroes/1

(sem corpo)

Erros comuns e cenários importantes em APIs CRUD com SQLModel

Um cenário frequente é a tentativa de buscar, atualizar ou apagar um registro inexistente. Nesses casos, o retorno adequado é um 404, indicando que o recurso não foi encontrado, e isso é feito com HTTPException. Outro ponto importante é a diferença entre dados obrigatórios e opcionais, pois campos não opcionais sem valor geram erro de validação antes mesmo de chegar ao banco. Isso protege o sistema de inserir registros incompletos.

Também é comum confundir o papel do commit e do refresh. O commit efetiva a transação, enquanto o refresh sincroniza a instância em memória com o estado no banco, incluindo valores automáticos. Sem commit, as mudanças não ficam persistidas. Sem refresh, a resposta pode não incluir o id recém-gerado em alguns cenários.

Um cuidado adicional envolve atualizar com PUT copiando todos os campos, pois valores ausentes podem virar nulos se forem enviados como null. Em exemplos simples, isso é aceitável, mas em sistemas maiores é comum usar modelos de atualização parcial (PATCH) para mudar apenas campos fornecidos. Mesmo mantendo PUT, a validação de tipos evita que textos sejam gravados onde se espera número, e impede formatos inválidos. Esse conjunto de validações e transações torna o CRUD mais previsível.

Conclusão: integração entre FastAPI, SQLModel e SQLite em um CRUD completo

Uma API CRUD com FastAPI e SQLModel combina roteamento web simples, validação automática e persistência em banco sem excesso de configuração. O modelo define tanto o esquema de dados quanto a tabela, reduzindo duplicação e mantendo consistência. A sessão do banco organiza transações e garante que operações de escrita sejam confirmadas com commit. O SQLite oferece um ponto de partida prático ao armazenar tudo em um arquivo local.

Com rotas para criar, listar, buscar por id, atualizar e remover, o ciclo completo de vida de um registro fica coberto de forma clara. A documentação automática do FastAPI descreve endpoints e modelos, reduzindo ambiguidades durante o uso da API. A estrutura em arquivos separados para modelo, banco e aplicação mantém o projeto legível e fácil de manter. O resultado é uma base sólida para aplicações maiores, preservando simplicidade e segurança por meio de tipagem e validação.