FastAPI

FastAPI

Tutorial FastAPI: Rutas y parámetros

Aprende a definir rutas, parámetros y métodos HTTP en FastAPI para crear APIs RESTful eficientes y bien estructuradas.

Aprende FastAPI y certifícate

Definición de endpoints y métodos HTTP (GET, POST, PUT, DELETE)

Los endpoints son las URLs específicas en tu API que responden a las solicitudes de los clientes. En FastAPI, estos endpoints se definen mediante decoradores que asocian una ruta URL con una función Python. Cada endpoint está diseñado para realizar una operación concreta sobre un recurso determinado.

FastAPI facilita la creación de APIs RESTful mediante un sistema de decoradores intuitivo que permite definir rutas y especificar qué método HTTP debe utilizarse para cada operación. Los métodos HTTP son fundamentales en el diseño de APIs REST, ya que indican la acción que se desea realizar sobre un recurso.

Creando tu primera API con FastAPI

Para comenzar a trabajar con FastAPI, primero necesitamos crear una instancia de la aplicación:

from fastapi import FastAPI

app = FastAPI()

Este objeto app será el punto central de nuestra aplicación, donde registraremos todas las rutas y funcionalidades.

Definiendo endpoints básicos

Los endpoints se definen mediante decoradores que comienzan con @app seguido del método HTTP correspondiente. Veamos un ejemplo simple:

@app.get("/")
async def root():
    return {"message": "Bienvenido a mi API"}

En este ejemplo, hemos creado un endpoint que responde a solicitudes GET en la ruta raíz (/). Cuando un cliente accede a esta URL, recibirá un objeto JSON con el mensaje de bienvenida.

Métodos HTTP principales

FastAPI soporta todos los métodos HTTP estándar, pero los cuatro más utilizados en APIs REST son:

  • GET: Solicita datos de un recurso específico
  • POST: Envía datos para crear un nuevo recurso
  • PUT: Actualiza un recurso existente
  • DELETE: Elimina un recurso específico

Veamos cómo implementar cada uno de estos métodos en FastAPI:

Método GET

El método GET se utiliza para recuperar información. Es el método más común y debe ser idempotente, es decir, realizar la misma operación múltiples veces debe producir el mismo resultado sin efectos secundarios.

@app.get("/items")
async def get_items():
    return {"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}

Este endpoint devuelve una lista de elementos cuando se accede a /items mediante una solicitud GET.

Método POST

El método POST se utiliza para crear nuevos recursos. Normalmente se envían datos en el cuerpo de la solicitud:

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items")
async def create_item(item: Item):
    return {"item_id": 1, **item.model_dump()}

En este ejemplo, utilizamos un modelo Pydantic para validar los datos entrantes. FastAPI automáticamente deserializa el cuerpo JSON de la solicitud en un objeto Item.

Método PUT

El método PUT se utiliza para actualizar recursos existentes. Debe ser idempotente, lo que significa que realizar la misma actualización varias veces debe tener el mismo resultado:

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.model_dump()}

Este endpoint actualiza un elemento existente identificado por item_id. Observa cómo capturamos el ID del elemento directamente desde la URL.

Método DELETE

El método DELETE se utiliza para eliminar recursos:

@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"message": f"Item {item_id} eliminado correctamente"}

Este endpoint elimina un elemento específico y devuelve un mensaje de confirmación.

Estructura de una API RESTful

Una API RESTful bien diseñada sigue ciertos patrones para sus endpoints. Aquí hay un ejemplo de cómo podrías estructurar una API para gestionar una colección de libros:

# Simulamos una base de datos simple
books = []

class Book(BaseModel):
    title: str
    author: str
    pages: int
    
@app.get("/books")
async def get_books():
    return {"books": books}

@app.get("/books/{book_id}")
async def get_book(book_id: int):
    if book_id < 0 or book_id >= len(books):
        return {"error": "Libro no encontrado"}
    return {"book": books[book_id]}

@app.post("/books")
async def create_book(book: Book):
    books.append(book.model_dump())
    return {"id": len(books) - 1, **book.model_dump()}

@app.put("/books/{book_id}")
async def update_book(book_id: int, book: Book):
    if book_id < 0 or book_id >= len(books):
        return {"error": "Libro no encontrado"}
    books[book_id] = book.model_dump()
    return {"id": book_id, **book.model_dump()}

@app.delete("/books/{book_id}")
async def delete_book(book_id: int):
    if book_id < 0 or book_id >= len(books):
        return {"error": "Libro no encontrado"}
    deleted_book = books.pop(book_id)
    return {"message": f"Libro '{deleted_book['title']}' eliminado correctamente"}

Esta estructura implementa las operaciones CRUD (Create, Read, Update, Delete) para una colección de libros.

Operaciones adicionales con métodos HTTP

Además de los cuatro métodos principales, FastAPI también soporta otros métodos HTTP:

@app.patch("/items/{item_id}")
async def partial_update(item_id: int, item: dict):
    """PATCH se usa para actualizaciones parciales de un recurso"""
    return {"item_id": item_id, "updated_fields": item}

@app.options("/items")
async def options_items():
    """OPTIONS se usa para obtener información sobre los métodos permitidos"""
    return {"allowed_methods": ["GET", "POST", "PUT", "DELETE"]}

@app.head("/items")
async def head_items():
    """HEAD es similar a GET pero solo devuelve encabezados, sin cuerpo"""
    # FastAPI maneja esto automáticamente
    return {}

Agrupando endpoints con APIRouter

Para APIs más grandes, es recomendable organizar los endpoints en módulos separados. FastAPI proporciona APIRouter para este propósito:

from fastapi import APIRouter

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/")
async def get_users():
    return {"users": ["Alice", "Bob"]}

@router.get("/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id, "name": "Sample User"}

# En el archivo principal
app.include_router(router)

El APIRouter permite agrupar endpoints relacionados y aplicarles un prefijo común, lo que facilita la organización del código en aplicaciones más complejas.

Documentación automática

Una de las características más potentes de FastAPI es la generación automática de documentación interactiva. Cuando ejecutas tu aplicación, puedes acceder a:

  • /docs: Interfaz Swagger UI para probar tu API
  • /redoc: Documentación alternativa con ReDoc

Estas interfaces se generan automáticamente basándose en tus definiciones de endpoints y modelos Pydantic, lo que facilita enormemente la prueba y documentación de tu API.

Ejecutando la aplicación

Para ejecutar una aplicación FastAPI, utilizamos Uvicorn, un servidor ASGI de alto rendimiento:

uvicorn main:app --reload

Donde main es el nombre del archivo Python que contiene la instancia app de FastAPI, y --reload habilita el recargado automático durante el desarrollo.

Parámetros de ruta y query

Cuando desarrollamos APIs con FastAPI, necesitamos formas de recibir información de los clientes para procesar sus solicitudes correctamente. FastAPI ofrece dos mecanismos principales para recibir parámetros en las peticiones: parámetros de ruta y parámetros de consulta (query).

Parámetros de ruta

Los parámetros de ruta (path parameters) son variables que forman parte de la URL y se capturan directamente de ella. Estos parámetros son ideales para identificar recursos específicos como un usuario, producto o cualquier entidad que necesite ser accedida de forma individual.

Para definir un parámetro de ruta en FastAPI, utilizamos llaves {} en la definición de la ruta:

from fastapi import FastAPI

app = FastAPI()

@app.get("/usuarios/{usuario_id}")
async def leer_usuario(usuario_id: int):
    return {"usuario_id": usuario_id}

En este ejemplo, usuario_id es un parámetro de ruta que se extrae directamente de la URL. Si accedemos a /usuarios/123, FastAPI automáticamente asignará el valor 123 al parámetro usuario_id.

Conversión de tipos

Una característica poderosa de FastAPI es la conversión automática de tipos. En el ejemplo anterior, definimos usuario_id como int, lo que significa que FastAPI:

  1. Validará que el valor proporcionado pueda convertirse a entero
  2. Convertirá el valor automáticamente
  3. Generará un error 422 (Unprocessable Entity) si la conversión falla

Podemos usar varios tipos para los parámetros de ruta:

@app.get("/items/{item_id}")
async def leer_item(item_id: int):
    return {"item_id": item_id}

@app.get("/productos/{producto_id}")
async def leer_producto(producto_id: str):
    return {"producto_id": producto_id}

@app.get("/archivos/{file_path:path}")
async def leer_archivo(file_path: str):
    return {"file_path": file_path}

El último ejemplo usa una característica especial :path que permite que el parámetro capture rutas completas incluyendo barras (/).

Validación con Enum

Podemos restringir los valores aceptados para un parámetro de ruta utilizando enumeraciones:

from enum import Enum
from fastapi import FastAPI

class ModeloDispositivo(str, Enum):
    TELEFONO = "telefono"
    TABLET = "tablet"
    ORDENADOR = "ordenador"

app = FastAPI()

@app.get("/dispositivos/{modelo}")
async def obtener_dispositivo(modelo: ModeloDispositivo):
    if modelo == ModeloDispositivo.TELEFONO:
        return {"modelo": modelo, "mensaje": "Has elegido un teléfono"}
    if modelo == ModeloDispositivo.TABLET:
        return {"modelo": modelo, "mensaje": "Has elegido una tablet"}
    return {"modelo": modelo, "mensaje": "Has elegido un ordenador"}

En este caso, FastAPI solo aceptará solicitudes donde el parámetro modelo sea uno de los valores definidos en la enumeración.

Parámetros de consulta (query)

Los parámetros de consulta son aquellos que aparecen después del signo de interrogación (?) en la URL, separados por el símbolo ampersand (&). Son ideales para filtrar, paginar o ordenar colecciones de recursos.

En FastAPI, los parámetros de consulta se definen simplemente como parámetros de función que no están presentes en la ruta:

from fastapi import FastAPI

app = FastAPI()

@app.get("/productos")
async def listar_productos(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

En este ejemplo, podemos acceder a /productos?skip=20&limit=50 y FastAPI asignará automáticamente los valores a los parámetros correspondientes.

Parámetros opcionales y valores predeterminados

Los parámetros de consulta pueden ser opcionales si les asignamos un valor predeterminado:

@app.get("/articulos")
async def listar_articulos(
    categoria: str = None,
    ordenar_por: str = "fecha",
    orden: str = "desc"
):
    return {
        "categoria": categoria,
        "ordenar_por": ordenar_por,
        "orden": orden
    }

En este caso, todos los parámetros son opcionales. Si no se proporcionan en la URL, se utilizarán los valores predeterminados.

Parámetros booleanos

FastAPI maneja inteligentemente los parámetros booleanos:

@app.get("/productos/disponibles")
async def listar_disponibles(mostrar_agotados: bool = False):
    return {"mostrar_agotados": mostrar_agotados}

Podemos usar /productos/disponibles?mostrar_agotados=true o simplemente /productos/disponibles?mostrar_agotados (que FastAPI interpretará como True).

Parámetros múltiples con el mismo nombre

Para recibir múltiples valores para el mismo parámetro, usamos listas:

from typing import List
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
async def leer_items(tags: List[str] = Query(None)):
    return {"tags": tags}

Esto permite URLs como /items/?tags=ropa&tags=verano&tags=oferta.

Validación avanzada de parámetros

FastAPI proporciona herramientas para validar parámetros de forma más detallada:

Validación de parámetros de consulta

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/productos/")
async def leer_productos(
    q: str = Query(
        None,
        min_length=3,
        max_length=50,
        pattern="^[a-zA-Z0-9 ]*$",
        title="Término de búsqueda",
        description="Cadena para filtrar productos por nombre"
    )
):
    resultados = {"productos": [{"id": 1, "nombre": "Producto 1"}]}
    if q:
        resultados.update({"filtro": q})
    return resultados

Este ejemplo valida que el parámetro q tenga entre 3 y 50 caracteres y cumpla con un patrón específico.

Validación de parámetros de ruta

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/productos/{producto_id}")
async def leer_producto(
    producto_id: int = Path(
        ...,  # ... indica que es obligatorio
        gt=0,  # mayor que 0
        le=1000,  # menor o igual a 1000
        title="ID del producto",
        description="Identificador único del producto"
    )
):
    return {"producto_id": producto_id}

En este caso, validamos que producto_id sea un número entre 1 y 1000.

Combinando parámetros de ruta y consulta

Es común combinar ambos tipos de parámetros en un mismo endpoint:

from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/usuarios/{usuario_id}/pedidos")
async def leer_pedidos_usuario(
    usuario_id: int = Path(..., gt=0, title="ID del usuario"),
    skip: int = Query(0, ge=0, title="Registros a omitir"),
    limit: int = Query(10, ge=1, le=100, title="Límite de registros")
):
    return {
        "usuario_id": usuario_id,
        "pedidos": [
            {"id": 1, "producto": "Laptop", "precio": 999.99},
            {"id": 2, "producto": "Mouse", "precio": 25.50}
        ],
        "paginacion": {"skip": skip, "limit": limit}
    }

Este endpoint combina un parámetro de ruta (usuario_id) con parámetros de consulta para paginación (skip y limit).

Parámetros de consulta opcionales con tipos complejos

Para parámetros opcionales que no son tipos simples, debemos usar None como valor predeterminado:

from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def leer_items(
    nombre: Optional[str] = None,
    precio_min: Optional[float] = None,
    precio_max: Optional[float] = None
):
    filtros = {}
    if nombre:
        filtros["nombre"] = nombre
    if precio_min is not None:
        filtros["precio_min"] = precio_min
    if precio_max is not None:
        filtros["precio_max"] = precio_max
        
    return {
        "filtros_aplicados": filtros,
        "items": [
            {"id": 1, "nombre": "Producto 1", "precio": 50.0},
            {"id": 2, "nombre": "Producto 2", "precio": 100.0}
        ]
    }

Parámetros de consulta requeridos

Por defecto, los parámetros de consulta son opcionales. Para hacerlos obligatorios, omitimos el valor predeterminado:

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/buscar/")
async def buscar_productos(q: str = Query(..., min_length=3)):
    return {"resultados": [f"Resultado para: {q}"]}

En este caso, el parámetro q es obligatorio y debe tener al menos 3 caracteres.

Ejemplo práctico: API de biblioteca

Veamos un ejemplo más completo que combina varios conceptos:

from typing import Optional, List
from fastapi import FastAPI, Query, Path
from enum import Enum

app = FastAPI()

class Categoria(str, Enum):
    FICCION = "ficcion"
    NO_FICCION = "no-ficcion"
    CIENCIA = "ciencia"
    HISTORIA = "historia"

# Base de datos simulada
libros = [
    {"id": 1, "titulo": "El nombre del viento", "autor": "Patrick Rothfuss", "categoria": "ficcion"},
    {"id": 2, "titulo": "Una breve historia del tiempo", "autor": "Stephen Hawking", "categoria": "ciencia"},
    {"id": 3, "titulo": "Sapiens", "autor": "Yuval Noah Harari", "categoria": "historia"},
    {"id": 4, "titulo": "1984", "autor": "George Orwell", "categoria": "ficcion"}
]

@app.get("/libros/")
async def listar_libros(
    categoria: Optional[Categoria] = None,
    autor: Optional[str] = None,
    ordenar: Optional[str] = Query(None, pattern="^(titulo|autor)$")
):
    resultados = libros.copy()
    
    # Aplicar filtros
    if categoria:
        resultados = [libro for libro in resultados if libro["categoria"] == categoria]
    if autor:
        resultados = [libro for libro in resultados if autor.lower() in libro["autor"].lower()]
    
    # Aplicar ordenación
    if ordenar:
        resultados = sorted(resultados, key=lambda x: x[ordenar])
    
    return {"total": len(resultados), "libros": resultados}

@app.get("/libros/{libro_id}")
async def obtener_libro(
    libro_id: int = Path(..., gt=0, title="ID del libro")
):
    for libro in libros:
        if libro["id"] == libro_id:
            return libro
    return {"error": "Libro no encontrado"}

Este ejemplo implementa una API para una biblioteca con dos endpoints:

  • /libros/ - Lista todos los libros con filtros opcionales por categoría y autor
  • /libros/{libro_id} - Obtiene un libro específico por su ID

La API permite filtrar por categoría (usando una enumeración para validar los valores), buscar por autor y ordenar los resultados.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende FastAPI online

Ejercicios de esta lección Rutas y parámetros

Evalúa tus conocimientos de esta lección Rutas y parámetros con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de FastAPI

Accede a todas las lecciones de FastAPI y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a FastAPI y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender cómo definir endpoints y asociarlos a métodos HTTP en FastAPI.
  • Aprender a manejar parámetros de ruta y parámetros de consulta (query) en las rutas.
  • Implementar validaciones y conversiones automáticas de tipos para parámetros.
  • Conocer la estructura básica para crear APIs RESTful con operaciones CRUD.
  • Organizar endpoints usando APIRouter y aprovechar la documentación automática de FastAPI.