首页
/ FastAPI 模板渲染实战:使用 Jinja2Templates 构建服务端渲染页面

FastAPI 模板渲染实战:使用 Jinja2Templates 构建服务端渲染页面

2026-09-07 18:29:36作者:姚月梅Lane

导读

本文围绕 FastAPI 官方文档中 advanced/templates 一页(docs/hi/docs/advanced/templates.md),系统讲解如何在 FastAPI 中接入模板引擎(以 Jinja2 为例)进行服务端页面渲染。读完本文,你将掌握模板依赖的安装方式、Jinja2Templates 的正确使用方法与参数签名、如何在 Jinja2 模板内渲染上下文变量、调用 url_for() 生成路由链接并挂载静态文件,同时结合当前仓库源码理解其与 Starlette 的底层关系及测试验证方式。


模板引擎:FastAPI 并不限定某一家

FastAPI 官方文档明确指出:你可以在 FastAPI 中使用任何你喜欢的模板引擎(template engine)。模板引擎负责把「HTML 骨架 + 动态数据」合成完整的 HTML 页面,是服务端渲染(Server-Side Rendering)的核心一环。

在实际项目中,最普遍的选择是 Jinja2——它同样是 Flask 等其他知名工具使用的模板引擎,语法简单、生态成熟。更关键的是,FastAPI 官方为 Jinja2 提供了「开箱即用」的配置工具类,你可以直接在 FastAPI application 里使用它,而这些工具实际上由 Starlette(FastAPI 底层的 ASGI 框架)提供。

一句话归纳:FastAPI 提供便捷入口,Starlette 负责具体实现,两者配合让你专注写页面逻辑而不必关心集成细节。


安装依赖:为项目加入 jinja2

使用 Jinja2Templates 前,需要先把 jinja2 加入项目依赖。仓库使用的示例命令是 uv

$ uv add jinja2

---> 100%

命令执行成功后,jinja2 会写入项目的依赖清单(对应当前仓库的 uv.lock / pyproject.toml 管理机制)。如果你习惯使用其他包管理器,也可以用 pip install jinja2poetry add jinja2 达到同样效果,核心是保证运行环境中能够 import jinja2


使用 Jinja2Templates:四个固定步骤

在 FastAPI 中接入 Jinja2 的标准流程,可以拆解为以下四个步骤:

  1. 导入 Jinja2Templates
  2. 创建一个可复用的 templates 对象(指向存放模板文件的目录);
  3. 将要返回模板path operation声明一个 Request 参数
  4. 使用前面创建的 templates 对象渲染并返回 TemplateResponse,传入三个要素:模板文件名 namerequest 对象、包含 key-value 对的「context」字典(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}
    )

逐行拆解这段代码中的关键细节:

  • templates = Jinja2Templates(directory="templates"):创建模板对象并指向 templates/ 目录。这里 directory相对当前工作目录的路径,也可以传绝对路径;你完全可以只创建一次然后在多个路由中复用它。
  • def read_item(request: Request, id: str)必须声明 request: Request 参数,因为 TemplateResponse 需要它(它的作用之一是后续在模板里通过 url_for 正确生成 URL)。
  • return templates.TemplateResponse(...):注意示例使用的调用方式是关键字参数——request=name=context=。其中 context 字典里的 id 会被模板读取渲染。
  • response_class=HTMLResponse建议声明。这能让 /docs 交互式文档界面(Swagger UI)知道该接口返回的是 HTML 内容,从而在 OpenAPI schema 中正确标注 media type。

💡 Tip(来自原文档):声明 response_class=HTMLResponse 后,docs UI 才能正确识别响应是 HTML。


版本兼容提示:TemplateResponse 的参数签名演进

官方文档特别标注了一条重要的版本兼容信息(详见 docs/hi/docs/advanced/templates.md 的 note 区块):

  • 在 FastAPI 0.108.0 / Starlette 0.29.0 之前TemplateResponse 的第一个参数是 name,即调用方式为 templates.TemplateResponse("item.html", {"request": request, "id": id}) 这类位置参数写法;
  • 同样在更早的版本中request 对象需要作为普通键值对手工放进 Jinja2 的 context 里(例如 {"request": request, ...}),而不是像现在这样作为独立的 request= 关键字传入。

如果你在旧版代码或网上旧教程中看到类似 TemplateResponse("name.html", {"request": request}) 的写法,请留意它与当前新签名(TemplateResponse(request=request, name="name.html", context={...}))的差异。当前仓库的示例统一采用新签名,升级项目时应同步迁移。


编写模板:templates/item.html

创建好 templates 对象并声明路由后,接下来就是在模板目录中编写真正的 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 }}

其中 {{ id }} 会从你传入的 context 字典中取出 id 对应的值:

{"id": id}

也就是说,context 字典的 key 就是模板内可直接引用的变量名。例如当请求路径为 /items/42(即 id42)时,最终渲染出的 HTML 就是:

Item ID: 42

模板内调用 url_for():生成路由链接

在 Jinja2 模板里你同样可以使用 url_for(),它接受的参数与你 path operation function 使用的参数一致,用于反向生成路由对应的 URL

例如模板中:

<a href="{{ url_for('read_item', id=id) }}">

这会生成一条指向「处理 read_item(id=id) 的 path operation」的链接。当 id42 时,渲染结果为:

<a href="/items/42">

这个机制的巧妙之处在于:URL 只定义一次(在路由装饰器中),模板侧通过函数名 + 参数反向生成,避免在 HTML 中硬编码路径——将来即使改了路由路径,只要函数名和参数不变,模板无需改动。这一点也印证了 Request 参数为何必须传入:url_for 需要借助当前请求上下文来解析完整、正确的 URL。


模板与静态文件:Jinja2 × StaticFiles

服务端渲染页面几乎离不开 CSS / JS / 图片等静态资源。FastAPI + Jinja2 的组合方式非常直接:

  1. 先用 app.mount("/static", StaticFiles(directory="static"), name="static") 挂载静态目录,并给挂载点命名 name="static"
  2. 在模板内通过 url_for('static', path='/styles.css') 引用具体文件。

前面 item.html 中的 <head> 部分正是这样做的:

<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">

它生成的地址会指向 static/styles.css。仓库对应的样式文件为 docs_src/templates/static/styles.css

h1 {
    color: green;
}

由于你挂载了 StaticFiles,这个 CSS 文件会被 FastAPI application 在 URL /static/styles.css自动对外提供(对应的实际目录文件是 static/styles.css),无需再手写任何静态文件路由。

注意此处 url_for 的第二个参数是 path='/styles.css'——它表示相对于静态挂载点的子路径,最终拼接出 /static/styles.css


整体运行与测试验证

把上述三块拼起来(docs_src/templates/tutorial001_py310.py + templates/item.html + static/styles.css),并保证目录结构如下:

./
├── main.py                 # 应用入口(含上述代码)
├── templates/
│   └── item.html
└── static/
    └── styles.css

启动方式与普通 FastAPI 应用一致:

$ uvicorn main:app --reload

然后访问:

  • http://localhost:8000/items/42 → 返回渲染后的 HTML 页面(绿色标题、可点击链接 Item ID: 42);
  • http://localhost:8000/static/styles.css → 直接返回 CSS 文件内容;
  • http://localhost:8000/docs → 看到接口响应类型被标注为 HTML。

仓库的自动化测试也完整覆盖了这一示例,测试文件位于 tests/test_tutorial/test_templates/test_tutorial001.py。它使用 TestClient 对该示例做了端到端断言,值得对照阅读:

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

测试逻辑透露出三个要点:

  1. 动态路由 /items/{id} 能正确渲染模板并把上下文值(这里是 foo)写入 HTML;
  2. url_for('static', ...)TestClient 环境下会解析出带 host 的完整 URL(http://testserver/...),进一步说明 Request 与请求上下文参与 URL 生成;
  3. 挂载的 /static/styles.css 能独立、正常地返回 CSS 文件。

从测试结构还可看出一个工程细节:该测试需要先把 docs_src/templates/ 下的 templates/static/ 目录复制到测试运行目录,原因是示例中使用了相对路径 directory="templates"directory="static",即路径解析依赖「当前工作目录」。这提醒我们在真实项目中尽量使用确定的工作目录绝对路径来定位模板与静态目录,避免因启动目录不同导致 FileNotFoundError


源码层面:FastAPI 与 Starlette 的关系

如果你想追根究底,可以打开仓库中的 fastapi/templating.py,它的全部内容只有一行:

from starlette.templating import Jinja2Templates as Jinja2Templates  # noqa

这行代码直观说明了原文档「技术细节」区块所强调的事实:

  • Jinja2Templates 的真正实现在 Starlettestarlette.templating 模块中;
  • FastAPI 只是出于开发者便利,把同一个类原样转发导出fastapi.templating.Jinja2Templates,所以下面两种导入方式完全等价:
from fastapi.templating import Jinja2Templates      # 推荐,FastAPI 统一入口
from starlette.templating import Jinja2Templates    # 同样可行,直接走底层

实际上,不只是模板系统,RequestStaticFiles 等大多数响应与请求基础组件同样直接源自 Starlette——FastAPI 在此之上提供了自动校验、序列化、OpenAPI 文档等「增量价值」。理解了这层转发关系,你在排查模板渲染问题时就清楚应该去查阅 Starlette 对 Jinja2Templates(以及它内部的 envcontext_processor 等机制)的实现约定。


延伸:更多细节去哪里找

本文覆盖的是模板渲染的完整主干流程。关于更深入的内容——例如如何在测试中校验模板、如何自定义模板环境、上下文处理器等——原文档建议进一步参阅 Starlette 官方的模板文档。

如果你希望在本仓库内继续动手验证,推荐按如下顺序阅读:

  1. 完整示例源码:docs_src/templates/tutorial001_py310.py
  2. 模板文件:docs_src/templates/templates/item.html
  3. 静态样式:docs_src/templates/static/styles.css
  4. FastAPI 的转发实现:fastapi/templating.py
  5. 端到端测试:tests/test_tutorial/test_templates/test_tutorial001.py

小结:FastAPI 服务端渲染的最小心法

在 FastAPI 中使用 Jinja2 渲染服务端页面,只需记住四条主线:jinja2 → 建 Jinja2Templates(directory=...) → 路由里声明 Request 并用关键字参数 request=... / name=... / context=... 返回 TemplateResponse → 在模板里用 {{ 变量 }} 插值、用 url_for() 反查路由与静态资源。若遇到旧写法(name 作为第一参数、request 塞进 context),请对照版本迁移说明进行更新。这套模式把 FastAPI 的接口能力与 Jinja2 的模板能力无缝衔接,足以支撑从简单页面到较完整服务端渲染应用的实际开发。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525