FastAPI Essentials

Course Content

FastAPI Essentials

1 sections · 32 lessons

How can you serve static files with FastAPI?


What you need to know

app.mount(path, app) attaches a separate ASGI app under a path prefix. StaticFiles is such an app: it looks up the requested path inside a directory and returns the file.

Python
from fastapi import FastAPIfrom fastapi.staticfiles import StaticFilesapp = FastAPI()@app.get("/api/health")def health():    return {"ok": True}app.mount("/static", StaticFiles(directory="static"), name="static")app.mount("/", StaticFiles(directory="dist", html=True), name="ui")   # the built front end; mount LAST

Real results with a small dist/ folder containing index.html and assets/app.css:

Text
API route declared first:  GET /api/health     -> 200 {"ok":true}UI mount declared first:   GET /api/health     -> 404 {"detail":"Not Found"}GET /                                          -> 200 <h1>app</h1>   (index.html, with an ETag)GET /assets/app.css                            -> 200 text/cssGET /courses/42   (a front-end route)          -> 404GET /..%2F..%2Fetc%2Fpasswd                    -> 404

Three lessons from these lines:

  • Order matters. A mount at / catches every path. Declared before the API route, it swallowed /api/health and answered 404, because there is no such file.
  • html=True is not a single-page-app fallback. It serves index.html for a directory and 404.html if present, but a client-side route such as /courses/42 still returns 404. For a SPA, add a catch-all route that returns index.html, or better, let the CDN or nginx handle it.
  • Path traversal is blocked. StaticFiles refuses paths that escape the directory.

Why not in production

Every static byte served from a model service uses a worker whose real job is inference. A CDN or nginx serves files faster, caches them near users and costs far less. The usual layout is: front end on a CDN or object storage, FastAPI serving only the API. Static serving from FastAPI is fine for demos, internal dashboards, local development and air-gapped installs.

For generated files — charts, audio from a text-to-speech model, exported reports — do not write them into a served folder. Upload them to object storage and return a pre-signed URL that expires in, say, 15 minutes. They then never touch local disk, which is lost when a container restarts, and there is no folder for a traversal bug to expose.

A real-life example

A voice-assistant startup's FastAPI service generated TTS audio files into ./static/audio/ and served them with StaticFiles. It worked on one server. After scaling to three pods, users got random 404s: the file was on pod A, but the load balancer sent the download to pod B. Disk also filled up, since nothing deleted old files.

They changed the flow: the service uploads each MP3 to object storage and returns a pre-signed URL valid for 15 minutes, with a lifecycle rule deleting files after 7 days. The React front end moved to a CDN. FastAPI now serves only JSON, and the 404s and disk alerts disappeared.

Follow-up questions to expect

  • "How do you serve one file, not a folder?" — Return FileResponse(path) from a route; it streams the file and sets headers.
  • "How do templates fit in?" — Jinja2Templates from fastapi.templating renders HTML pages, but for an API service a separate front end is more common.
  • "What does name="static" do?" — It lets you build URLs with request.url_for("static", path="logo.png").