Module 1: Setup and First API
Primer Endpoint y Uvicorn
Descripción
En esta cápsula escribes tu primer endpoint real en FastAPI, entiendes qué es una "path operation function" y experimentas con hot reload para iterar rápido. Pasas de tener un servidor que responde en una sola ruta a tener múltiples endpoints GET con diferentes rutas y respuestas.
FastAPI conecta tres conceptos que ya conoces por separado: una ruta HTTP (path), un verbo HTTP (operation) y una función Python (function). Cuando escribes @app.get("/health") sobre una función def health(), estás diciendo "cuando alguien haga GET a /health, ejecuta esta función." Eso es una path operation — y es el bloque fundamental de toda API en FastAPI.
Al terminar, tendrás un servidor con múltiples endpoints funcionando y habrás experimentado el ciclo de desarrollo con hot reload: escribir código → guardar → ver el cambio automáticamente.
Path Operations: El concepto fundamental
¿Qué es una path operation?
Una path operation es la combinación de:
- Path (ruta): La URL del endpoint (
/,/health,/greet) - Operation (operación): El verbo HTTP (
GET,POST,PUT,DELETE) - Function (función): La función Python que se ejecuta
from fastapi import FastAPI
app = FastAPI()
# Path: "/"
# Operation: GET
# Function: root()
@app.get("/")
def root():
return {"message": "Hello, World!"}
El decorador @app.get("/") le dice a FastAPI: "Registra la función root() como el handler para requests GET a la ruta /."
Anatomía de un endpoint
@app.get("/health") # ← Decorador: define path + operation
def health_check(): # ← Function: lógica del endpoint
return { # ← Return: FastAPI lo convierte a JSON automáticamente
"status": "healthy",
"service": "my-api"
}
Tres cosas importantes:
- El decorador (
@app.get) registra la función en el router de FastAPI - La función puede llamarse como quieras — el nombre no afecta la ruta
- El return acepta diccionarios, listas, strings, números — FastAPI los serializa a JSON
Tu primer endpoint GET
Abre app/main.py y reemplaza el contenido con esto:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {
"message": "Welcome to my API",
"version": "1.0.0"
}
Levantar el servidor
# Desde la raíz del proyecto (donde está la carpeta app/)
uvicorn app.main:app --reload
Output esperado:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [12345] using StatReload
INFO: Started server process [12346]
INFO: Waiting for application startup.
INFO: Application startup complete.
Probar el endpoint
Abre tu navegador en http://127.0.0.1:8000. Verás:
{
"message": "Welcome to my API",
"version": "1.0.0"
}
También puedes usar curl desde otra terminal:
curl http://127.0.0.1:8000
# {"message":"Welcome to my API","version":"1.0.0"}
# Con formato bonito:
curl -s http://127.0.0.1:8000 | python -m json.tool
# {
# "message": "Welcome to my API",
# "version": "1.0.0"
# }
Hot Reload: Iterar sin reiniciar
El flag --reload de uvicorn monitorea tus archivos Python. Cada vez que guardas un cambio, uvicorn reinicia el servidor automáticamente.
Experimenta hot reload
Sin detener el servidor, edita app/main.py:
@app.get("/")
def root():
return {
"message": "Welcome to my API",
"version": "1.0.0",
"author": "Tu Nombre" # ← Agrega esta línea
}
Guarda el archivo. En la terminal de uvicorn verás:
WARNING: StatReload detected changes in 'app/main.py'. Reloading...
INFO: Started server process [12347]
INFO: Waiting for application startup.
INFO: Application startup complete.
Refresca el navegador. La respuesta ahora incluye "author":
{
"message": "Welcome to my API",
"version": "1.0.0",
"author": "Tu Nombre"
}
No reiniciaste nada manualmente. Este ciclo — editar → guardar → ver resultado — es tu flujo de desarrollo con FastAPI.
Múltiples endpoints
Un API real tiene más de una ruta. Agrega endpoints a app/main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {
"message": "Welcome to my API",
"version": "1.0.0"
}
@app.get("/health")
def health_check():
return {
"status": "healthy"
}
@app.get("/about")
def about():
return {
"name": "FastAPI Fundamentals API",
"description": "Learning FastAPI step by step",
"python_version": "3.12"
}
Guarda y prueba cada ruta:
curl http://127.0.0.1:8000/
# {"message":"Welcome to my API","version":"1.0.0"}
curl http://127.0.0.1:8000/health
# {"status":"healthy"}
curl http://127.0.0.1:8000/about
# {"name":"FastAPI Fundamentals API","description":"Learning FastAPI step by step","python_version":"3.12"}
Cada decorador @app.get() registra una ruta diferente. FastAPI mantiene un router interno que mapea paths a funciones.
Path parameters: Rutas dinámicas
Hasta ahora las rutas son estáticas: /, /health, /about. Pero muchas APIs necesitan rutas dinámicas — por ejemplo, saludar a un usuario por nombre.
Ejemplo básico con path parameter
@app.get("/greet/{name}")
def greet(name: str):
return {
"message": f"Hello, {name}!"
}
La parte {name} en la ruta es un path parameter. FastAPI lo extrae de la URL y lo pasa como argumento a la función.
curl http://127.0.0.1:8000/greet/Maria
# {"message":"Hello, Maria!"}
curl http://127.0.0.1:8000/greet/Carlos
# {"message":"Hello, Carlos!"}
Type hints y conversión automática
El type hint name: str le dice a FastAPI que el parámetro es un string. FastAPI también puede convertir tipos automáticamente:
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {
"item_id": item_id,
"type": type(item_id).__name__
}
curl http://127.0.0.1:8000/items/42
# {"item_id":42,"type":"int"}
# Si pasas algo que no es int:
curl http://127.0.0.1:8000/items/abc
# {"detail":[{"type":"int_parsing","loc":["path","item_id"],
# "msg":"Input should be a valid integer..."}]}
FastAPI valida automáticamente que item_id sea un entero. Si no lo es, retorna un error 422 con un mensaje claro. No escribiste ni una línea de validación — el type hint int fue suficiente.
Funciones sync vs async
FastAPI soporta tanto funciones síncronas (def) como asíncronas (async def):
# Síncrono — FastAPI lo ejecuta en un thread pool
@app.get("/sync")
def sync_endpoint():
return {"type": "sync"}
# Asíncrono — FastAPI lo ejecuta en el event loop
@app.get("/async")
async def async_endpoint():
return {"type": "async"}
¿Cuándo usar cada uno?
| Situación | Usa | Razón |
|---|---|---|
| Operaciones simples (retornar datos) | def | No necesitas async para algo instantáneo |
| I/O async (base de datos async, HTTP async) | async def | Aprovecha el event loop, no bloquea |
| I/O sync (archivo local, DB sync) | def | FastAPI lo maneja en thread pool automáticamente |
Para este módulo usa def (síncrono). Es más simple y funciona perfectamente para endpoints que retornan datos inmediatamente. Cuando integres bases de datos async en guías posteriores, usarás async def.
Regla práctica: Si no estás usando await dentro de la función, usa def. Si usas await, usa async def.
Return types: Qué puedes retornar
FastAPI convierte automáticamente varios tipos de Python a respuestas JSON:
# Diccionario → JSON object
@app.get("/dict")
def return_dict():
return {"key": "value"}
# Response: {"key": "value"}
# Lista → JSON array
@app.get("/list")
def return_list():
return [1, 2, 3, "four"]
# Response: [1, 2, 3, "four"]
# String → JSON string
@app.get("/string")
def return_string():
return "Hello"
# Response: "Hello"
# Número → JSON number
@app.get("/number")
def return_number():
return 42
# Response: 42
# Bool → JSON boolean
@app.get("/bool")
def return_bool():
return True
# Response: true
# None → JSON null
@app.get("/none")
def return_none():
return None
# Response: null
En la práctica, la mayoría de endpoints retornan diccionarios. Es la convención estándar para APIs REST.
Comparación: Decoradores de FastAPI vs decoradores Python
Si vienes de la Guía #1 (Python Essentials), ya viste decoradores. Los de FastAPI son similares pero con un propósito específico:
| Aspecto | Decorador Python genérico | Decorador FastAPI |
|---|---|---|
| Sintaxis | @mi_decorador | @app.get("/ruta") |
| Propósito | Modificar comportamiento de función | Registrar función como handler HTTP |
| Recibe | La función decorada | Path + opciones de configuración |
| Resultado | Función envuelta | Función registrada en el router |
# Decorador Python genérico (concepto)
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@my_decorator
def say_hello():
return "Hello"
# Decorador FastAPI (registra ruta)
@app.get("/hello")
def say_hello():
return {"message": "Hello"}
Lo que hace @app.get("/hello") internamente:
- Registra la ruta
/hellocon el método GET - Asocia la función
say_hellocomo handler - Configura la serialización automática del return a JSON
- Agrega la ruta a la documentación OpenAPI
No necesitas entender la implementación interna — solo saber que el decorador conecta una ruta HTTP con una función Python.
Código completo del módulo hasta aquí
Este es el app/main.py con todo lo que aprendiste en esta cápsula:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {
"message": "Welcome to my API",
"version": "1.0.0"
}
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.get("/about")
def about():
return {
"name": "FastAPI Fundamentals API",
"description": "Learning FastAPI step by step"
}
@app.get("/greet/{name}")
def greet(name: str):
return {"message": f"Hello, {name}!"}
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {
"item_id": item_id,
"found": True
}
# Ejecutar
uvicorn app.main:app --reload
# Probar todos los endpoints
curl http://127.0.0.1:8000/
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/about
curl http://127.0.0.1:8000/greet/FastAPI
curl http://127.0.0.1:8000/items/7
Troubleshooting
Problema 1: El cambio no se refleja con hot reload
Causa: Guardaste el archivo pero uvicorn no detectó el cambio (raro, pero ocurre).
Solución:
# Detener uvicorn con Ctrl+C y reiniciar
uvicorn app.main:app --reload
Si persiste, verifica que estás editando el archivo correcto (app/main.py, no otro main.py en otra ubicación).
Problema 2: 404 Not Found al acceder a un endpoint
Causa: La ruta en el navegador no coincide con la ruta en el decorador.
Solución:
# Si definiste:
@app.get("/health")
# Accede a:
# ✅ http://127.0.0.1:8000/health
# ❌ http://127.0.0.1:8000/Health (case sensitive)
# ❌ http://127.0.0.1:8000/health/ (trailing slash)
Las rutas en FastAPI son case-sensitive y no incluyen trailing slash por defecto.
Problema 3: 422 Unprocessable Entity con path parameters
Causa: El valor del path parameter no coincide con el type hint.
Solución:
@app.get("/items/{item_id}")
def get_item(item_id: int): # ← Espera int
return {"item_id": item_id}
# ✅ /items/42 → funciona (42 es int)
# ❌ /items/abc → 422 error ("abc" no es int)
# ❌ /items/3.14 → 422 error (3.14 no es int)
Si necesitas aceptar cualquier tipo, usa str como type hint.
Ejercicios
Ejercicio 1: Endpoint de timestamp (Fácil)
Crea un endpoint GET en /time que retorne la fecha y hora actual del servidor.
Ver solución
from datetime import datetime
from fastapi import FastAPI
app = FastAPI()
@app.get("/time")
def current_time():
now = datetime.now()
return {
"date": now.strftime("%Y-%m-%d"),
"time": now.strftime("%H:%M:%S"),
"timestamp": now.isoformat()
}
Output esperado:
{
"date": "2026-03-13",
"time": "14:30:25",
"timestamp": "2026-03-13T14:30:25.123456"
}
Explicación: datetime.now() obtiene la fecha/hora actual. strftime la formatea como string. FastAPI convierte el diccionario a JSON automáticamente.
Ejercicio 2: Endpoint con cálculo (Fácil)
Crea un endpoint GET en /square/{number} que reciba un número entero y retorne su cuadrado.
Ver solución
@app.get("/square/{number}")
def square(number: int):
return {
"input": number,
"result": number ** 2
}
curl http://127.0.0.1:8000/square/5
# {"input":5,"result":25}
curl http://127.0.0.1:8000/square/12
# {"input":12,"result":144}
Explicación: El type hint int convierte automáticamente el string de la URL a entero. Si pasas algo que no sea número, FastAPI retorna 422.
Ejercicio 3: Múltiples path parameters (Medio)
Crea un endpoint GET en /add/{a}/{b} que reciba dos números enteros y retorne la suma, resta, multiplicación y división.
Ver solución
@app.get("/add/{a}/{b}")
def math_operations(a: int, b: int):
result = {
"a": a,
"b": b,
"sum": a + b,
"difference": a - b,
"product": a * b,
}
if b != 0:
result["division"] = a / b
else:
result["division"] = "Cannot divide by zero"
return result
curl http://127.0.0.1:8000/add/10/3
# {"a":10,"b":3,"sum":13,"difference":7,"product":30,"division":3.333...}
curl http://127.0.0.1:8000/add/10/0
# {"a":10,"b":0,"sum":10,"difference":10,"product":0,"division":"Cannot divide by zero"}
Explicación: FastAPI extrae ambos path parameters a y b y los convierte a int. El chequeo de división por cero evita un error runtime — en Módulo 5 aprenderás a manejar esto con HTTPException.
Ejercicio 4: Endpoint con lógica condicional (Medio)
Crea un endpoint GET en /age/{years} que reciba una edad y retorne la categoría: "child" (0-12), "teenager" (13-17), "adult" (18-64), "senior" (65+). Si la edad es negativa, retorna un mensaje de error.
Ver solución
@app.get("/age/{years}")
def age_category(years: int):
if years < 0:
return {"error": "Age cannot be negative", "input": years}
if years <= 12:
category = "child"
elif years <= 17:
category = "teenager"
elif years <= 64:
category = "adult"
else:
category = "senior"
return {
"age": years,
"category": category
}
curl http://127.0.0.1:8000/age/8
# {"age":8,"category":"child"}
curl http://127.0.0.1:8000/age/25
# {"age":25,"category":"adult"}
curl http://127.0.0.1:8000/age/-5
# {"error":"Age cannot be negative","input":-5}
Explicación: Lógica condicional estándar de Python dentro de un endpoint. El manejo de edad negativa es básico — en Módulo 5 usarás HTTPException con status code 400 para comunicar errores de forma estándar.
Ejercicio 5: API de información del sistema (Difícil)
Crea un endpoint GET en /system que retorne información del sistema: sistema operativo, versión de Python, y la lista de endpoints disponibles en tu API (hardcoded está bien).
Ver solución
import platform
import sys
from fastapi import FastAPI
app = FastAPI()
@app.get("/system")
def system_info():
return {
"os": platform.system(),
"os_version": platform.version(),
"python_version": sys.version,
"architecture": platform.machine(),
"endpoints": [
{"method": "GET", "path": "/"},
{"method": "GET", "path": "/health"},
{"method": "GET", "path": "/system"},
{"method": "GET", "path": "/greet/{name}"},
]
}
{
"os": "Darwin",
"os_version": "24.0.0",
"python_version": "3.12.3 (main, ...)",
"architecture": "arm64",
"endpoints": [
{"method": "GET", "path": "/"},
{"method": "GET", "path": "/health"},
{"method": "GET", "path": "/system"},
{"method": "GET", "path": "/greet/{name}"}
]
}
Explicación: platform y sys son módulos de la librería estándar de Python que dan información del sistema. La lista de endpoints está hardcoded — en la siguiente cápsula verás que FastAPI genera esta información automáticamente en /docs.
Resumen
- Una path operation combina ruta + verbo HTTP + función Python
@app.get("/ruta")registra una función como handler para GET en esa ruta- Hot reload (
--reload) reinicia uvicorn automáticamente al guardar cambios - Path parameters (
/greet/{name}) extraen valores de la URL y los pasan como argumentos - Type hints (
name: str,item_id: int) activan validación automática - FastAPI convierte diccionarios, listas y otros tipos Python a JSON automáticamente
- Usa
defpara funciones síncronas simples,async defcuando usesawait
Próxima cápsula: Documentación automática — Descubrirás /docs y /redoc, las interfaces interactivas que FastAPI genera sin que escribas una sola línea extra.
Recursos adicionales
- FastAPI - First Steps - Tutorial oficial del primer endpoint
- FastAPI - Path Parameters - Path parameters en detalle
- Uvicorn - Deployment - Opciones de ejecución de uvicorn
- Starlette - Routing - El router que FastAPI usa internamente
- Python Type Hints - Docs - Referencia de type hints
- HTTP Methods - MDN - Referencia de verbos HTTP