Conectar FastAPI a SQLite con SQLModel

Resumen

Conectar tu API con una base de datos real es el paso que transforma un prototipo en una aplicación funcional. Aquí verás cómo usar SQLModel y FastAPI para definir tablas, crear claves primarias y persistir customers con sesiones SQL, todo pensado para desarrolladores Python que ya manejan endpoints básicos.

Por qué aparece el error de primary key en SQLModel

Al ejecutar la aplicación por primera vez después de integrar SQLModel, la consola lanza un error porque no encuentra ninguna primary key. Esto ocurre cuando el campo id queda definido como None sin indicarle a la base de datos cómo tratarlo.

La solución viene de una clase que SQLModel expone llamada Field, que conecta automáticamente un atributo de Pydantic con una columna de base de datos. Para declarar una clave primaria, asignas un valor por defecto y marcas la opción correspondiente:

python id: int | None = Field(default=None, primary_key=True)

Con esto, la base de datos se encarga de autoincrementar el ID uno a uno sin que tú tengas que generarlo manualmente.

¿Qué hace Field en SQLModel? Es la clase que une Pydantic con SQLAlchemy. Si un campo no la usa, ese campo no se guarda en la base de datos.

Cómo aseguras que todos los campos se persistan

Un detalle fácil de pasar por alto: cualquier atributo del modelo que no tenga Field se queda fuera de la tabla. Por eso, después de configurar el id, conviene aplicar Field(default=None) al resto de propiedades del customer para que todos los campos se guarden en la base de datos.

Cómo crear las tablas automáticamente al iniciar FastAPI

Después de definir los modelos, el archivo db.sqlite3 aún no existe en el proyecto. La razón es simple: las tablas no se crean solas, hace falta ejecutar un comando explícito.

El flujo recomendado es definir una función que reciba la aplicación FastAPI y use el metadata de SQLModel junto al motor de base de datos:

python def create_all_tables(app: FastAPI): SQLModel.metadata.create_all(engine) yield

Qué es el lifespan en FastAPI y cuándo conviene usarlo

FastAPI ofrece un parámetro llamado lifespan que recibe una función ejecutada al iniciar y al cerrar la aplicación. La separación entre arranque y cierre se marca con un yield dentro del método: lo que va antes corre al levantar el servidor, lo que va después corre al apagarlo.

En main.py importas create_all_tables y se lo pasas a la instancia de FastAPI. Al ejecutar fastapi dev, la aplicación arranca correctamente y aparece el archivo nuevo de SQLite en el proyecto.

Para verificar, abres una terminal y corres sqlite3 con la ruta de la base de datos. El comando .tables confirma que la tabla customer fue creada, aunque al hacer select * from customer no devuelve filas todavía porque no se ha insertado ningún registro.

Cómo guardar y listar customers usando sesiones SQL

El endpoint de creación originalmente almacenaba customers en una variable en memoria. El cambio consiste en reemplazar esa lógica por una sesión de base de datos que ejecute la inserción real.

El patrón tiene tres pasos clave:

  • session.add(customer) para registrar el objeto en la sesión.
  • session.commit() para que SQLModel genere y ejecute la sentencia SQL.
  • session.refresh(customer) para actualizar la variable en memoria con los datos reales, incluido el ID generado por la base.

¿Por qué hay que hacer refresh después del commit? Porque el ID se genera del lado de la base de datos. Sin refrescar, tu objeto en memoria no conoce ese valor.

Una nota importante sobre SQLModel: las acciones SQL solo se ejecutan cuando llamas a commit. Sin ese paso, la sentencia se queda preparada pero nunca llega al motor.

Cómo construir un endpoint que liste registros desde SQLite

Para el endpoint de listado se usa la misma sesión y dos piezas extra: exec, que ejecuta transacciones SQL, y select, que se importa desde sqlmodel y arma la consulta.

python result = session.exec(select(Customer)).all() return result

El método select(Customer) indica que quieres todas las entidades de ese tipo, y .all() devuelve la lista completa. Como el endpoint ya está tipado para responder una lista de customers, puedes retornar el resultado directamente.

Al probar desde la documentación interactiva con try it out, aparece el customer creado previamente, por ejemplo Luis Profe Platzi con ID 1, dentro de un array JSON.

Tu reto: endpoint para obtener un customer por ID

Con los endpoints de crear y listar funcionando contra SQLite, queda un caso por resolver: obtener un customer específico a partir de su ID. La idea es que reutilices la sesión que ya inyectas en los demás endpoints y manejes el caso en el que el ID no existe en la base.

¿Cómo lo resolverías? Cuéntame tu implementación en los comentarios.