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
nombreno contenga../para evitar ataques de path traversal. Puedes usarpathlib.Path(path).resolve()y comprobar que el resultado esté dentro de tu carpetastatic/.
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?
| Necesidad | Solución |
|---|---|
| Imágenes, CSS, JS accesibles por URL | StaticFiles |
| Página HTML fija | HTMLResponse leyendo el archivo |
| HTML con datos del servidor | Jinja2Templates |
| 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.

Ingeniero en Informática, Investigador, me encanta crear cosas o arreglarlas y darles una nueva vida. Escritor y poeta. Más de 20 APPs publicadas y un libro en Amazon.