Creación de endpoints con POST

POST se usa para crear o procesar recursos, enviando datos en el cuerpo de la petición (request body). En FastAPI, el body se modela con Pydantic, que valida tipos automáticamente y genera documentación en /docs.

Conceptos clave

  • Decorador: @app.post("/ruta")
  • Cuerpo (body): parámetros tipados con un modelo Pydantic en la función.
  • Códigos de estado: creación exitosa → 201 Created
  • response_model: define el contrato de salida (documentación + validación).

Ejemplo 1: Crear productos (POST /productos)

1) Archivo main.py

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from typing import List

app = FastAPI(
    title="API de Productos",
    version="1.0.0",
    description="Ejemplo de creación de recursos con POST"
)

# --------- Modelos ---------
class Producto(BaseModel):
    codigo: int = Field(gt=0, description="Identificador positivo y único")
    descripcion: str = Field(min_length=1, max_length=100)
    precio: float = Field(gt=0, description="Precio mayor que 0")

# Modelo de salida (opcional, aquí igual al de entrada)
ProductoOut = Producto

# --------- "Base de datos" simulada ---------
productos_db: List[Producto] = [
    Producto(codigo=1, descripcion="Teclado", precio=50.0),
    Producto(codigo=2, descripcion="Mouse", precio=30.0),
]

# --------- Endpoints ---------
@app.get("/productos", response_model=List[ProductoOut], tags=["productos"])
def listar_productos():
    return productos_db

@app.post(
    "/productos",
    response_model=ProductoOut,
    status_code=status.HTTP_201_CREATED,
    tags=["productos"]
)
def crear_producto(producto: Producto):
    # Verificar si ya existe un producto con el mismo código
    for p in productos_db:
        if p.codigo == producto.codigo:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Ya existe un producto con ese código"
            )

    # Agregar a la "base de datos"
    productos_db.append(producto)
    return producto

2) ¿Qué está pasando?

  • Producto modela lo que el cliente envía (codigo, descripcion, precio).
  • Validaciones con Field: gt=0, min_length, etc.
  • @app.post("/productos") recibe un Producto en el body, lo valida y lo guarda en la base simulada.
  • Si ya existe un producto con el mismo código, responde con 409 Conflict.
  • Si todo va bien, devuelve 201 Created y el producto creado.

3) Probar en Windows

Ejecutar:

uvicorn main:app --reload --host 0.0.0.0 --port 8000

Abrir Swagger UI: http://127.0.0.1:8000/docs

Probar POST /productos con un JSON como:

{
  "codigo": 3,
  "descripcion": "Monitor",
  "precio": 200.0
}

Respuesta:

{"codigo":3,"descripcion":"Monitor","precio":200.0}

Luego probar GET /productos para confirmar que se agregó.

💡 Alternativa didáctica: que el servidor asigne el código

Cambiá el modelo de entrada a:

class ProductoCrear(BaseModel):
    descripcion: str
    precio: float

Y en el endpoint generá el nuevo código automáticamente:

nuevo_codigo = max([p.codigo for p in productos_db] or [0]) + 1

🧪 Errores y buenas prácticas

  • 409 Conflict si el codigo ya existe.
  • 422 Unprocessable Entity lo devuelve FastAPI automáticamente cuando el JSON no cumple el modelo (por ejemplo, precio <= 0).
  • Usar response_model para no exponer campos internos y mantener respuestas consistentes.

Problema propuesto

Extender el problema del traductor del tema anterior para poder enviar una palabra nueva, su traducción y el idioma utilizando POST.

  • Endpoint: POST /traducir
  • Body (JSON):
    {
      "palabra": "hola",
      "traduccion": "hello",
      "idioma": "en"
    }
  • Si la palabra no existe, crear la entrada. Si existe, agregar/actualizar la traducción para ese idioma.
  • Validar que idioma sea uno de: en, fr, it. Si no, responder 400.
  • Responder con el JSON resultante de la palabra y sus traducciones.