Module 5: Error Handling and CORS
Introducción al Módulo 5: Error Handling y CORS
Descripción
Tu API tiene CRUD completo, parámetros validados con Query() y Path(), modelos Pydantic que definen contratos claros para requests y responses. Es una API funcional. Pero hay un problema que no has resuelto: ¿qué pasa cuando las cosas salen mal? Cuando un cliente pide un libro que no existe, ¿qué recibe? Cuando envía un ID duplicado, ¿qué le dice la API? Cuando un frontend en localhost:3000 intenta consumir tu API en localhost:8000, ¿por qué el navegador bloquea la petición? Estas preguntas no se responden con más endpoints ni con más modelos Pydantic. Se responden con error handling profesional y configuración de CORS.
Sin error handling estructurado, tu API es impredecible. Algunos endpoints retornan {"detail": "Not found"}, otros retornan {"error": "libro no encontrado"}, otros simplemente explotan con un 500 Internal Server Error que no le dice nada útil al cliente. El frontend no puede parsear errores de forma consistente porque cada endpoint inventa su propio formato. Y sin CORS, tu API es invisible para cualquier frontend que corra en un dominio diferente — que en desarrollo es prácticamente siempre. Un React app en localhost:3000 no puede hacer fetch a localhost:8000 sin que el navegador lo bloquee. No es un bug de tu código, es una política de seguridad del navegador que necesitas manejar explícitamente.
Este módulo cierra las dos brechas de robustez más importantes de tu API. Después de completarlo, tus errores serán consistentes, informativos y parseables por cualquier cliente. Y tu API será consumible desde cualquier frontend, mobile app o servicio externo que necesite acceder a ella. Es el paso que separa una API que funciona en /docs de una API que funciona en el mundo real.
¿Dónde estamos en la guía?
Estás en el Módulo 5 de 6 de la guía FastAPI Fundamentals:
Módulo 1: Setup y Primera API ✅ (completado)
→ Instalación, uvicorn, endpoints GET, documentación automática
Módulo 2: Path Operations ✅ (completado)
→ GET, POST, PUT, PATCH, DELETE — CRUD completo
Módulo 3: Request y Response ✅ (completado)
→ Query params, Path(), Query(), Body(), filtros, paginación
Módulo 4: Pydantic y Validación ✅ (completado)
→ BaseModel, Field(), validators, modelos separados, response_model
Módulo 5: Error Handling y CORS ← ESTÁS AQUÍ
→ HTTPException, custom handlers, CORS middleware, status codes
Módulo 6: Proyecto Final — To-Do List API
Progresión acumulativa
Cada módulo agrega una capa sobre el anterior:
Módulo 1: Servidor corriendo + endpoints GET básicos
↓ (base)
Módulo 2: + CRUD completo (POST, PUT, PATCH, DELETE)
↓ (operaciones)
Módulo 3: + Parámetros avanzados, filtros, paginación
↓ (datos de entrada)
Módulo 4: + Modelos Pydantic, validación estructurada, contratos
↓ (datos estructurados)
Módulo 5: + Error handling profesional + CORS ← AQUÍ
↓ (robustez)
Módulo 6: To-Do List API (integración total)
El Módulo 4 te dio contratos para los datos: qué acepta tu API y qué retorna cuando todo sale bien. El Módulo 5 define qué pasa cuando las cosas salen mal — y cómo tu API se abre al mundo exterior. Estás casi en la meta: después de este módulo solo queda el proyecto final que integra todo.
Lo que ya dominas vs Lo nuevo
Lo que ya dominas
De los Módulos 1-4 traes:
- ✅ Virtual environment con FastAPI, uvicorn y Pydantic v2 funcionando
- ✅ Estructura de proyecto
app/main.pycon Books API - ✅ Endpoints CRUD completos con modelos Pydantic (
BookCreate,BookUpdate,BookResponse) - ✅ Validación automática con Pydantic — errores 422 para datos inválidos
- ✅
JSONResponsepara retornar status codes custom (usado en algunos endpoints) - ✅ Lógica básica de "not found" con
if/elseen endpoints GET y DELETE - ✅
response_modelpara controlar datos de respuesta - ✅
Field(),@field_validator,model_dump()con opciones
Lo nuevo de este módulo
- 🆕
HTTPException— la forma estándar de comunicar errores en FastAPI - 🆕 Status codes semánticos — 400, 404, 409, 403, 422 usados con intención
- 🆕 Custom exception handlers — capturar excepciones y formatear la respuesta
- 🆕 Formato de error consistente — todos los errores siguen la misma estructura
- 🆕
CORSMiddleware— permitir que otros dominios consuman tu API - 🆕 Middleware como concepto — Request → Middleware → Handler → Middleware → Response
- 🆕 Logging básico — registrar errores para debugging
- 🆕 Archivos estáticos — servir archivos desde tu API
De errores ad-hoc a errores profesionales
| Aspecto | Tu API actual (Módulo 4) | Tu API con error handling (Módulo 5) |
|---|---|---|
| Recurso no encontrado | if not book: return {"error": "not found"} con status 200 | raise HTTPException(status_code=404, detail="Book not found") |
| Formato de errores | Cada endpoint inventa su formato | Todos los errores siguen {"detail": "...", "status_code": N} |
| ID duplicado | No se valida | HTTPException(409, "Book with this ISBN already exists") |
| Status codes | Algunos 200, algunos 404, inconsistente | Cada error usa el código HTTP semánticamente correcto |
| Errores inesperados | 500 genérico sin contexto | Custom handler que loguea el error y retorna respuesta útil |
| Frontend externo | Bloqueado por el navegador | CORSMiddleware permite dominios específicos |
El problema: errores inconsistentes
Para entender por qué este módulo importa, veamos cómo tu API maneja errores ahora mismo. Después de cuatro módulos, tienes endpoints que manejan el caso "no encontrado" de diferentes maneras:
# Endpoint 1 — retorna dict con status 200 (incorrecto)
@app.get("/books/{book_id}")
def get_book(book_id: int):
book = find_book(book_id)
if not book:
return {"error": "Book not found"} # status 200 — el cliente cree que fue exitoso
return book
# Endpoint 2 — usa JSONResponse (mejor, pero formato propio)
@app.delete("/books/{book_id}")
def delete_book(book_id: int):
book = find_book(book_id)
if not book:
return JSONResponse(
status_code=404,
content={"message": "No se encontró el libro"}
)
books.remove(book)
return {"status": "deleted"}
# Endpoint 3 — no maneja el error (peor caso)
@app.put("/books/{book_id}")
def update_book(book_id: int, book_data: BookUpdate):
book = find_book(book_id)
book["title"] = book_data.title # si book es None → 500 Internal Server Error
return book
Tres endpoints, tres formas diferentes de manejar el mismo error. Un cliente que consume tu API tiene que adivinar qué formato de error esperar en cada caso. ¿El campo se llama "error", "message" o "detail"? ¿El status code es 200, 404 o 500? ¿La respuesta tiene siempre la misma estructura? No hay forma de saberlo sin probar cada endpoint individualmente.
Ahora imagina que eres el desarrollador frontend. Necesitas escribir código que maneje errores de tu API:
// ¿Cómo parseo el error? No sé qué esperar
try {
const response = await fetch("/api/books/999");
if (!response.ok) {
const error = await response.json();
// ¿Es error.error? ¿error.message? ¿error.detail?
// Depende del endpoint... 😬
}
} catch (e) {
// ¿500 sin body? ¿Timeout? ¿CORS blocked?
}
Esto no es un problema teórico. Es la razón por la que los equipos frontend y backend tienen reuniones interminables sobre "el formato de los errores." Un formato consistente elimina esa conversación.
Dos problemas, un módulo
Este módulo agrupa dos temas que podrían parecer no relacionados. Pero ambos responden a la misma pregunta: ¿está tu API lista para que alguien la consuma de verdad?
Problema 1: Error Handling — ¿Cómo comunica la API qué salió mal?
Cuando una operación falla, el cliente necesita tres cosas:
- Un status code HTTP correcto — 404 para "no existe", 400 para "datos inválidos", 409 para "conflicto"
- Un formato de error predecible — siempre la misma estructura JSON
- Un mensaje útil — que explique qué falló y, idealmente, qué hacer al respecto
Sin estas tres cosas, el cliente solo sabe que "algo falló." Con ellas, el cliente puede mostrar un mensaje de error específico, reintentar la operación, o redirigir al usuario — automáticamente, sin intervención humana.
Problema 2: CORS — ¿Cómo permite la API que otros dominios la consuman?
Tu API funciona perfectamente en /docs. Funciona en Postman. Funciona con curl. Pero cuando un frontend React en http://localhost:3000 hace fetch("http://localhost:8000/books"), el navegador bloquea la petición. No es un bug — es la Same-Origin Policy, una política de seguridad que impide que un sitio web haga requests a otro dominio sin permiso explícito. CORS (Cross-Origin Resource Sharing) es el mecanismo que le dice al navegador: "este dominio tiene permiso para acceder a mi API."
¿Por qué en el mismo módulo?
Ambos son requisitos no negociables para producción. Una API sin error handling consistente genera frustración en los consumidores. Una API sin CORS es literalmente inaccesible desde un navegador. Además, CORS se implementa como middleware en FastAPI, y el concepto de middleware es útil para entender cómo se procesan los requests en general — incluyendo cómo se interceptan errores.
Status Codes HTTP — El lenguaje universal
Antes de escribir una línea de error handling, necesitas entender el sistema de comunicación que HTTP te da: los status codes. Son el primer canal de información entre tu API y sus clientes. Un cliente bien escrito revisa el status code antes de leer el body de la respuesta.
Los status codes que vas a usar
2xx — Éxito (todo salió bien)
| Código | Nombre | Cuándo usarlo |
|---|---|---|
| 200 | OK | Request procesado exitosamente. GET que retorna datos, PUT/PATCH que actualizan |
| 201 | Created | Se creó un recurso nuevo. POST exitoso |
| 204 | No Content | Operación exitosa sin datos que retornar. DELETE exitoso |
4xx — Error del cliente (el cliente hizo algo mal)
| Código | Nombre | Cuándo usarlo |
|---|---|---|
| 400 | Bad Request | Datos inválidos que no cubren los otros 4xx. Request malformado |
| 401 | Unauthorized | No autenticado. Falta token o credenciales |
| 403 | Forbidden | Autenticado pero sin permisos. Sabe quién eres, no puedes hacer esto |
| 404 | Not Found | El recurso no existe. /books/999 cuando no hay libro 999 |
| 409 | Conflict | Conflicto con el estado actual. Crear un libro con ISBN duplicado |
| 422 | Unprocessable Entity | Datos con formato correcto pero semánticamente inválidos. Pydantic los genera |
5xx — Error del servidor (el servidor hizo algo mal)
| Código | Nombre | Cuándo usarlo |
|---|---|---|
| 500 | Internal Server Error | Error inesperado en el servidor. Bug, excepción no manejada |
¿Por qué importan los status codes?
Los status codes no son decorativos. Son el mecanismo principal con el que tu API comunica el resultado de una operación. Un cliente HTTP (navegador, frontend, mobile app, otro servicio) toma decisiones basándose en el código:
- 200-299: La operación fue exitosa. Muestra los datos al usuario
- 400-499: El cliente cometió un error. Muestra un mensaje de error, pide corrección
- 500-599: El servidor falló. Reintenta la operación, muestra "intenta más tarde"
Cuando tu endpoint retorna {"error": "not found"} con status 200, el cliente lo interpreta como éxito. Tiene que parsear el body para descubrir que en realidad falló. Eso rompe el contrato HTTP y hace que el error handling del lado del cliente sea innecesariamente complejo.
401 y 403 en este módulo
Vas a aprender qué significan 401 (Unauthorized) y 403 (Forbidden) porque son parte del vocabulario de status codes que todo desarrollador backend necesita conocer. Pero no vas a implementar autenticación en este módulo — eso viene en el path de Backend Python Developer. Aquí los mencionamos para que tu modelo mental de status codes esté completo.
Errores son parte del contrato
En el Módulo 4 aprendiste que los modelos Pydantic definen el contrato de éxito: qué acepta tu API y qué retorna cuando todo sale bien. Pero un contrato completo incluye también el contrato de fallo: qué retorna tu API cuando algo sale mal.
El contrato de éxito (ya lo tienes)
POST /books con BookCreate válido → 201 + BookResponse
GET /books/5 → 200 + BookResponse
El contrato de fallo (lo que vas a construir)
POST /books con datos inválidos → 422 + ErrorResponse (Pydantic automático)
GET /books/999 → 404 + ErrorResponse
POST /books con ISBN duplicado → 409 + ErrorResponse
PUT /books/999 con body vacío → 400 + ErrorResponse
¿Qué necesita saber el cliente?
Para cada posible error, el cliente necesita tres piezas de información:
- Status code — el número que indica la categoría del error
- Formato — la estructura JSON del error (qué campos tiene)
- Contenido — el mensaje que describe qué falló
Cuando estos tres elementos son consistentes a través de toda tu API, el cliente puede escribir un solo handler de errores que funciona para todos los endpoints:
Si status >= 400:
error = response.json()
mostrar error["detail"]
Ese handler funciona porque todos tus endpoints de error retornan la misma estructura. Sin consistencia, el cliente necesita un handler diferente por endpoint — o peor, un try/catch genérico que muestra "Ocurrió un error" sin contexto.
FastAPI y HTTPException: el estándar
FastAPI proporciona HTTPException como la forma estándar de comunicar errores. Cuando haces raise HTTPException(status_code=404, detail="Book not found"), FastAPI automáticamente:
- Detiene la ejecución del endpoint
- Retorna una respuesta con status code 404
- Incluye el body
{"detail": "Book not found"}
El campo detail es la convención de FastAPI. Todos los errores de Pydantic ya usan ese campo. Cuando tú también lo usas, el cliente tiene un formato unificado para todos los errores — ya sean de validación automática o de lógica de negocio.
¿Qué es CORS? (Conceptual)
Antes de configurar CORS en FastAPI, necesitas entender por qué existe. No es una decisión arbitraria de los navegadores — es una protección de seguridad fundamental que resuelve un problema real.
Same-Origin Policy
Los navegadores implementan una regla llamada Same-Origin Policy: un script en una página web solo puede hacer requests HTTP al mismo origen de donde se cargó la página. Un "origen" se define por tres componentes: protocolo + dominio + puerto.
http://localhost:3000 ← Origen del frontend React
http://localhost:8000 ← Origen de tu API FastAPI
¿Son el mismo origen? NO — el puerto es diferente
Cuando tu frontend en localhost:3000 intenta hacer fetch("http://localhost:8000/books"), el navegador ve que los orígenes son diferentes y bloquea la petición. No llega a tu API. El navegador la detiene antes.
¿Por qué existe esta restricción?
Imagina que no existiera. Entras a un sitio malicioso evil-site.com. Ese sitio tiene un script que hace fetch("https://tu-banco.com/api/transferir?a=hacker&monto=10000"). Si estás logueado en tu banco, el navegador enviaría tus cookies de sesión con el request. Sin Same-Origin Policy, el script de evil-site.com podría operar tu cuenta bancaria. Same-Origin Policy existe para que un sitio web no pueda hacer requests a otro sitio en tu nombre.
CORS: excepciones controladas
CORS (Cross-Origin Resource Sharing) no desactiva la Same-Origin Policy. La extiende con un mecanismo de permisos. Tu API puede declarar explícitamente: "acepto requests desde estos orígenes específicos." El navegador verifica esa declaración antes de permitir el request.
El flujo simplificado:
1. Frontend (localhost:3000) quiere hacer GET a API (localhost:8000)
2. Navegador envía un "preflight request" (OPTIONS) a la API
3. API responde con headers: "Permito requests desde localhost:3000"
4. Navegador verifica → el origen está permitido → deja pasar el GET
5. Si la API no responde con los headers correctos → navegador bloquea
¿Quién necesita CORS?
- ✅ Frontends web (React, Vue, Angular, vanilla JS) — siempre necesitan CORS si están en un dominio diferente
- ❌ Postman, curl, HTTPie — no son navegadores, no aplica Same-Origin Policy
- ❌ Otro backend (server-to-server) — no son navegadores, no aplica
- ❌ Swagger UI (
/docs) — se sirve desde el mismo origen que tu API, no necesita CORS
Esto explica por qué tu API funciona perfectamente en /docs y en Postman pero falla cuando un frontend React intenta consumirla. El problema no es tu API — es el navegador aplicando Same-Origin Policy.
CORS en desarrollo vs producción
En desarrollo es común permitir todos los orígenes (origins=["*"]) para no complicarse. En producción, debes listar explícitamente los dominios permitidos (origins=["https://mi-frontend.com"]). La diferencia tiene implicaciones de seguridad que verás en las cápsulas técnicas.
Middleware — el concepto
CORS se implementa en FastAPI como un middleware. Antes de ver cómo, necesitas entender qué es middleware y por qué existe.
¿Qué es middleware?
Middleware es código que se ejecuta antes y después de cada request, pero no pertenece a ningún endpoint específico. Es una capa que envuelve toda tu aplicación:
Request del cliente
↓
┌─────────────────────┐
│ Middleware (antes) │ ← Procesa el request antes de llegar al endpoint
└─────────────────────┘
↓
┌─────────────────────┐
│ Tu endpoint/handler │ ← Tu función de FastAPI
└─────────────────────┘
↓
┌─────────────────────┐
│ Middleware (después) │ ← Procesa la response antes de enviarla al cliente
└─────────────────────┘
↓
Response al cliente
¿Para qué se usa middleware?
Middleware es ideal para lógica que se aplica a todos los requests, no a endpoints individuales:
- 🔄 CORS — agregar headers de permisos a cada response
- ⏱️ Timing — medir cuánto tarda cada request
- 📝 Logging — registrar cada request que llega
- 🔒 Autenticación — verificar tokens antes de llegar al endpoint
- 🗜️ Compresión — comprimir responses automáticamente
¿Por qué CORS es middleware?
CORS necesita agregar headers específicos a cada response de tu API. No a un endpoint, no a una ruta — a cada response sin excepción. Con middleware, lo configuras una vez y aplica a todo automáticamente.
Múltiples middlewares
Puedes tener varios middlewares encadenados. Se ejecutan en orden: Request → CORS → Logging → Endpoint → Logging → CORS → Response. Cada uno puede modificar el request al entrar y la response al salir. El orden importa, pero eso lo verás en las cápsulas técnicas.
Objetivo del módulo
Al completar este módulo serás capaz de:
- ✅ Usar
HTTPExceptionpara comunicar errores con status codes semánticos - ✅ Aplicar el status code correcto para cada situación: 400, 404, 409, 422
- ✅ Crear custom exception handlers que formatean errores de manera consistente
- ✅ Definir un formato de error estándar para toda tu API
- ✅ Configurar
CORSMiddlewarepara permitir acceso desde frontends específicos - ✅ Entender Same-Origin Policy y por qué CORS existe
- ✅ Implementar middleware básico en FastAPI
- ✅ Agregar logging para registrar errores y requests
- ✅ Servir archivos estáticos desde tu API
- ✅ Distinguir cuándo usar 400 vs 404 vs 409 vs 422
- ✅ Configurar CORS de forma segura para desarrollo y producción
Prerequisitos
Para este módulo necesitas:
- Módulos 1-4 completados — Books API con modelos Pydantic, validación,
response_model - Tu proyecto Books API — Con modelos
BookCreate,BookUpdate,BookResponsefuncionando - uvicorn corriendo con
--reload— Para probar cambios en tiempo real
Verificación rápida
cd fastapi-fundamentals
source venv/bin/activate
uvicorn app.main:app --reload
Abre http://localhost:8000/docs y verifica que puedes:
- Crear un libro con POST
/booksusando un body que sigue el schemaBookCreate - Obtener un libro por ID con GET
/books/{book_id}— incluyendo un ID que no existe - Actualizar un libro con PATCH
/books/{book_id}conBookUpdate - Ver schemas detallados en
/docscon tipos, constraints y ejemplos
Si eso funciona, estás listo para el Módulo 5.
¿Qué pasa si no cumples algún prerequisito?
| Prerequisito faltante | Qué hacer |
|---|---|
| No hiciste el Módulo 4 | Completa las cápsulas 02-05 del Módulo 4 primero — necesitas modelos Pydantic |
| Tu Books API no tiene modelos Pydantic | Vuelve a la Cápsula 05 del Módulo 4 e implementa la migración de dicts a modelos |
No usas response_model | Revisa la Cápsula 04 del Módulo 4 — lo necesitarás para controlar respuestas de error |
| Tu Books API no levanta | Revisa errores de importación y verifica que FastAPI y Pydantic v2 están instalados |
Roadmap del módulo
| Cápsula | Tema | Qué aprenderás |
|---|---|---|
| 01 | Introducción (esta cápsula) | Contexto, el problema de errores inconsistentes, CORS conceptual, roadmap |
| 02 | HTTPException y status codes | raise HTTPException, status codes semánticos, errores en CRUD |
| 03 | Custom exception handlers | Capturar excepciones, formato consistente, logging de errores |
| 04 | CORS y Middleware | CORSMiddleware, Same-Origin Policy, configuración dev/prod, archivos estáticos |
| 05 | Proyecto: Books API robusta | Error handling completo + CORS en tu Books API, tests manuales |
Flujo de aprendizaje
Primero aprenderás HTTPException y cómo usarlo con status codes semánticos para comunicar errores claros en todos tus endpoints CRUD (Cápsula 02). Después crearás custom exception handlers que capturan excepciones de forma centralizada y retornan errores en un formato consistente — además de agregar logging para debugging (Cápsula 03). Luego configurarás CORSMiddleware para que tu API sea accesible desde frontends externos, entenderás el concepto de middleware, y agregarás servicio de archivos estáticos (Cápsula 04). Al final, integrarás todo en tu Books API: error handling completo en cada endpoint, CORS configurado, y logging funcionando (Cápsula 05).
La progresión es: comunicar errores → centralizar errores → abrir al mundo → integrar todo.
Detalle por cápsula
Cápsula 02 — HTTPException y status codes: Reemplazarás los if/else con return {"error": "..."} por raise HTTPException(status_code=404, detail="Book not found"). Aprenderás cuándo usar cada status code: 404 para recursos que no existen, 400 para requests malformados, 409 para conflictos (como ISBN duplicado). Verás cómo raise detiene la ejecución del endpoint inmediatamente — no necesitas else. Y cómo el módulo status de starlette te da constantes legibles como status.HTTP_404_NOT_FOUND.
Cápsula 03 — Custom exception handlers: Cuando HTTPException no es suficiente, creas handlers personalizados. Registrarás un handler que captura todas las HTTPException y las formatea con una estructura consistente. Crearás un handler para RequestValidationError que mejora los mensajes de error 422 de Pydantic. Agregarás un handler catch-all para excepciones inesperadas que loguea el traceback y retorna un 500 limpio. Aprenderás logging de Python para registrar errores con nivel, timestamp y contexto.
Cápsula 04 — CORS y Middleware: Configurarás CORSMiddleware paso a paso: orígenes permitidos, métodos permitidos, headers permitidos. Verás la diferencia entre allow_origins=["*"] (desarrollo) y allow_origins=["https://mi-app.com"] (producción). Entenderás preflight requests (OPTIONS) y qué headers CORS agrega el middleware. Aprenderás a crear tu propio middleware básico. Y configurarás StaticFiles para servir archivos estáticos desde tu API.
Cápsula 05 — Proyecto Books API robusta: La integración completa. Cada endpoint de tu Books API tendrá error handling apropiado con el status code correcto. Los errores seguirán un formato unificado. CORS estará configurado para desarrollo. Logging registrará operaciones y errores. Y probarás manualmente cada caso de error para verificar que la respuesta es correcta y consistente.
¿Qué NO se cubre en este módulo?
Es importante saber los límites para no buscar algo que viene después o que está fuera del scope:
- ❌ Autenticación / Autorización — Los status codes 401 y 403 se explican conceptualmente, pero no implementas login, tokens JWT ni verificación de permisos. Eso se cubre en el path Backend Python Developer
- ❌ Rate limiting — Limitar requests por IP o usuario es importante en producción pero requiere almacenamiento (Redis) que está fuera del scope de fundamentals
- ❌ Middleware avanzado — Middleware con estado, middleware asíncrono complejo, o middleware que modifica el body del request. Aquí cubres el concepto y
CORSMiddleware - ❌ Retry logic — Reintentar operaciones fallidas es responsabilidad del cliente, no del servidor
- ❌ Circuit breaker pattern — Patrones de resiliencia para servicios distribuidos están fuera del scope
- ❌ Structured logging (JSON) — Logging avanzado con formatos JSON para sistemas de monitoreo se cubre en guías de producción
¿Por qué estos límites?
- Autenticación es otro dominio. Error handling te dice cómo comunicar errores. Autenticación te dice quién puede hacer qué. Son ortogonales. Implementar auth aquí mezclaría dos conceptos que se entienden mejor por separado
- Rate limiting necesita estado. Para contar requests por IP necesitas almacenamiento persistente (Redis, base de datos). Tu API todavía trabaja en memoria
- Middleware avanzado es raro en APIs simples. El 90% de las APIs solo necesitan CORS middleware. Los otros patrones son para servicios complejos en producción
- Logging JSON es para infraestructura.
logging.error()es suficiente para desarrollo. Structured logging importa cuando tienes Datadog, CloudWatch o ELK Stack
Cada límite es intencional. Error handling y CORS son lo que necesitas para que tu API sea consumible por clientes reales. El resto viene cuando tengas las bases sólidas.
Conexión con el proyecto
Tu Books API recibe su último upgrade antes del proyecto final. Así se ve el cambio:
Antes (Módulo 4) — errores inconsistentes, sin CORS
@app.get("/books/{book_id}", response_model=BookResponse)
def get_book(book_id: int):
book = find_book(book_id)
if not book:
return JSONResponse(status_code=404, content={"error": "not found"})
return book
@app.delete("/books/{book_id}")
def delete_book(book_id: int):
book = find_book(book_id)
if not book:
return {"message": "Book not found"} # status 200!
books.remove(book)
return {"status": "deleted"}
- Cada endpoint tiene su formato de error
- Algunos retornan 200 cuando deberían retornar 404
- No hay CORS — un frontend no puede consumir la API
- No hay logging — si algo falla, no hay registro
Después (Módulo 5) — errores profesionales, CORS configurado
@app.get("/books/{book_id}", response_model=BookResponse)
def get_book(book_id: int):
book = find_book(book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
return book
@app.delete("/books/{book_id}", status_code=204)
def delete_book(book_id: int):
book = find_book(book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
books.remove(book)
- Todos los errores usan
HTTPExceptioncon el status code correcto - El formato es siempre
{"detail": "..."}— consistente y predecible - CORS middleware permite acceso desde frontends configurados
- Logging registra errores con contexto para debugging
La Books API como proyecto evolutivo
Módulo 1: Hello World API (esqueleto vacío)
↓
Módulo 2: Books API con CRUD (operaciones básicas con dicts)
↓
Módulo 3: Books API con filtros y validación de params
↓
Módulo 4: Books API con Pydantic (dicts → modelos)
↓
Módulo 5: Books API con error handling y CORS ← AQUÍ
↓
Módulo 6: To-Do List API (proyecto nuevo integrando todo)
La transición de este módulo es menos visual que la del Módulo 4 (donde los schemas en /docs cambiaron dramáticamente), pero es igual de importante. Después de este módulo, tu API no solo funciona — funciona correctamente cuando las cosas salen mal. Y eso es lo que separa una API de desarrollo de una API lista para el mundo real.
Evidencia de éxito
Al terminar este módulo (las 5 cápsulas), sabrás que tuviste éxito si:
- ✅ Todos tus endpoints usan
HTTPExceptionen lugar dereturn {"error": "..."}oJSONResponsemanual - ✅ Cada error retorna el status code HTTP semánticamente correcto (404 para no encontrado, 409 para conflicto, 400 para request inválido)
- ✅ Tienes al menos un custom exception handler registrado con
@app.exception_handler - ✅ Todos los errores siguen el mismo formato JSON — el cliente puede parsearlos con un solo handler
- ✅
CORSMiddlewareestá configurado con orígenes, métodos y headers explícitos - ✅ Puedes explicar qué es Same-Origin Policy y por qué CORS existe
- ✅ Logging básico registra errores con timestamp y contexto
- ✅ Tu API sirve archivos estáticos con
StaticFiles - ✅ Enviar un request a un recurso inexistente retorna 404, no 200 ni 500
- ✅ Un frontend en otro puerto puede consumir tu API sin errores de CORS
- ✅ Entiendes la diferencia entre
raise HTTPException(...)yreturn JSONResponse(...)
Resumen
- Este es el Módulo 5 de 6, enfocado en error handling profesional y configuración de CORS
- Tu API tiene CRUD y validación Pydantic, pero los errores son inconsistentes y el CORS no está configurado
HTTPExceptiones la forma estándar de comunicar errores en FastAPI — reemplazareturn {"error": "..."}- Los status codes HTTP son el lenguaje universal: 200 éxito, 404 no encontrado, 409 conflicto, 500 error del servidor
- Errores son parte del contrato de tu API — así como defines el formato de éxito, defines el formato de fallo
- Same-Origin Policy es una protección del navegador. CORS es el mecanismo para crear excepciones controladas
- Middleware es código que se ejecuta antes y después de cada request — CORS se implementa como middleware
- Custom exception handlers permiten centralizar el formato de errores y agregar logging
- Tu Books API recibe error handling consistente y CORS — lista para que un frontend la consuma
- No se cubre: autenticación (401/403 implementado), rate limiting, middleware avanzado, structured logging
- La progresión es: comunicar errores → centralizar errores → abrir al mundo → integrar todo
Recursos adicionales
- FastAPI - Handling Errors — Tutorial oficial de HTTPException y custom exception handlers
- FastAPI - HTTPException — Referencia de la clase HTTPException y sus parámetros
- FastAPI - CORS — Tutorial oficial de configuración de CORSMiddleware
- MDN - Cross-Origin Resource Sharing (CORS) — Guía completa de CORS desde la perspectiva del navegador
- MDN - HTTP Status Codes — Referencia completa de todos los status codes HTTP
- FastAPI - Middleware — Cómo crear y usar middleware en FastAPI
Siguiente cápsula: HTTPException y status codes — Reemplazarás los errores ad-hoc de tu API por raise HTTPException(...) con status codes semánticos, y verás cómo una línea de código comunica más que diez líneas de if/else con return.