FastAPI 服务端渲染实战:基于 Jinja2Templates 的模板渲染、url_for 与静态资源服务
在 FastAPI 中渲染 HTML 页面时,官方文档 docs/de/docs/advanced/templates.md 给出了一套标准方案:通过 Starlette 提供的 Jinja2Templates 工具类完成 Jinja2 模板的服务端渲染,并结合 StaticFiles 处理样式、脚本等静态资源。读完本篇,你将掌握如何安装配置模板依赖、编写可复用的模板对象、向模板注入上下文变量、在模板内部用 url_for() 生成正确链接,以及验证渲染结果的完整方法。
可以自由选择模板引擎
FastAPI 本身并不绑定任何特定的模板引擎——你可以使用任意你喜欢的引擎。最常见的选择是 Jinja2,也就是 Flask 等主流 Python Web 框架所使用的同一款引擎。
FastAPI(经由 Starlette 提供)内置了便于直接集成的配置工具,你几乎不需要手动初始化 Jinja2 环境。
安装依赖
模板渲染功能依赖 jinja2 包,将其加入项目即可:
$ uv add jinja2
---> 100%
使用 Jinja2Templates 渲染页面
整个流程包含四个步骤:
- 导入
Jinja2Templates; - 创建一个
templates对象,后续在多个路径操作中复用; - 在返回模板的 路径操作 中声明一个
Request参数; - 调用
templates.TemplateResponse(),传入模板名称、请求对象和一个"上下文"字典(键值对将被注入 Jinja2 模板中),渲染并返回TemplateResponse。
仓库中的完整示例见 教程示例代码:
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return templates.TemplateResponse(
request=request, name="item.html", context={"id": id}
)
参数顺序的变化(FastAPI 0.108.0 / Starlette 0.29.0)
需要注意版本兼容性问题:
- 在 FastAPI 0.108.0 和 Starlette 0.29.0 之前,
name(模板文件名)是TemplateResponse的第一个位置参数; - 更早的版本中,
request对象是作为上下文键值对的一部分传给 Jinja2 的,而不是独立的request=关键字参数。
因此在新版中应采用 TemplateResponse(request=..., name=..., context=...) 的关键字参数形式(如上例所示),以兼容当前版本。
声明 response_class=HTMLResponse 的价值
示例中路由装饰器显式声明了 response_class=HTMLResponse。这样做让交互式文档界面(Swagger UI)能够知道该端点的响应是 HTML 而非 JSON,从而在文档中正确展示响应类型。
fastapi.templating 与 starlette.templating 的关系
源码层面可以直接印证这一点:查看 fastapi/templating.py,整个文件只有一行:
from starlette.templating import Jinja2Templates as Jinja2Templates # noqa
也就是说,你既可以写 from fastapi.templating import Jinja2Templates,也可以写 from starlette.templating import Jinja2Templates,二者完全等价——FastAPI 只是把 Starlette 的 starlette.templating 以 fastapi.templating 的名义再导出一次,作为开发上的便利。同理,Request、StaticFiles 等能力也直接来自 Starlette。
编写模板文件
接下来在 templates/item.html 中编写模板。仓库中的实际模板文件见 item.html:
<html>
<head>
<title>Item Details</title>
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">
</head>
<body>
<h1><a href="{{ url_for('read_item', id=id) }}">Item ID: {{ id }}</a></h1>
</body>
</html>
模板上下文值(Context Values)
模板中这一行:
Item ID: {{ id }}
其值 id 取自你在调用 TemplateResponse 时传入的"上下文"字典:
{"id": id}
假设请求的 ID 为 42,该行将渲染为:
Item ID: 42
这就是模板上下文注入的核心机制:context 字典中的每个键都成为模板中可直接引用的变量名。
模板中的 url_for() 用法
你同样可以在模板内部使用 url_for(),它接收的命名参数与你路径操作函数的参数一致。上面的模板片段:
<a href="{{ url_for('read_item', id=id) }}">
生成的正是由路径操作函数 read_item(id=id) 所处理的 URL。当 ID 为 42 时渲染结果为:
<a href="/items/42">
这样做的好处是:链接由路由系统统一生成,即使将来路径前缀或路由模式变化,模板中的链接依然正确,无需手动拼接字符串。
模板与静态文件
url_for() 还可以配合以 name="static" 挂载的 StaticFiles 使用,这正是上面模板第 4 行的写法:
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">
在本示例中,它指向 static/styles.css,内容为:
h1 {
color: green;
}
由于挂载了 StaticFiles(见示例中 app.mount("/static", StaticFiles(directory="static"), name="static")),该 CSS 文件会自动由 FastAPI 应用在 URL /static/styles.css 处对外提供服务,模板中的 url_for('static', ...) 会生成指向该 URL 的链接。
测试验证:渲染结果如何被断言
仓库中的 模板测试 展示了如何验证整套流程:
def test_main():
# ... 将 docs_src 下的模板与静态文件复制到工作目录 ...
from docs_src.templates.tutorial001_py310 import app
client = TestClient(app)
response = client.get("/items/foo")
assert response.status_code == 200, response.text
assert (
b'<h1><a href="http://testserver/items/foo">Item ID: foo</a></h1>'
in response.content
)
response = client.get("/static/styles.css")
assert response.status_code == 200, response.text
assert b"color: green;" in response.content
测试验证了两件事:
GET /items/foo返回 200,且响应体包含由url_for('read_item', id=id)生成的完整绝对 URL(测试服务器地址为http://testserver/items/foo)与上下文变量渲染出的Item ID: foo——这印证了url_for生成的链接是绝对 URL(含 scheme 和 host),在模板中直接可用;GET /static/styles.css返回 200 且包含color: green;,印证了StaticFiles挂载后的静态资源自动服务行为。
小结
本篇覆盖了文档的全部核心内容:
- 使用
uv add jinja2安装模板引擎依赖; - 通过
Jinja2Templates(directory="templates")创建可复用的模板对象; - 路径操作声明
Request参数后,用TemplateResponse(request=..., name=..., context=...)渲染并返回 HTML(注意 0.108.0 前后的参数顺序差异); - 通过
response_class=HTMLResponse让文档 UI 正确识别 HTML 响应; - 用上下文字典向模板注入变量,用
url_for()在模板中生成路径操作和静态文件链接; - 用
StaticFiles挂载静态目录并自动对外服务; - 通过
TestClient断言渲染结果完成验证。
如需进一步了解模板测试等更多细节,可参考 Starlette 官方的 templates 文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00