在很多人眼里,FastAPI 天生就是为了写前后端分离的 JSON 接口而生的。但凡事总有例外,在构建管理后台、博客系统或者轻量级工具时,比起折腾复杂的前端工程化框架,直接在服务端把 HTML 页面拼装好扔给浏览器,往往更简单、更痛快。
FastAPI 与 Jinja2 的结合,正是为了解决这一诉求。它既保留了 Python 异步框架的风驰电掣,又继承了经典的服务端渲染体验 。
什么是模板渲染,不妨把它看作填字游戏
如果把服务端渲染想象成一个具体的场景,它非常像小学生写请假条的填空模版。
你在模版里留好空白,比如 ___同学因为___原因需要请假___天。当有学生发起申请时,服务端程序把具体的名字、原因和天数填进空白处,整合成一张完整的请假条,再交给班主任。
在 Web 世界里,Jinja2 就是那个负责填空的模板引擎,HTML 文件是模版,FastAPI 则是那个接收请求、查询数据库并把具体数据喂给模板的指挥官 。
整个工作流清晰明了,浏览器发出请求,FastAPI 拿到参数后组装成一个数据字典,Jinja2 负责把数据嵌入到 HTML 中,最后把纯文本的 HTML 网页原样送回浏览器解析呈现 。
搭建基底,从目录结构与环境准备开始
要让这套体系转起来,第一步是准备好渲染引擎。FastAPI 并没有将 Jinja2 硬编码进核心库,而是借助其底层的 Starlette 框架进行集成,因此需要手动安装依赖包 。
在终端执行环境安装,
bash
pip install jinja2
接下来是推荐的标准工程目录结构。将页面模版与静态资源(如 CSS 样式表、JavaScript 脚本和图片)分门别类放置,
text
my_project/
├── main.py
├── static/
│ └── styles.css
└── templates/
└── item.html
核心运作代码剖析
服务端渲染的代码非常紧凑,主要分为配置模板引擎、挂载静态资源、定义路由三个关键动作。
在 main.py 中,编写如下代码,
python
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):
# 业务数据
item_data = {"id": id, "name": "机械键盘", "price": 499}
# 渲染并返回 HTML 页面
return templates.TemplateResponse(
request=request,
name="item.html",
context={"item": item_data}
)
这段代码藏着两个极为关键的细节。
路由函数的参数列表中必须明确声明 request: Request。很多人初学时容易忽略它,但在模板体系中,Jinja2 需要通过 request 对象来识别当前的请求上下文,包括提取 Cookies、请求头以及后续生成静态资源的绝对链接 。
在较新的 FastAPI 与 Starlette 版本中,templates.TemplateResponse 推荐将 request 作为显式参数传入,也可以将其放置在 context 字典中,但显式传递 request=request 更加直观且符合现代类型推导标准。
编写 Jinja2 模板与静态资源配合
来看对应的 templates/item.html 模板文件。Jinja2 允许开发者使用双大括号 {{ }} 输出变量,使用 {% %} 编写循环和判断逻辑。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>商品详情 - {{ item.name }}</title>
<!-- 使用 url_for 动态反向解析静态文件路径 -->
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet">
</head>
<body>
<div class="card">
<h1>商品 ID, {{ item.id }}</h1>
<p>名称, <strong>{{ item.name }}</strong></p>
<p>价格, ¥{{ item.price }}</p>
{% if item.price > 300 %}
<span class="badge">包邮商品</span>
{% else %}
<span>运费 10 元</span>
{% endif %}
</div>
</body>
</html>
这里的亮点在于 {{ url_for('static', path='/styles.css') }}。它并不是简单地写死相对路径,而是利用 FastAPI 注册的路由名称 static 动态生成正确的资源 URL。这意味着即使未来将静态资源迁移到子路径或者特定反向代理下,整个系统的链接结构也不会轻易失效 。
服务端渲染的适用边界
把 FastAPI 和 Jinja2 绑在一起用,并不是要开历史倒车去全盘否定前端现代化单页应用(SPA)。
当你的项目主要用于内部数据看板、简易管理系统、SEO 要求极高且交互简单的内容展示站时,这套方案省去了 Vue 或 React 的打包配置、跨域设置以及接口联调成本。用最地道的 Python 代码,搭配极其直观的 HTML 模板,几十分钟就能搭建出稳定健壮的 Web 服务。
参考资料
FastAPI 官方文档 - 模板使用指南, fastapi.tiangolo.com/zh/advanced...