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:

  1. Path (ruta): La URL del endpoint (/, /health, /greet)
  2. Operation (operación): El verbo HTTP (GET, POST, PUT, DELETE)
  3. 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:

  1. El decorador (@app.get) registra la función en el router de FastAPI
  2. La función puede llamarse como quieras — el nombre no afecta la ruta
  3. 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ónUsaRazón
Operaciones simples (retornar datos)defNo necesitas async para algo instantáneo
I/O async (base de datos async, HTTP async)async defAprovecha el event loop, no bloquea
I/O sync (archivo local, DB sync)defFastAPI 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:

AspectoDecorador Python genéricoDecorador FastAPI
Sintaxis@mi_decorador@app.get("/ruta")
PropósitoModificar comportamiento de funciónRegistrar función como handler HTTP
RecibeLa función decoradaPath + opciones de configuración
ResultadoFunción envueltaFunció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:

  1. Registra la ruta /hello con el método GET
  2. Asocia la función say_hello como handler
  3. Configura la serialización automática del return a JSON
  4. 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 def para funciones síncronas simples, async def cuando uses await

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

  1. FastAPI - First Steps - Tutorial oficial del primer endpoint
  2. FastAPI - Path Parameters - Path parameters en detalle
  3. Uvicorn - Deployment - Opciones de ejecución de uvicorn
  4. Starlette - Routing - El router que FastAPI usa internamente
  5. Python Type Hints - Docs - Referencia de type hints
  6. HTTP Methods - MDN - Referencia de verbos HTTP