FastAPI 不仅能构建 API 接口,还内置了对静态文件服务的支持。通过 StaticFiles,你可以轻松地将图片、CSS、JavaScript 或任意静态资源目录挂载到应用中,像传统 Web 服务器一样对外提供文件访问。

为什么需要静态文件服务

大多数 Web 应用除了 API 接口外,还需要直接向浏览器提供静态资源。比如:

  • 前端打包后的 HTML、CSS、JS 文件
  • 用户上传的头像、附件
  • 接口文档引用的图片资源
  • favicon 等站点图标

FastAPI 基于 Starlette,而 Starlette 提供了 StaticFiles 这个 ASGI 应用,可以直接挂载到 FastAPI 的路由体系中使用。

基础用法:挂载目录

StaticFiles 位于 starlette.staticfiles(也可从 fastapi.staticfiles 导入),使用 app.mount() 将其挂载到指定路径前缀下:

1
2
3
4
5
6
7
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# 将本地的 static 目录挂载到 /static 路径
app.mount("/static", StaticFiles(directory="static"), name="static")

目录结构假设如下:

1
2
3
4
5
6
7
project/
├── main.py
└── static/
├── logo.png
├── style.css
└── images/
└── banner.jpg

挂载后访问规则:

请求路径 对应本地文件
/static/logo.png static/logo.png
/static/style.css static/style.css
/static/images/banner.jpg static/images/banner.jpg

注意:app.mount() 挂在指定路径下的 子应用,该路径本身不会被 FastAPI 的路由处理 — 完全由 StaticFiles 接管。

关键参数详解

StaticFiles 构造函数的常用参数:

1
2
3
4
5
6
7
StaticFiles(
directory="static", # 必填:本地文件目录
packages=None, # 可选:Python 包列表(如 Jinja2 静态资源)
html=False, # 是否将 directory 下的 HTML 文件当作文本返回
check_dir=True, # 启动时检查目录是否存在
follow_symlinks=False, # 是否跟随符号链接
)

html 参数

html=True 时,访问 index.html 等文件会以 text/html Content-Type 返回,适合挂载前端 SPA 的打包产物:

1
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")

packages 参数

用于引用已安装 Python 包内的静态资源:

1
app.mount("/static", StaticFiles(packages=[("my_package", "static")]), name="static")

这会查找 my_package 包内的 static 子目录。

挂载顺序与路由优先级

挂载路径优先于路由。Starlette 按注册顺序匹配,一旦某个 mount 路径前缀命中,后续路由就不会再检查。错误示例:

1
2
3
4
5
6
7
8
app = FastAPI()

# ❌ 错误:先挂载 /,会吞掉所有 API 路由
app.mount("/", StaticFiles(directory="dist", html=True), name="frontend")

@app.get("/api/hello") # 永远不会被命中!
async def hello():
return {"message": "Hello"}

正确做法是 API 路由注册在前,静态文件挂载在后(或者用不同路径前缀):

1
2
3
4
5
6
7
8
9
app = FastAPI()

# ✅ API 路由在前
@app.get("/api/hello")
async def hello():
return {"message": "Hello"}

# ✅ 静态文件挂载在后,且路径前缀不冲突
app.mount("/", StaticFiles(directory="dist", html=True), name="frontend")

或者更清晰的方式 — 使用不同前缀:

1
2
3
4
5
6
7
app = FastAPI()

@app.get("/api/hello")
async def hello():
return {"message": "Hello"}

app.mount("/app", StaticFiles(directory="dist", html=True), name="frontend")

运行时动态添加静态目录

如果需要根据运行时条件挂载不同目录(如按用户区分的上传目录),可以在路由层手动构造 StaticFiles 应用后挂载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import os

app = FastAPI()

@app.get("/api/hello")
async def hello():
return {"message": "Hello"}


# 假如通过环境变量控制静态文件目录
static_dir = os.getenv("STATIC_DIR", "static")
app.mount("/static", StaticFiles(directory=static_dir), name="static")

如果需要更复杂的逻辑(如按用户 id 分流),则不适合用 mount,应改用 FileResponse 在路由中手动处理文件返回。

配合 FileResponse 精确控制

对于需要权限校验或动态路径的单个文件返回,使用 FileResponse 而非全局挂载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from fastapi import FastAPI
from fastapi.responses import FileResponse
import os

app = FastAPI()

@app.get("/download/{filename}")
async def download_file(filename: str):
file_path = os.path.join("uploads", filename)
if not os.path.isfile(file_path):
return {"error": "文件不存在"}
return FileResponse(
path=file_path,
filename=filename,
media_type="application/octet-stream"
)

FileResponse 支持的关键参数:

参数 说明
path 文件路径
filename 下载时显示的文件名(触发 Content-Disposition)
media_type 显式指定 MIME 类型
headers 自定义响应头字典

挂载多个静态目录

FastAPI 支持同时挂载多个静态目录到不同路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

@app.get("/api/health")
async def health():
return {"status": "ok"}

# 前端资源
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")

# 用户上传文件
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")

# 静态资源
app.mount("/static", StaticFiles(directory="static"), name="static")

各挂载点互不干扰,各自管理自己的目录。

生产环境注意事项

StaticFiles 使用 Python 的 aiofiles 做异步文件 I/O,在开发和小规模场景足够。但在高并发生产环境中,建议:

  1. 由 Nginx / Caddy 等反向代理直接处理静态文件请求
  2. FastAPI 只处理 API 逻辑,静态文件路径在反向代理层拦截

Nginx 配置示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 80;
server_name example.com;

# 静态文件由 Nginx 直接返回
location /static/ {
alias /app/static/;
expires 30d;
}

# API 请求转发给 FastAPI
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}
}

总结

场景 推荐方案
全局静态资源目录 app.mount("/static", StaticFiles(...))
前端 SPA 部署 app.mount("/", StaticFiles(..., html=True))
需要权限控制的文件下载 路由中使用 FileResponse
高并发生产环境 Nginx 直接处理,FastAPI 只做 API

掌握 StaticFilesFileResponse 两种方式,就能灵活应对从开发到生产的不同静态文件需求。