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.
1from fastapi import FastAPI2from fastapi.staticfiles import StaticFiles34app = FastAPI()56@app.get("/api/health")7def health():8 return {"ok": True}910app.mount("/static", StaticFiles(directory="static"), name="static")11app.mount("/", StaticFiles(directory="dist", html=True), name="ui") # the built front end; mount LASTReal results with a small dist/ folder containing index.html and assets/app.css:
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 -> 404Three lessons from these lines:
- Order matters. A mount at
/catches every path. Declared before the API route, it swallowed/api/healthand answered 404, because there is no such file. html=Trueis not a single-page-app fallback. It servesindex.htmlfor a directory and404.htmlif present, but a client-side route such as/courses/42still returns 404. For a SPA, add a catch-all route that returnsindex.html, or better, let the CDN or nginx handle it.- Path traversal is blocked.
StaticFilesrefuses 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?" —
Jinja2Templatesfromfastapi.templatingrenders HTML pages, but for an API service a separate front end is more common. - "What does
name="static"do?" — It lets you build URLs withrequest.url_for("static", path="logo.png").