Serving Static Files in FastAPI
Learn to serve static files from your FastAPI app step by step — mount static directories, set up routes, and handle common edge cases.
Focus: serving static files from your fastapi app
Serving static files from your FastAPI app seems trivial — until you realize that a missing StaticFiles import or a wrong mount path turns your sleek API into a 404 machine. Images don't load, CSS is invisible, and your single-page app frontend feels broken. This lesson fixes that pain by showing you exactly how to configure FastAPI to deliver static assets reliably, so your frontend and backend work together from day one.
The problem this lesson solves
Your API returns JSON, but every web app needs more than data. You need CSS, JavaScript, images, fonts, and maybe a favicon. When those files are requested, your FastAPI app doesn't know how to serve them — it treats every URL as an API endpoint and returns a 404 error.
The problem is that FastAPI, by default, is built for APIs, not static file delivery. If you've ever tried to link a stylesheet and seen a blank page in the browser, you've hit this exact wall.
Without a solution, you'll end up with messy workarounds: putting static files on a separate CDN, running a second web server, or embedding CSS inline in Python strings. All of those are fragile, slow, and hard to maintain. You need a way to serve static files from your FastAPI app directly, with minimal configuration.
This lesson gives you that solution — using FastAPI's built-in StaticFiles class. You'll learn how to mount a directory of static assets, route them correctly, and handle edge cases like caching and missing files.
Core concept / mental model
Think of your FastAPI app as a receptionist at a company. When a request comes in, the receptionist checks a directory of rules: if a URL matches an API route, it forwards to the appropriate function. If the URL matches a mounted static path, it fetches the file from the filesystem and returns it.
FastAPI uses StaticFiles to create this 'file directory' inside your app. Mounting static files adds a sub-application to your main app. When a request hits the mount path (e.g., /static), Starlette — the framework underneath FastAPI — intercepts it and serves the corresponding file from the specified directory.
Definitions you need
- Mount: To attach a sub-application or a routes handler at a specific path. FastAPI's
app.mount()method is used for this. - StaticFiles: A Starlette class that handles serving files from a directory. It can serve individual files or whole directories, and it supports features like
html=Truefor servingindex.htmlautomatically. - Mount path: The URL prefix where your static files will be available (e.g.,
/static). - Static directory: The filesystem folder where your static files live (e.g.,
app/static).
When you mount static files, you're essentially telling FastAPI: "For any URL starting with /static, look in this folder on disk and return the matching file. Don't treat it as an API endpoint." This is a fundamental concept for full-stack development with FastAPI.
How it works step by step
Let's break down the process of serving static files from your FastAPI app:
-
Create a static directory — Organize your static assets (CSS, JS, images) in a folder, typically named
staticinside your project. -
Import
StaticFiles— Fromfastapi.staticfiles, import theStaticFilesclass. -
Mount the directory — Use
app.mount('/static', StaticFiles(directory='static'), name='static'). This tells FastAPI to serve anything under/staticfrom thestaticdirectory. Thenameparameter gives the mount a name, useful in templates. -
Test with a browser or
curl— Visit a URL likehttp://localhost:8000/static/styles.cssand you should see your CSS file's content. -
Handle the HTML option — If you want to serve
index.htmlautomatically when the root of the mount is requested, sethtml=TrueinStaticFiles. -
Use relative URLs in your frontend — In your HTML, reference static files with the mount path, e.g.,
<link rel="stylesheet" href="/static/styles.css">.
The key is the mount call — it creates a new routing layer that takes precedence over your normal API routes for the given prefix.
Hands-on walkthrough
Let's put it into practice. We'll build a minimal FastAPI app that serves a simple HTML page with a CSS file and an image.
Project structure
First, create your project folders:
mkdir fastapi_static_demo
cd fastapi_static_demo
mkdir static
echo '{"name": "FastAPI Static Demo", "version": "1.0.0"}' > package.json
Now, create your static files. Save a simple CSS file:
/* static/styles.css */
body {
font-family: sans-serif;
background-color: #f0f0f0;
color: #333;
}
h1 {
color: #2c3e50;
}
And a simple image — you can use any small image, or create a placeholder with Python:
# generate_placeholder.py
from PIL import Image
img = Image.new('RGB', (200, 100), color='red')
img.save('static/logo.png')
Run the script to create the image:
python generate_placeholder.py
Now, create your main FastAPI app:
# main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# Mount the static directory at /static
app.mount('/static', StaticFiles(directory='static'), name='static')
@app.get('/')
async def root():
return {"message": "Hello from FastAPI! Visit /static/styles.css"}
Run the server:
uvicorn main:app --reload
Now open your browser:
- Go to
http://localhost:8000/static/styles.css— you should see the CSS content. - Go to
http://localhost:8000/static/logo.png— your image should display.
If you want to serve an index.html automatically, create a static HTML file and mount with html=True.
First, create static/index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<h1>FastAPI Static Demo</h1>
<img src="/static/logo.png" alt="Logo">
</body>
</html>
Then modify the mount to use html=True:
app.mount('/', StaticFiles(directory='static', html=True), name='static')
Now visiting http://localhost:8000/ will serve index.html automatically, and the CSS and image will load from /static.
Pro tip: Using
html=Trueis a quick way to serve a single-page front-end without a separate API route. However, be careful — mounting at'/'will take precedence over your API routes, so only use this if you don't have any other root-level routes.
Compare options / when to choose what
When serving static files from your FastAPI app, you have a few choices. Each has its strengths and trade-offs.
| Method | Best for | Pros | Cons |
|---|---|---|---|
StaticFiles with html=True |
Single-page apps or demos | Serves index.html automatically; simple |
Mount at root can conflict with API routes |
StaticFiles at a subpath (e.g., /static) |
Most APIs that need separate frontend assets | No conflict with API routes; clean separation | Must reference files with full /static/... URLs |
FileResponse for individual files |
A few files, custom logic (e.g., auth) | Full control over headers and caching | Verbose; not scalable for many files |
| External CDN | High-traffic production apps | Performance, caching, global distribution | Extra setup; not part of your app |
When to choose what:
- For a quick prototype or internal tool, mount at a subpath like /static — simple and safe.
- For a fully frontend-heavy app where the backend is just an API, use html=True at root, but be aware of route conflicts.
- For files that require authentication or dynamic generation, use FileResponse inside a normal route.
- For production with high traffic, serve static files from a CDN or your reverse proxy (e.g., Nginx) and only use StaticFiles in development.
Troubleshooting & edge cases
Even with a simple setup, you'll run into issues. Here are the most common and how to fix them.
1. 404 not found for static files
Symptom: curl http://localhost:8000/static/styles.css returns 404.
Causes & fixes:
- The directory path is wrong. Check that your StaticFiles(directory=...) points to the correct folder. If your app runs from a different working directory, use an absolute path or compute it relative to the file.
- The file doesn't exist in the folder (typo in filename).
- FastAPI may have a __init__.py issue if your directory is a package — ensure your static folder is a plain directory, not a package.
2. Mount at root blocks all API routes
Symptom: After mounting at '/', your @app.get("/api/...") routes return 404.
Cause: Mounting at '/' makes the static sub-application handle every request that doesn't match a more specific route. Since the mount is registered first, it catches everything.
Fix: Mount at a subpath like /static, or move your API routes to a prefix like /api and mount at '/' only if you are sure no other routes exist.
3. Files load but are served with wrong MIME type
Symptom: CSS is served as text/plain or JavaScript not executing.
Cause: Starlette infers MIME types from file extensions via mimetypes. If the extension is non-standard, it defaults to application/octet-stream.
Fix: Ensure files have standard extensions like .css, .js, .png. If you need custom types, override the media_type parameter when serving via FileResponse.
4. Static files updated but not reflected in browser
Symptom: You change a CSS file, refresh, but see old styles.
Cause: Browser caching. The response includes a Last-Modified header, but your browser may cache aggressively.
Fix: Add a cache-busting query parameter (?v=2) in your HTML, or configure Cache-Control: no-cache in your proxies or responses.
What you learned & what's next
You now understand how to serve static files from your FastAPI app — mounting directories, setting up routes, and troubleshooting common issues. You can create a frontend and connect it to your API, or serve a simple one-page app directly from FastAPI.
Key takeaways:
- StaticFiles is the go-to tool for serving static assets from FastAPI.
- Mount at a subpath like /static to avoid conflicts with API routes.
- Use html=True only when you want automatic index.html serving and have no other root routes.
- Always verify your directory paths to avoid 404s.
- In production, consider offloading static serving to a CDN or reverse proxy.
Now, you're ready to dive deeper. The next lesson in this track is Deploying FastAPI: Running in Production — you'll learn how to take your app from development to a production-ready service, including handling static files with a proper web server.
Practice recap
Practice exercise: Build a small FastAPI app that serves a static HTML page with an image and a CSS file. Mount the static directory at /assets and try accessing files both with and without html=True. Then, change one CSS property and observe how clearing your browser cache affects the update. This will solidify your understanding of static file serving and caching.
Common mistakes
- Mounting static files at the root
/without realizing it shadows API routes, causing 404s for all/apiendpoints. - Forgetting to set
html=Truewhen you expectindex.htmlto be served automatically, resulting in a 404 at the root. - Using a relative path for the static directory that doesn't match the actual working directory when running with
uvicornfrom a different folder. - Ignoring browser caching, leading to confusion when static file changes don't appear after a refresh.
Variations
- Use
FileResponseinside a normal route for serving files that require authentication or dynamic generation. - Serve static files via a reverse proxy (e.g., Nginx) in production instead of FastAPI directly, while keeping
StaticFilesfor development. - Integrate with a frontend build tool (like Vite) that outputs static files to a
distfolder, then mount that folder withStaticFiles.
Real-world use cases
- Serving a React or Vue.js single-page app built into a
distfolder, mounted at/withhtml=Truefor a seamless full-stack app. - Providing downloadable resources (e.g., CSV exports, PDFs) from a FastAPI backend using
StaticFilesorFileResponsebased on permissions. - Serving a documentation site (like MkDocs output) as static files from your FastAPI app's root, while keeping API routes under
/api.
Key takeaways
StaticFilesis the core tool for serving static files from your FastAPI app.- Mount at a subpath like
/staticto keep your API routes clean and avoid conflicts. - Set
html=Trueonly when you want automaticindex.htmlserving and have no other root routes. - Always double-check directory paths to prevent 404s — use absolute paths if in doubt.
- In production, offload static file serving to a CDN or reverse proxy for better performance.
- Cache-busting is essential when updating static assets in a browser environment.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.