Contenido del curso
Parámetros y Validación
CRUD en FastAPI
Arquitectura en FastAPI
Bases de Datos y Consultas
Middlewares
Unit Testing
Seguridad y Autenticación
Relaciones uno a muchos con SQLModel
Resumen
Cuando trabajas con APIs reales, los datos casi nunca viven en una sola tabla. Aprender a crear relaciones uno a muchos en FastAPI con SQLModel te permite conectar entidades como clientes y transacciones sin duplicar información, manteniendo la base de datos limpia y consultable.
Esto te sirve si estás construyendo una API con Python que necesita modelar usuarios, pedidos, planes o cualquier entidad que dependa de otra. Aquí vas a ver cómo definir la relación, configurar la foreign key y exponerla en tus endpoints.
¿Por qué relacionar tablas en lugar de duplicar datos?
Imagina que quieres asignar un mismo plan a varios usuarios. Si guardas el plan completo dentro de cada usuario, terminas repitiendo la misma información una y otra vez. Con relaciones, creas el plan una sola vez en su tabla y cada usuario apunta hacia ese registro mediante un identificador.
En el caso del proyecto, cada customer puede tener muchas transacciones, pero cada transacción pertenece a un solo customer. Esa es la definición clásica de una relación uno a muchos.
¿Qué es una foreign key? Es un campo en una tabla que apunta al id de otra tabla. Sirve para garantizar que el valor guardado exista realmente en la tabla referenciada.
¿Cómo se convierte un modelo en tabla con SQLModel?
En el minuto [01:05] aparece el ajuste clave: el modelo Transaction originalmente heredaba solo de un base model, lo que no lo convertía en tabla. Para que SQLModel lo registre en la base de datos, necesitas dos cosas:
- Heredar desde
SQLModel. - Pasar el parámetro
table=Trueen la declaración de la clase.
Después se aplica la misma estrategia que con Customer: crear un TransactionBase con los campos comunes y hacer que el resto de modelos (creación, lectura, tabla) hereden de esa base. Así, cualquier campo que agregues en TransactionBase se propaga al resto.
El id se marca como primary key con default=None, lo que indica que la base de datos lo generará automáticamente.
¿Cómo verificar que la tabla se creó?
Desde la terminal puedes inspeccionar la base de datos SQLite. Estos son los comandos útiles:
.tableslista todas las tablas existentes..schemamuestra la estructura de cada tabla, incluyendo columnas y restricciones.
Si ejecutas el proyecto y luego abres SQLite, verás la tabla transaction con su id, description y amount.
¿Cómo conectar transaction con customer mediante foreign key?
Aquí entra la parte interesante. En el minuto [03:20] se agrega un nuevo campo a Transaction:
python customer_id: int = Field(foreign_key="customer.id")
Este campo le dice a la base de datos que customer_id debe existir como id en la tabla customer. Si borras la base de datos y vuelves a ejecutar el proyecto, al revisar el schema aparece la nueva columna marcada como foreign key hacia customer(id).
Pero la foreign key sola no basta para trabajar cómodo en código Python. Lo siguiente es declarar la relación en ambos modelos para acceder a los objetos relacionados sin escribir consultas manuales.
¿Qué hace Relationship y back_populates en SQLModel?
En Customer defines:
python transactions: list["Transaction"] = Relationship(back_populates="customer")
Y en Transaction defines el lado opuesto:
python customer: Customer = Relationship(back_populates="transactions")
El back_populates le indica a SQLModel cómo se llenan ambos lados de la relación. Las comillas alrededor de "Transaction" en Customer son necesarias porque la clase Transaction aún no está declarada en ese punto del archivo; FastAPI y Python las interpretan como referencias diferidas.
¿Qué pasa si olvido tipar la relación? SQLModel lanza un key error porque no sabe a qué clase asociar el campo. Tipar
customer: Customeres lo que permite resolver la relación correctamente.
¿Cómo construir los endpoints de transactions?
Para mantener orden, conviene separar los routers en archivos. Se crea transactions.py, se importa APIRouter desde FastAPI y se mueve el endpoint de creación que estaba en main.
El flujo de creación queda así:
- Recibir los datos con un modelo
TransactionCreateque incluyecustomer_id. - Buscar el cliente con
session.get(Customer, customer_id). - Si el cliente no existe, lanzar
HTTPExceptionconstatus_code=404y mensaje claro: "Customer doesn't exist". - Validar la data con
Transaction.model_validate(transaction_data). - Guardar con
session.add(), confirmar consession.commit()y refrescar consession.refresh(). - Retornar el objeto creado.
Para listar transacciones, usas session.exec(select(Transaction)).all() y devuelves la lista. El select se importa desde SQLModel.
¿Cómo registrar el router en la app principal?
En el archivo main.py, además de importar el router de customers, debes importar e incluir el de transactions:
python app.include_router(transaction.router)
Y dentro del APIRouter, agrega tags=["transactions"] para que la documentación de Swagger los agrupe visualmente. Sin ese tag, los endpoints aparecen sueltos y cuesta encontrarlos.
Un detalle final: cuando creas un recurso, lo correcto es retornar status_code=201 en lugar del 200 por defecto. Es una convención REST que indica que algo nuevo fue creado.
¿Qué ganas con este patrón de relaciones?
Una vez configurado, puedes acceder a las transacciones de un cliente directamente desde el objeto customer.transactions, sin escribir una query adicional. SQLModel resuelve la relación por ti.
Esto reduce el número de consultas a la base de datos y hace tu código más legible. Si más adelante necesitas relaciones muchos a muchos o uno a uno, la lógica de Relationship y back_populates se mantiene; solo cambian los tipos.
¿Ya intentaste modelar una relación parecida en tu proyecto? Cuéntame en los comentarios qué entidades estás conectando y qué dudas te surgieron al definir la foreign key.