Métodos POST

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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 caracteres
  • email debe seguir un patrón básico de email
  • edad debe estar entre 18 y 100 años
  • telefono 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

⭐⭐⭐⭐⭐
4.9/5 valoración