首页
/ FastAPI 结合 Jinja2 模板引擎:使用 Jinja2Templates 实现服务端 HTML 渲染

FastAPI 结合 Jinja2 模板引擎:使用 Jinja2Templates 实现服务端 HTML 渲染

2026-09-07 10:03:47作者:殷蕙予

在 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 的核心流程共四步,官方文档明确归纳为:

  1. 导入 Jinja2Templates
  2. 创建一个可复用的 templates 对象;
  3. 在将要返回模板的 path operation(路径操作函数)中声明一个 Request 参数;
  4. 调用 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,两者等价;同样地,RequestStaticFiles 以及绝大多数响应类也都直接来自 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(可选静态资源)。核心心智模型可总结为三句话:

  1. Jinja2Templates(directory=...) 一次性创建、多处复用,它是渲染入口;
  2. path operation 中声明 Request 参数,渲染时以 request=name=context= 关键字调用,返回 TemplateResponse
  3. 模板内使用 {{ 变量 }} 消费 context,用 url_for('read_item', ...)url_for('static', path=...) 生成路由与静态资源链接,避免手写硬编码路径。

配合 TestClient 断言渲染结果,即可在 CI 中持续守护模板改动的正确性。若需深入了解模板上下文处理器、Jinja2 环境定制等进阶能力,可继续查阅 Starlette 官方关于模板的文档(原文档"More details"一节提供了入口),并结合本仓库 docs/en/docs/advanced/templates.md 与配套示例持续对照学习。

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

项目优选

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