Modelos de entrada y salida en FastAPI

Resumen

La validación de datos con Pydantic dentro de FastAPI te permite separar lo que recibes de lo que devuelves en cada endpoint. Esto es clave cuando quieres ocultar campos sensibles, como un ID generado en el backend, y aun así responder con la información completa al cliente.

Por qué separar los modelos de entrada y salida en FastAPI

Cuando defines un modelo único con todos los campos, la documentación de tu API termina pidiendo datos que el usuario no debería enviar, como el ID. Ese ID se genera en la base de datos, no lo aporta quien hace el request.

La solución es crear modelos diferentes para cada propósito y aprovechar la herencia de Python para no repetir código.

Cómo creo un modelo base con herencia en Pydantic

En el archivo models.py defines una clase base con los campos comunes (nombre, descripción, email, edad). Luego creas dos clases hijas:

  • CustomerCreate: hereda de la base y se usa para recibir datos. No incluye el ID.
  • Customer: hereda de la base y agrega el ID. Se usa para responder y para guardar en la lista que simula la base de datos.

El ID lo defines como int | None = None. Así, en el momento de la creación puede ir vacío y luego se asigna automáticamente.

¿Qué hace model_validate en Pydantic? Toma un diccionario de entrada y lo convierte en una instancia del modelo, validando tipos y restricciones. Si algo falla, FastAPI devuelve un error sin que escribas código extra.

Cómo recibir un modelo y responder con otro en un endpoint

El decorador de FastAPI acepta el parámetro response_model, que indica con qué estructura va a salir la respuesta. Esto te permite recibir un CustomerCreate (sin ID) y responder con un Customer (con ID).

El flujo dentro del endpoint queda así:

  1. Recibes customer_data: CustomerCreate desde el request.
  2. Conviertes ese objeto a diccionario con model_dump().
  3. Validas el diccionario con Customer.model_validate(...) para obtener una instancia con ID.
  4. Asignas el ID y devuelves la instancia.

Con esta separación, la documentación de Swagger ya no pide el ID al crear, pero sí lo muestra al responder.

Cómo simulo una base de datos en memoria con una lista

Mientras conectas una base de datos real, puedes usar una lista como almacenamiento temporal. Defines algo como db_customers: list[Customer] = [] fuera del endpoint.

Dentro del endpoint de creación, calculas el ID con len(db_customers) y luego haces db_customers.append(customer). Esto evita problemas con variables globales y programación asíncrona, donde múltiples hilos podrían sobrescribir un contador.

¿Por qué no usar una variable contador global con asíncrono? Porque varios hilos pueden leer y escribir esa variable al mismo tiempo, generando IDs duplicados o perdidos. Una lista con len() te da una referencia consistente para el ejemplo.

Ten presente que esta lista vive en memoria: si reinicias el servidor, los datos desaparecen. Es solo para practicar el flujo de validación.

Cómo listar customers con response_model y tipado de lista

Para devolver todos los registros, creas un nuevo endpoint de tipo GET con la misma ruta /customers, siguiendo la convención RESTful: misma URL, distinto método HTTP.

La diferencia clave está en el response_model. Como vas a devolver varios elementos, debes tiparlo como list[Customer]. FastAPI usa esa anotación para validar la salida y generar la documentación.

El cuerpo del endpoint simplemente retorna db_customers. Si la lista está vacía, recibes un array vacío. Si has creado registros antes, los ves todos con su ID, nombre, email y edad.

Qué aprendes al separar entrada y salida en FastAPI

Este patrón te enseña tres cosas que vas a repetir en cualquier API real:

  • Validar la entrada con un modelo restringido evita que el cliente envíe campos que no le corresponden.
  • Responder con un modelo distinto te permite enriquecer la salida con datos generados en el servidor.
  • Tipar correctamente las listas en response_model mantiene la documentación alineada con el comportamiento real.

Ahora que tienes los endpoints de crear y listar funcionando, el siguiente paso es construir uno que reciba un ID por la URL y devuelva un solo customer. ¿Cómo lo resolverías tú? Cuéntame en los comentarios qué estructura de ruta usarías.