FastAPI + Jinja 模板完全教程:从入门到实战
Jinja 是 Python 生态中强大的模板引擎,能轻松将 FastAPI 接口数据渲染为 HTML 等文本格式,本教程在参考原文基础上,补充实战细节与进阶用法,帮你快速掌握完整流程。
一、核心概念:Jinja 与 FastAPI 的结合
Jinja 是专门用于渲染文本模板的工具,支持变量替换、逻辑控制等功能,可生成 HTML、XML 等格式文件。FastAPI 虽主打 API 开发,但通过集成 Jinja 能快速实现服务器端渲染(SSR),适用于简单网页、登录页、数据展示页等场景。
两者结合的核心价值:
- 无需前端框架,直接通过模板渲染动态数据
- 语法简洁,Python 开发者可快速上手
- 支持静态文件(CSS/JS)关联,实现完整网页效果
二、环境准备与安装
1. 基础依赖
确保已安装 Python 3.7+,推荐使用虚拟环境隔离依赖:
# 创建虚拟环境
python -m venv fastapi-jinja-env
# 激活环境(Windows)
fastapi-jinja-env\Scripts\activate
# 激活环境(Mac/Linux)
source fastapi-jinja-env/bin/activate
2. 安装必要包
需安装 FastAPI、Jinja2 及服务器 uvicorn:
# 方式1:pip 安装
pip install fastapi jinja2 uvicorn
# 方式2:pipenv 安装(原文方式)
pipenv install fastapi jinja2 uvicorn
三、项目结构设计
规范的目录结构能提升可维护性,推荐如下布局:
fastapi-jinja-demo/
├── main.py # 项目入口(FastAPI 核心逻辑)
├── templates/ # 模板文件目录(必须命名为 templates)
│ ├── index.html # 首页模板
│ └── users/ # 子模块模板(可选,用于分类)
│ └── list.html
└── static/ # 静态文件目录
├── css/
│ └── styles.css
└── js/
└── main.js
四、快速入门:渲染第一个模板
1. 编写模板文件
在 templates/index.html 中写入基础 HTML,嵌入 Jinja 变量:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>FastAPI + Jinja 入门</title>
<!-- 引入静态 CSS 文件 -->
<link href="{{ url_for('static', path='/css/styles.css') }}" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>欢迎,{{ username }}!</h1>
<p>当前时间:{{ current_time }}</p>
</div>
</body>
</html>
2. 编写 FastAPI 核心逻辑(main.py)
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from datetime import datetime
# 初始化 FastAPI 应用
app = FastAPI(title="Jinja 模板演示")
# 挂载静态文件目录(让模板能访问 CSS/JS)
app.mount("/static", StaticFiles(directory="static"), name="static")
# 初始化 Jinja 模板引擎,指定模板目录
templates = Jinja2Templates(directory="templates")
# 定义路由,渲染模板
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
# 传递给模板的上下文数据(键值对形式)
context = {
"request": request, # 必须传递 request 对象(FastAPI 要求)
"username": "FastAPI 学习者",
"current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# 渲染模板并返回
return templates.TemplateResponse("index.html", context)
3. 编写静态 CSS(static/css/styles.css)
.container {
width: 80%;
margin: 50px auto;
text-align: center;
}
h1 {
color: #2c3e50;
}
p {
color: #7f8c8d;
font-size: 18px;
}
4. 运行与访问
启动 FastAPI 服务:
uvicorn main:app --reload --port 8000
- --reload:开发模式,修改代码自动重启
- --port 8000:指定端口为 8000
访问 http://127.0.0.1:8000,即可看到渲染后的网页,包含动态传递的用户名和当前时间。
五、Jinja 核心语法详解
1. 变量与属性访问
模板变量由上下文字典传递,支持多种访问方式:
- 直接访问简单变量:{{ username }}
- 访问对象属性:{{ user.name }}(等价于 {{ user['name'] }})
- 访问列表元素:{{ fruits[0] }}
- 支持 Python 内置类型:字符串、数字、列表、字典、对象等
2. 过滤器(Filters)
过滤器用于修改变量值,通过 | 连接,支持链式调用:
- 常用过滤器示例:
<!-- 默认值:变量未定义时显示默认内容 -->
{{ nickname | default("匿名用户") }}
<!-- 字符串处理:去除标签并首字母大写 -->
{{ content | striptags | title }}
<!-- 类型转换:数字转字符串、浮点数转整数 -->
{{ 123 | string }}
{{ 3.14 | int }}
<!-- 列表处理:拼接、求长度 -->
{{ ["苹果", "香蕉", "橙子"] | join("、") }} <!-- 输出:苹果、香蕉、橙子 -->
{{ fruits | length }} <!-- 输出列表长度 -->
<!-- 日期格式化 -->
{{ create_time | date("%Y年%m月%d日") }}
3. 条件判断(if 语句)
用 {% if %} {% elif %} {% else %} {% endif %} 实现逻辑判断:
{% if user.is_vip %}
<div class="vip-tag">VIP 用户</div>
{% elif user.score > 100 %}
<div class="high-score">高级用户</div>
{% else %}
<div class="normal-user">普通用户</div>
{% endif %}
4. 循环(for 语句)
用 {% for %} {% endfor %} 迭代列表、字典等可迭代对象:
<h3>水果列表</h3>
<ul>
{% for fruit in fruits %}
<!-- loop 变量:获取循环信息 -->
<li>
{{ loop.index }}. {{ fruit.name }} - 价格:{{ fruit.price }}元
{% if loop.first %}(首项){% endif %}
{% if loop.last %}(末项){% endif %}
</li>
{% empty %}
<!-- 列表为空时显示 -->
<li>暂无水果数据</li>
{% endfor %}
</ul>
5. 宏(Macro)
宏类似函数,用于复用代码片段,支持参数传递:
<!-- 定义宏:创建表单输入框 -->
{% macro input(name, value="", type="text", placeholder="") %}
<input type="{{ type }}" name="{{ name }}" value="{{ value | escape }}"
placeholder="{{ placeholder }}" class="form-input">
{% endmacro %}
<!-- 调用宏 -->
{{ input("username", placeholder="请输入用户名") }}
{{ input("password", type="password", placeholder="请输入密码") }}
{{ input("submit", type="submit", value="登录", class="btn") }}
6. 模板继承
通过继承实现模板复用,核心是 {% extends %} 和 {% block %}:
- 父模板(templates/base.html) :
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
<link href="{{ url_for('static', path='/css/styles.css') }}" rel="stylesheet">
</head>
<body>
<header class="header">网站头部</header>
<main class="content">
{% block content %}{% endblock %} <!-- 子模板填充内容 -->
</main>
<footer class="footer">网站底部</footer>
</body>
</html>
- 子模板(templates/users/list.html) :
{% extends "base.html" %} <!-- 继承父模板 -->
{% block title %}用户列表{% endblock %} <!-- 重写标题块 -->
{% block content %} <!-- 填充内容块 -->
<h2>用户列表</h2>
<table>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
</tr>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
7. 注释
Jinja 注释不会在网页源码中显示,格式为:
{# 这是模板注释,前端看不到 #}
<!-- 这是 HTML 注释,前端能看到 -->
六、实战进阶:动态数据渲染
1. 传递复杂数据(模型 / 列表)
定义 Pydantic 模型,传递列表数据到模板:
# main.py 中补充
from pydantic import BaseModel
from typing import List
# 定义用户模型
class User(BaseModel):
id: int
username: str
email: str
is_vip: bool
# 模拟用户数据
fake_users: List[User] = [
User(id=1, username="张三", email="zhangsan@example.com", is_vip=True),
User(id=2, username="李四", email="lisi@example.com", is_vip=False),
User(id=3, username="王五", email="wangwu@example.com", is_vip=False),
]
# 渲染用户列表页
@app.get("/users", response_class=HTMLResponse)
async def user_list(request: Request):
return templates.TemplateResponse(
"users/list.html",
{"request": request, "users": fake_users}
)
2. 结合路径参数与查询参数
# main.py 中补充
@app.get("/user/{user_id}", response_class=HTMLResponse)
async def user_detail(request: Request, user_id: int, show_email: bool = False):
# 根据 user_id 查询用户(此处用模拟数据)
user = next((u for u in fake_users if u.id == user_id), None)
return templates.TemplateResponse(
"users/detail.html",
{"request": request, "user": user, "show_email": show_email}
)
对应的模板(templates/users/detail.html):
{% extends "base.html" %}
{% block title %}{{ user.username }} 的详情{% endblock %}
{% block content %}
{% if user %}
<div class="user-detail">
<h2>{{ user.username }}</h2>
<p>ID:{{ user.id }}</p>
{% if show_email %}
<p>邮箱:{{ user.email }}</p>
{% else %}
<p>邮箱:<a href="?show_email=true">点击显示</a></p>
{% endif %}
{% if user.is_vip %}
<span class="vip-tag">VIP 标识</span>
{% endif %}
</div>
{% else %}
<p>用户不存在</p>
{% endif %}
{% endblock %}
七、注意事项与最佳实践
- 模板目录规范:Jinja2Templates 默认读取 templates 目录,无需修改路径,子模板可放在子文件夹中。
- 静态文件路径:使用 url_for('static', path='/xxx') 动态生成静态文件路径,避免硬编码。
- 自动转义:Jinja 默认自动转义 HTML 特殊字符,防止 XSS 攻击,如需渲染原始 HTML,用 {{ content | safe }}。
- FastAPI 适用场景:FastAPI 更适合 API 开发,若需构建复杂 SSR 网站,可考虑 Django;若为前后端分离项目,仅用 FastAPI 提供接口即可。
- 性能优化:生产环境关闭 --reload,可使用模板缓存提升性能。
八、常见问题排查
- 模板找不到:检查模板目录是否命名为 templates,文件路径是否正确。
- 静态文件加载失败:确认 app.mount 配置正确,路径中是否遗漏 static 前缀。
- 变量未渲染:检查上下文字典中是否传递了该变量,变量名是否拼写一致。
- 语法错误:Jinja 语法严格,确保 {% %} {{ }} 成对闭合,循环、条件语句有结束标签。