Manejo de errores y excepciones

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

HTTPException y códigos de error

Cuando desarrollamos APIs, es inevitable que surjan situaciones donde algo no funciona como esperamos. Un usuario puede solicitar un recurso que no existe, enviar datos incorrectos o intentar acceder a algo sin permisos. En lugar de que nuestra aplicación se rompa, necesitamos manejar estos errores de forma elegante y comunicar al cliente qué ha ocurrido.

FastAPI proporciona la clase HTTPException para gestionar estos escenarios de error de manera profesional. Esta excepción nos permite devolver respuestas HTTP apropiadas con códigos de estado específicos y mensajes descriptivos.

Uso básico de HTTPException

La forma más sencilla de usar HTTPException es importarla desde FastAPI y lanzarla cuando detectemos un error:

from fastapi import FastAPI, HTTPException

app = FastAPI()

usuarios = [
    {"id": 1, "nombre": "Ana", "email": "ana@email.com"},
    {"id": 2, "nombre": "Carlos", "email": "carlos@email.com"}
]

@app.get("/usuarios/{usuario_id}")
def obtener_usuario(usuario_id: int):
    for usuario in usuarios:
        if usuario["id"] == usuario_id:
            return usuario
    
    # Si llegamos aquí, el usuario no existe
    raise HTTPException(status_code=404, detail="Usuario no encontrado")

En este ejemplo, cuando un cliente solicita un usuario que no existe, la aplicación no se rompe. En su lugar, devuelve una respuesta HTTP 404 con un mensaje claro.

Códigos de error más comunes

Los códigos de estado HTTP comunican el resultado de una petición. Estos son los más útiles para APIs básicas:

  • 400 (Bad Request): Los datos enviados por el cliente son incorrectos o están mal formateados
  • 404 (Not Found): El recurso solicitado no existe
  • 422 (Unprocessable Entity): Los datos tienen el formato correcto pero contienen errores de validación

Veamos ejemplos prácticos de cada uno:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Usuario(BaseModel):
    nombre: str
    email: str
    edad: int

usuarios = []

@app.post("/usuarios")
def crear_usuario(usuario: Usuario):
    # Validación de negocio personalizada
    if usuario.edad < 18:
        raise HTTPException(
            status_code=400, 
            detail="La edad debe ser mayor o igual a 18 años"
        )
    
    # Verificar si el email ya existe
    for u in usuarios:
        if u["email"] == usuario.email:
            raise HTTPException(
                status_code=400,
                detail="Ya existe un usuario con este email"
            )
    
    nuevo_usuario = {
        "id": len(usuarios) + 1,
        "nombre": usuario.nombre,
        "email": usuario.email,
        "edad": usuario.edad
    }
    usuarios.append(nuevo_usuario)
    return nuevo_usuario

Personalización de mensajes de error

Los mensajes de error deben ser claros y útiles para quien consume la API. Evita mensajes técnicos complejos y opta por explicaciones sencillas:

@app.get("/productos/{producto_id}")
def obtener_producto(producto_id: int):
    if producto_id <= 0:
        raise HTTPException(
            status_code=400,
            detail="El ID del producto debe ser un número positivo"
        )
    
    # Simular búsqueda en base de datos
    producto = buscar_producto(producto_id)
    
    if not producto:
        raise HTTPException(
            status_code=404,
            detail=f"No se encontró el producto con ID {producto_id}"
        )
    
    return producto

def buscar_producto(producto_id: int):
    productos = [
        {"id": 1, "nombre": "Laptop", "precio": 899.99},
        {"id": 2, "nombre": "Mouse", "precio": 25.50}
    ]
    
    for producto in productos:
        if producto["id"] == producto_id:
            return producto
    return None

Validación de parámetros de consulta

También podemos usar HTTPException para validar parámetros de consulta y otros datos de entrada:

@app.get("/usuarios")
def listar_usuarios(limite: int = 10, pagina: int = 1):
    if limite <= 0 or limite > 100:
        raise HTTPException(
            status_code=400,
            detail="El límite debe estar entre 1 y 100"
        )
    
    if pagina <= 0:
        raise HTTPException(
            status_code=400,
            detail="El número de página debe ser mayor a 0"
        )
    
    # Calcular índices para paginación
    inicio = (pagina - 1) * limite
    fin = inicio + limite
    
    return {
        "usuarios": usuarios[inicio:fin],
        "pagina": pagina,
        "limite": limite,
        "total": len(usuarios)
    }

Respuesta estructurada de errores

FastAPI automáticamente estructura las respuestas de error en formato JSON. Cuando lanzas una HTTPException, el cliente recibe algo como esto:

{
    "detail": "Usuario no encontrado"
}

Esta consistencia hace que sea fácil para las aplicaciones cliente manejar los errores de forma predecible. El código de estado HTTP (404, 400, etc.) se incluye en los headers de la respuesta, mientras que el mensaje descriptivo va en el cuerpo.

¿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

Manejadores personalizados de excepciones

Aunque HTTPException cubre la mayoría de casos de error, a veces necesitamos mayor control sobre cómo se manejan las excepciones en nuestra API. Los manejadores personalizados nos permiten interceptar errores específicos y devolver respuestas customizadas de forma consistente en toda la aplicación.

FastAPI nos permite crear manejadores de excepciones que se ejecutan automáticamente cuando ocurre un tipo específico de error. Esto es especialmente útil cuando queremos estandarizar el formato de nuestras respuestas de error o manejar excepciones que no son HTTPException.

Creación de un manejador básico

Para crear un manejador personalizado, utilizamos el decorador @app.exception_handler() junto con el tipo de excepción que queremos capturar:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

class ProductoNoEncontrado(Exception):
    def __init__(self, producto_id: int):
        self.producto_id = producto_id

@app.exception_handler(ProductoNoEncontrado)
async def manejador_producto_no_encontrado(request: Request, exc: ProductoNoEncontrado):
    return JSONResponse(
        status_code=404,
        content={
            "error": "Producto no encontrado",
            "mensaje": f"El producto con ID {exc.producto_id} no existe",
            "codigo": "PRODUCTO_NO_ENCONTRADO"
        }
    )

Ahora podemos usar nuestra excepción personalizada en cualquier endpoint:

productos = [
    {"id": 1, "nombre": "Laptop", "precio": 899.99},
    {"id": 2, "nombre": "Mouse", "precio": 25.50}
]

@app.get("/productos/{producto_id}")
def obtener_producto(producto_id: int):
    for producto in productos:
        if producto["id"] == producto_id:
            return producto
    
    # Lanzar nuestra excepción personalizada
    raise ProductoNoEncontrado(producto_id)

Manejador para errores de validación

Podemos crear manejadores para errores de validación que proporcionen información más detallada que los mensajes por defecto:

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

@app.exception_handler(RequestValidationError)
async def manejador_validacion(request: Request, exc: RequestValidationError):
    errores_detallados = []
    
    for error in exc.errors():
        errores_detallados.append({
            "campo": " -> ".join(str(loc) for loc in error["loc"]),
            "mensaje": error["msg"],
            "valor_recibido": error.get("input")
        })
    
    return JSONResponse(
        status_code=422,
        content={
            "error": "Datos de entrada inválidos",
            "detalles": errores_detallados
        }
    )

Con este manejador, cuando un usuario envíe datos incorrectos, recibirá una respuesta más clara:

{
    "error": "Datos de entrada inválidos",
    "detalles": [
        {
            "campo": "body -> edad",
            "mensaje": "ensure this value is greater than 0",
            "valor_recibido": -5
        }
    ]
}

Múltiples excepciones personalizadas

Podemos crear diferentes tipos de excepciones para distintos escenarios de error y manejar cada una de forma específica:

class UsuarioNoEncontrado(Exception):
    def __init__(self, usuario_id: int):
        self.usuario_id = usuario_id

class EmailDuplicado(Exception):
    def __init__(self, email: str):
        self.email = email

class EdadInsuficiente(Exception):
    def __init__(self, edad: int):
        self.edad = edad

@app.exception_handler(UsuarioNoEncontrado)
async def manejador_usuario_no_encontrado(request: Request, exc: UsuarioNoEncontrado):
    return JSONResponse(
        status_code=404,
        content={
            "error": "Usuario no encontrado",
            "mensaje": f"No existe usuario con ID {exc.usuario_id}",
            "sugerencia": "Verifica que el ID sea correcto"
        }
    )

@app.exception_handler(EmailDuplicado)
async def manejador_email_duplicado(request: Request, exc: EmailDuplicado):
    return JSONResponse(
        status_code=400,
        content={
            "error": "Email ya registrado",
            "mensaje": f"El email {exc.email} ya está en uso",
            "sugerencia": "Usa un email diferente o inicia sesión"
        }
    )

@app.exception_handler(EdadInsuficiente)
async def manejador_edad_insuficiente(request: Request, exc: EdadInsuficiente):
    return JSONResponse(
        status_code=400,
        content={
            "error": "Edad insuficiente",
            "mensaje": f"La edad {exc.edad} no cumple los requisitos mínimos",
            "requisito": "Debes ser mayor de 18 años"
        }
    )

Uso práctico en endpoints

Ahora podemos usar estas excepciones en nuestros endpoints de forma limpia y expresiva:

from pydantic import BaseModel

class Usuario(BaseModel):
    nombre: str
    email: str
    edad: int

usuarios = []

@app.post("/usuarios")
def crear_usuario(usuario: Usuario):
    # Validar edad
    if usuario.edad < 18:
        raise EdadInsuficiente(usuario.edad)
    
    # Verificar email duplicado
    for u in usuarios:
        if u["email"] == usuario.email:
            raise EmailDuplicado(usuario.email)
    
    nuevo_usuario = {
        "id": len(usuarios) + 1,
        "nombre": usuario.nombre,
        "email": usuario.email,
        "edad": usuario.edad
    }
    usuarios.append(nuevo_usuario)
    return nuevo_usuario

@app.get("/usuarios/{usuario_id}")
def obtener_usuario(usuario_id: int):
    for usuario in usuarios:
        if usuario["id"] == usuario_id:
            return usuario
    
    raise UsuarioNoEncontrado(usuario_id)

Manejador genérico para errores inesperados

Es recomendable tener un manejador genérico que capture cualquier excepción no prevista y evite que la aplicación se rompa:

@app.exception_handler(Exception)
async def manejador_generico(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={
            "error": "Error interno del servidor",
            "mensaje": "Ha ocurrido un error inesperado",
            "codigo": "ERROR_INTERNO"
        }
    )

Los manejadores personalizados nos dan flexibilidad total para controlar cómo se comunican los errores a los clientes de nuestra API. Esto resulta en una experiencia más consistente y profesional, donde cada tipo de error tiene su propio formato y mensaje apropiado.

Aprendizajes de esta lección

  • Comprender el uso de HTTPException para gestionar errores comunes en APIs.
  • Aprender a personalizar mensajes de error claros y útiles para el cliente.
  • Implementar validaciones de parámetros y datos de entrada con respuestas adecuadas.
  • Crear manejadores personalizados de excepciones para controlar errores específicos.
  • Desarrollar un manejador genérico para capturar errores inesperados y mantener la estabilidad de 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