Python FastAPI + Jinja 模板完全教程:从入门到实战

6 阅读6分钟

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 %}

七、注意事项与最佳实践

  1. 模板目录规范:Jinja2Templates 默认读取 templates 目录,无需修改路径,子模板可放在子文件夹中。
  1. 静态文件路径:使用 url_for('static', path='/xxx') 动态生成静态文件路径,避免硬编码。
  1. 自动转义:Jinja 默认自动转义 HTML 特殊字符,防止 XSS 攻击,如需渲染原始 HTML,用 {{ content | safe }}。
  1. FastAPI 适用场景:FastAPI 更适合 API 开发,若需构建复杂 SSR 网站,可考虑 Django;若为前后端分离项目,仅用 FastAPI 提供接口即可。
  1. 性能优化:生产环境关闭 --reload,可使用模板缓存提升性能。

八、常见问题排查

  1. 模板找不到:检查模板目录是否命名为 templates,文件路径是否正确。
  1. 静态文件加载失败:确认 app.mount 配置正确,路径中是否遗漏 static 前缀。
  1. 变量未渲染:检查上下文字典中是否传递了该变量,变量名是否拼写一致。
  1. 语法错误:Jinja 语法严格,确保 {% %} {{ }} 成对闭合,循环、条件语句有结束标签。