FastAPI 结合 Jinja2 模板引擎:使用 Jinja2Templates 实现服务端 HTML 渲染
在 FastAPI 中渲染 HTML 页面并不需要引入额外框架——你可以直接使用任意喜欢的模板引擎,官方推荐的常见选择是 Jinja2(与 Flask 等工具使用的引擎相同)。本文基于 FastAPI 官方高级指南的 Templates 章节(见 官方英文文档 与 西语翻译版),结合仓库内配套示例源码与自动化测试,完整讲解如何安装依赖、创建 Jinja2Templates 对象、编写 .html 模板、注入上下文变量、在模板中调用 url_for() 生成路由与静态资源链接,以及用 TestClient 编写可回归验证的测试。读完本文,你将能熟练地为 FastAPI 应用搭建一整套"路由 + 模板 + 静态文件"的服务端渲染(SSR)流程。
安装 Jinja2 依赖
Jinja2Templates 本身由 Starlette 提供,但渲染引擎 Jinja2 属于第三方依赖,需要显式加入项目。在仓库根目录执行:
$ uv add jinja2
---> 100%
安装完成后,FastAPI 应用即可通过 fastapi.templating 导入 Jinja2Templates 使用。仓库中配套示例源码 docs_src/templates/tutorial001_py310.py 的完整运行前提,就是 templates/ 目录与 static/ 目录就位(自动化测试也印证了这一点,见下文"编写测试"一节)。
使用 Jinja2Templates 渲染响应
使用模板渲染 HTML 的核心流程共四步,官方文档明确归纳为:
- 导入
Jinja2Templates; - 创建一个可复用的
templates对象; - 在将要返回模板的 path operation(路径操作函数)中声明一个
Request参数; - 调用
templates上的方法渲染并返回一个TemplateResponse,向其传入模板名称、request对象,以及一个由键值对组成的"context"(上下文)字典,供 Jinja2 模板内部使用。
下面这段就是官方教程的完整可运行代码(来源:docs_src/templates/tutorial001_py310.py):
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}
)
逐行解读关键点
Jinja2Templates(directory="templates"):模板对象在应用启动时就创建一次,之后可在多个 path operation 中复用。directory指向存放.html模板文件的目录。官方建议以相对路径运行时,应保证当前工作目录下存在该目录(测试中会先复制docs_src/templates/templates/到工作区./templates,见 tests/test_tutorial/test_templates/test_tutorial001.py)。request: Request:模板渲染需要访问请求对象(例如用于生成绝对 URL、获取请求作用域上下文)。把它作为参数声明后,FastAPI 会自动注入当前请求,这也是为何调用TemplateResponse时必须显式传入request。templates.TemplateResponse(request=request, name="item.html", context={"id": id}):返回一个响应对象交给 FastAPI。其中name是相对directory的模板文件路径,context字典中的每个键都能在模板中直接作为变量使用。
仓库里的 fastapi/templating.py 只有一行内容:from starlette.templating import Jinja2Templates as Jinja2Templates。这意味着 FastAPI 并未重写模板引擎,而是把 Starlette 的实现原样再导出,作为对开发者的便利。官方文档也特意注明:你也可以直接写 from starlette.templating import Jinja2Templates,两者等价;同样地,Request、StaticFiles 以及绝大多数响应类也都直接来自 Starlette。
版本兼容性提示
官方文档对旧版本 API 差异做了明确提醒,若你维护的是老项目,迁移时需注意:
- 在 FastAPI 0.108.0 / Starlette 0.29.0 之前,
name(模板名称)曾是TemplateResponse的第一个位置参数,调用形如templates.TemplateResponse("item.html", {"request": request, "id": id}); - 更早的版本中,
request是作为 context 键值对的一部分(即以"request"为键)传给 Jinja2 的。
新代码应统一采用本文示例中 request=、name=、context= 的关键字传参写法。
建议声明 response_class=HTMLResponse
在 path operation 上声明 response_class=HTMLResponse 能让自动生成的交互式文档 UI 正确得知该接口返回的是 HTML(text/html),从而提升文档页展示的准确性。这是一个值得长期养成的习惯。
编写 Jinja2 模板
将渲染逻辑与展示层分离后,把 HTML 模板写入 templates/item.html。仓库配套模板内容如下(来源:docs_src/templates/templates/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>
模板上下文值(Template Context Values)
Jinja2 用双花括号 {{ ... }} 输出变量。模板中的:
Item ID: {{ id }}
会取自上一步传给渲染函数的 context 字典:
{"id": id}
例如请求 id = 42 时,该片段渲染结果就是:
Item ID: 42
context 字典可以是任意键值对(字符串、数字、列表、Pydantic 模型、数据库查询结果均可),它们在模板内部以同名变量被访问。这种"函数侧组装数据、模板侧负责展示"的模式,是 FastAPI 服务端渲染页面最基础的数据流。
模板中的 url_for 与路由参数
在模板内也可以调用 url_for(),其参数与 path operation function 声明的参数保持一致。例如:
<a href="{{ url_for('read_item', id=id) }}">
这段会生成指向 read_item(id=id) 这条 path operation 所对应路由的链接。当 id = 42 时渲染结果为:
<a href="/items/42">
url_for 的第一个参数是路由(或挂载点)的 name:若使用 @app.get("/items/{id}") 装饰器定义,默认 name 即为函数名 read_item;也可在装饰器中通过 name= 参数显式指定。它能把"渲染时的路径拼接"交给框架完成,避免在模板中硬编码 URL,日后路由前缀调整时模板无需改动。
模板与静态文件:url_for("static", ...) 配合 StaticFiles
页面几乎必然需要 CSS、JavaScript、图片等静态资源。FastAPI 中先通过 app.mount 把静态目录挂载为应用子路由,并指定 name:
app.mount("/static", StaticFiles(directory="static"), name="static")
对应地,仓库的静态资源目录为 docs_src/templates/static/styles.css,内容如下:
h1 {
color: green;
}
在模板中就可以借助同一个 url_for() 生成静态文件链接:
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">
因为目录被挂载在 /static 且命名为 "static",url_for('static', path='/styles.css') 会解析出 /static/styles.css。由于你使用了 StaticFiles,该 CSS 文件会被 FastAPI 应用自动托管于 URL /static/styles.css,无需为每个静态文件单独编写 path operation。运行时浏览器请求 /items/foo 页面时会顺带加载该样式,<h1> 文本随之显示为绿色。
注意:这里 url_for 的第二参数与上一节的 id=id 不同——针对静态文件挂载点,传入的是形如 path='/styles.css' 的关键字参数,它对应挂载点路径下的文件相对路径(Starlette 的静态文件路由会按 path 定位文件)。
用 TestClient 验证模板渲染与静态文件
官方文档在"更多细节"一节提示可参考 Starlette 关于模板的文档了解如何编写模板测试。仓库则直接给出了可运行的回归测试(tests/test_tutorial/test_templates/test_tutorial001.py),它验证了整条链路:
import os
import shutil
from fastapi.testclient import TestClient
from tests.utils import workdir_lock
@workdir_lock
def test_main():
if os.path.isdir("./static"): # pragma: nocover
shutil.rmtree("./static")
if os.path.isdir("./templates"): # pragma: nocover
shutil.rmtree("./templates")
shutil.copytree("./docs_src/templates/templates/", "./templates")
shutil.copytree("./docs_src/templates/static/", "./static")
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
shutil.rmtree("./templates")
shutil.rmtree("./static")
该测试揭示了三个可复用的验证要点:
- 模板渲染正确性:请求
/items/foo返回 200,且响应体中包含渲染后的 HTML 片段<h1><a href="http://testserver/items/foo">Item ID: foo</a></h1>——可见模板中的url_for('read_item', id=id)被正确解析为当前请求基址下的/items/foo,{{ id }}也被替换为路径参数foo; - 静态文件可达性:请求
/static/styles.css返回 200,且内容包含color: green;,证明StaticFiles挂载与模板内静态链接解析均生效; - 运行环境约束:测试通过
@workdir_lock加锁、并在运行前后复制/清理./templates与./static目录,说明Jinja2Templates(directory="templates")是按当前工作目录解析路径的——自建项目时务必保证进程工作目录中包含该相对目录,或用Path(__file__).parent等绝对路径方式构造目录,以免运行环境不同导致模板或静态文件找不到。
小结
将以上各部分组合起来,一个最小可用的 FastAPI 服务端渲染应用只需要三个文件:负责路由与数据装配的 main.py(含 Jinja2Templates 对象与 StaticFiles 挂载)、templates/item.html(HTML + Jinja2 语法)、static/styles.css(可选静态资源)。核心心智模型可总结为三句话:
Jinja2Templates(directory=...)一次性创建、多处复用,它是渲染入口;- path operation 中声明
Request参数,渲染时以request=、name=、context=关键字调用,返回TemplateResponse; - 模板内使用
{{ 变量 }}消费 context,用url_for('read_item', ...)与url_for('static', path=...)生成路由与静态资源链接,避免手写硬编码路径。
配合 TestClient 断言渲染结果,即可在 CI 中持续守护模板改动的正确性。若需深入了解模板上下文处理器、Jinja2 环境定制等进阶能力,可继续查阅 Starlette 官方关于模板的文档(原文档"More details"一节提供了入口),并结合本仓库 docs/en/docs/advanced/templates.md 与配套示例持续对照学习。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00