Estructura básica de POST con Request Body
Los métodos POST representan un cambio fundamental en la forma de enviar datos a una API. Mientras que con GET enviamos información a través de parámetros en la URL, POST nos permite enviar datos más complejos y sensibles mediante el request body o cuerpo de la petición.
Diferencias clave entre GET y POST
La principal diferencia radica en dónde y cómo se envían los datos:
- GET: Los datos viajan en la URL como parámetros visibles
- POST: Los datos viajan en el cuerpo de la petición, ocultos de la URL
# GET - datos en la URL
@app.get("/usuarios/{user_id}")
async def obtener_usuario(user_id: int):
return {"usuario_id": user_id}
# POST - datos en el body
@app.post("/usuarios")
async def crear_usuario(datos_usuario: dict):
return {"mensaje": "Usuario creado", "datos": datos_usuario}
Definiendo modelos con Pydantic
Para trabajar con request bodies de forma estructurada, utilizamos modelos de Pydantic que definen la estructura esperada de los datos:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Usuario(BaseModel):
nombre: str
email: str
edad: int
@app.post("/usuarios")
async def crear_usuario(usuario: Usuario):
return {
"mensaje": "Usuario recibido correctamente",
"datos": {
"nombre": usuario.nombre,
"email": usuario.email,
"edad": usuario.edad
}
}
El modelo Usuario
actúa como un contrato que especifica qué campos debe contener el JSON enviado en el body de la petición.
Estructura del endpoint POST
Un endpoint POST típico sigue esta estructura básica:
@app.post("/ruta")
async def nombre_funcion(modelo: ClaseModelo):
# Procesar los datos recibidos
# Simular guardado o procesamiento
# Devolver respuesta
return {"resultado": "datos procesados"}
Ejemplo práctico: API de productos
Veamos un ejemplo completo para crear productos en una tienda online:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Producto(BaseModel):
nombre: str
precio: float
descripcion: Optional[str] = None
categoria: str
@app.post("/productos")
async def crear_producto(producto: Producto):
# Simular el guardado del producto
producto_guardado = {
"id": 123, # ID simulado
"nombre": producto.nombre,
"precio": producto.precio,
"descripcion": producto.descripcion,
"categoria": producto.categoria,
"estado": "activo"
}
return {
"mensaje": "Producto creado exitosamente",
"producto": producto_guardado
}
Enviando datos al endpoint
Para probar nuestro endpoint, el JSON enviado en el body debe coincidir con la estructura del modelo:
{
"nombre": "Laptop Gaming",
"precio": 1299.99,
"descripcion": "Laptop para gaming de alta gama",
"categoria": "Electrónicos"
}
FastAPI automáticamente deserializa este JSON y lo convierte en una instancia del modelo Producto
, permitiendo acceder a los datos como atributos del objeto.
Campos opcionales y valores por defecto
Los modelos pueden incluir campos opcionales con valores por defecto:
class Tarea(BaseModel):
titulo: str
descripcion: str
completada: bool = False # Valor por defecto
prioridad: Optional[str] = "media" # Campo opcional
@app.post("/tareas")
async def crear_tarea(tarea: Tarea):
return {
"tarea_creada": {
"titulo": tarea.titulo,
"descripcion": tarea.descripcion,
"completada": tarea.completada,
"prioridad": tarea.prioridad
}
}
En este caso, si no se envía el campo completada
, automáticamente tomará el valor False
. Si no se envía prioridad
, será "media"
.
Acceso a los datos del modelo
Una vez que FastAPI procesa el request body, podemos acceder a los datos del modelo como propiedades normales de Python:
@app.post("/contacto")
async def procesar_contacto(contacto: Contacto):
# Acceder a los datos directamente
nombre_usuario = contacto.nombre
email_usuario = contacto.email
# Procesar la información
respuesta = f"Gracias {nombre_usuario}, hemos recibido tu mensaje en {email_usuario}"
return {"respuesta": respuesta}
Esta aproximación hace que trabajar con datos complejos sea intuitivo y type-safe, ya que Python y FastAPI conocen exactamente qué tipo de datos esperar en cada campo.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Validación de datos de entrada
La validación automática es una de las características más valiosas de FastAPI cuando trabajamos con métodos POST. Pydantic se encarga de verificar que los datos recibidos cumplan con las reglas definidas en nuestros modelos, proporcionando mensajes de error claros cuando algo no es correcto.
Validaciones básicas por tipo de dato
FastAPI valida automáticamente los tipos de datos definidos en el modelo. Si enviamos un tipo incorrecto, la API responderá con un error detallado:
from pydantic import BaseModel
class Empleado(BaseModel):
nombre: str
salario: float
activo: bool
@app.post("/empleados")
async def crear_empleado(empleado: Empleado):
return {"empleado_creado": empleado.dict()}
Si enviamos un JSON con tipos incorrectos, FastAPI automáticamente rechazará la petición:
{
"nombre": "Juan Pérez",
"salario": "no_es_numero",
"activo": "tal_vez"
}
La respuesta incluirá información específica sobre qué campos tienen errores de validación y por qué.
Validaciones de longitud y formato
Podemos añadir restricciones adicionales usando las funciones de validación de Pydantic:
from pydantic import BaseModel, Field
class PerfilUsuario(BaseModel):
nombre: str = Field(min_length=2, max_length=50)
email: str = Field(pattern=r'^[^@]+@[^@]+\.[^@]+$')
edad: int = Field(ge=18, le=100) # ge = greater equal, le = less equal
telefono: str = Field(min_length=9, max_length=15)
@app.post("/perfiles")
async def crear_perfil(perfil: PerfilUsuario):
return {
"mensaje": "Perfil validado correctamente",
"perfil": perfil.dict()
}
En este ejemplo:
nombre
debe tener entre 2 y 50 caracteresemail
debe seguir un patrón básico de emailedad
debe estar entre 18 y 100 añostelefono
debe tener entre 9 y 15 caracteres
Validaciones numéricas
Para campos numéricos, podemos establecer rangos y restricciones específicas:
class Producto(BaseModel):
nombre: str = Field(min_length=3)
precio: float = Field(gt=0, le=10000) # gt = greater than
descuento: float = Field(ge=0, le=100)
stock: int = Field(ge=0)
@app.post("/productos")
async def agregar_producto(producto: Producto):
precio_final = producto.precio * (1 - producto.descuento / 100)
return {
"producto": producto.nombre,
"precio_original": producto.precio,
"precio_final": precio_final,
"stock_disponible": producto.stock
}
Validaciones con listas de valores permitidos
Podemos restringir campos a valores específicos usando enumeraciones:
from enum import Enum
class EstadoPedido(str, Enum):
pendiente = "pendiente"
procesando = "procesando"
enviado = "enviado"
entregado = "entregado"
class Pedido(BaseModel):
numero_pedido: str = Field(min_length=5)
estado: EstadoPedido
total: float = Field(gt=0)
@app.post("/pedidos")
async def crear_pedido(pedido: Pedido):
return {
"pedido_numero": pedido.numero_pedido,
"estado_actual": pedido.estado,
"total_pedido": pedido.total
}
Solo se aceptarán los valores definidos en EstadoPedido
. Cualquier otro valor generará un error de validación.
Campos opcionales con validaciones
Los campos opcionales también pueden tener validaciones aplicadas cuando se proporcionan:
from typing import Optional
class Comentario(BaseModel):
titulo: str = Field(min_length=5, max_length=100)
contenido: str = Field(min_length=10)
puntuacion: Optional[int] = Field(None, ge=1, le=5)
autor: Optional[str] = Field(None, min_length=2)
@app.post("/comentarios")
async def crear_comentario(comentario: Comentario):
respuesta = {
"titulo": comentario.titulo,
"contenido": comentario.contenido
}
if comentario.puntuacion:
respuesta["puntuacion"] = comentario.puntuacion
if comentario.autor:
respuesta["autor"] = comentario.autor
return {"comentario_creado": respuesta}
Manejo de errores de validación
Cuando las validaciones fallan, FastAPI devuelve automáticamente un código de estado 422 (Unprocessable Entity) junto con detalles específicos del error:
class Evento(BaseModel):
nombre: str = Field(min_length=3)
fecha: str = Field(pattern=r'^\d{4}-\d{2}-\d{2}$') # Formato YYYY-MM-DD
capacidad: int = Field(gt=0, le=1000)
@app.post("/eventos")
async def crear_evento(evento: Evento):
return {
"evento_programado": evento.nombre,
"fecha": evento.fecha,
"plazas_disponibles": evento.capacidad
}
Si enviamos datos inválidos, la respuesta incluirá información detallada sobre cada campo problemático, facilitando la corrección por parte del cliente que consume la API.
Validaciones personalizadas simples
Podemos crear validaciones más específicas usando el decorador @field_validator
:
from pydantic import BaseModel, Field, field_validator
class CuentaBancaria(BaseModel):
titular: str = Field(min_length=2)
numero_cuenta: str = Field(min_length=10, max_length=20)
saldo_inicial: float = Field(ge=0)
@field_validator('numero_cuenta')
def validar_numero_cuenta(cls, v):
if not v.isdigit():
raise ValueError('El número de cuenta solo puede contener dígitos')
return v
@app.post("/cuentas")
async def crear_cuenta(cuenta: CuentaBancaria):
return {
"cuenta_creada": {
"titular": cuenta.titular,
"numero": cuenta.numero_cuenta,
"saldo": cuenta.saldo_inicial
}
}
Esta aproximación nos permite implementar lógica de validación personalizada manteniendo la simplicidad y claridad del código.
Aprendizajes de esta lección
- Comprender la diferencia entre los métodos GET y POST en el envío de datos.
- Aprender a definir modelos de datos con Pydantic para estructurar el request body.
- Implementar endpoints POST que reciban y procesen datos complejos.
- Aplicar validaciones automáticas y personalizadas en los datos recibidos.
- Manejar errores de validación y comprender su respuesta en la API.
Completa FastAPI y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs