Cómo servir imágenes y HTML estático en FastAPI

Tiempo de lectura: 2 minutos

Hoy vamos a aprender cómo podemos servir imágenes en estático usando FastAPI además de servir HTML.

1. Instalar dependencias

FastAPI necesita aiofiles para servir archivos de disco de forma asíncrona:

bash

pip install aiofiles

2. Estructura de carpetas recomendada

mi_proyecto/
├── main.py
├── static/
│   ├── images/
│   │   ├── gato.png
│   │   └── perro.png
│   ├── css/
│   │   └── estilos.css
│   └── js/
│       └── app.js
└── templates/
    └── index.html

La carpeta static/ contiene todo lo que el navegador descargará directamente. La carpeta templates/ es opcional, solo la necesitas si usas HTML dinámico con Jinja2.

3. Montar la carpeta estática

python

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# "directory" es la carpeta en disco, "/static" es la URL pública
app.mount("/static", StaticFiles(directory="static"), name="static")

Con esto, cualquier archivo dentro de static/ queda accesible automáticamente:

http://localhost:8000/static/images/gato.png

4. Servir el index.html

Opción A — HTML fijo (sin plantillas, ideal si el HTML no necesita datos del servidor):

python

from fastapi.responses import HTMLResponse

@app.get("/", response_class=HTMLResponse)
async def index():
    with open("static/index.html") as f:
        return f.read()

Opción B — HTML dinámico con Jinja2 (cuando necesitas pasar variables al HTML):

bash

pip install jinja2

python

from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    return templates.TemplateResponse(
        "index.html",
        {"request": request, "titulo": "Mi app"}
    )

En el HTML puedes usar la sintaxis {{ titulo }} de Jinja2 para renderizar esas variables.

5. Devolver una imagen concreta desde un endpoint

Si necesitas lógica antes de servir la imagen (comprobar permisos, elegir una variante, etc.) usa FileResponse:

python

import os
from fastapi import HTTPException
from fastapi.responses import FileResponse

@app.get("/imagen/{nombre}")
async def get_imagen(nombre: str):
    path = f"static/images/{nombre}.png"
    if not os.path.exists(path):
        raise HTTPException(status_code=404, detail="Imagen no encontrada")
    return FileResponse(path, media_type="image/png")

Accesible en: http://localhost:8000/imagen/gato

⚠️ Valida siempre que el parámetro nombre no contenga ../ para evitar ataques de path traversal. Puedes usar pathlib.Path(path).resolve() y comprobar que el resultado esté dentro de tu carpeta static/.

6. Usar las imágenes desde el frontend

Una vez montada la carpeta, el HTML puede referenciarlas directamente con la ruta /static/...:

html

<img src="/static/images/gato.png" alt="Gato">

O construir la URL dinámicamente desde JavaScript:

javascript

const nombre = "gato";
const img = document.createElement("img");
img.src = `/static/images/${nombre}.png`;
document.body.appendChild(img);

¿cuándo usar cada enfoque?

NecesidadSolución
Imágenes, CSS, JS accesibles por URLStaticFiles
Página HTML fijaHTMLResponse leyendo el archivo
HTML con datos del servidorJinja2Templates
Imagen con lógica previa (auth, etc.)FileResponse en un endpoint

En la mayoría de los casos lo más limpio es combinar StaticFiles para los recursos y un endpoint / que devuelva el HTML principal.

Deja un comentario