FastAPI

对比维度 FastAPI Flask Django
性能 高(异步支持) 较低(同步)
异步支持 ✅ 内置 async/await ❌ 需扩展 ❌ 不原生
数据验证 ✅ Pydantic 自动校验 ❌ 手动处理 ✅ ORM 级验证
自动文档 ✅ 自动生成 ❌ 需插件 ❌ 需扩展
适用场景 API、微服务、AI 推理 小型 Web 项目 大型网站

一、FastAPI基本使用

1. 创建项目

FastAPI → 存储位置及项目名称 → 创建虚拟环境 → Create

FastAPI框架虚拟环境运行项目有两种运行方式:

直接点击按钮运行命令行运行

1.1 点击按钮运行(默认热重载)

1.2 命令行 run 项目

uvicorn main:app --reload

这是 FastAPI / Starlette 框架最常用的启动命令,作用是启动一个基于 Uvicorn 的 ASGI 服务器,运行你的 Python Web 应用。

把命令拆成三部分:

1.2.1 uvicorn

  • 是什么:一个轻量、高性能的 Python ASGI 服务器

  • 作用:专门用来运行 FastAPI、Starlette 这类现代异步 Web 框架

  • 类比:相当于 Django/Flask 里的 runserver

1.2.2 main:app

  • 格式固定模块名:应用实例

  • main → 你的 Python 文件名 main.py

  • app → 你在代码里创建的 FastAPI 实例(通常写 app = FastAPI()

  • 所以 main:app 的意思是:去 main.py 文件里,找到名叫 app 的 FastAPI 应用并运行

1.2.3 --reload

  • 作用:开发模式自动重载

  • 效果:你修改代码后保存即生效,不用手动重启服务器(热重载)

  • 注意:只在开发时用,生产环境不要加

uvicorn main:app --reload

使用 Uvicorn 服务器,启动 main.py 里名为 app 的 FastAPI 应用,并开启代码自动重载功能。

接着修改代码 → 访问项目 → 访问路由 → 访问交互式文档

浏览器输入地址:http://127.0.0.1:8000/docs


2. 路由

路由可以帮助我们使用不同的路径访问到不同的结果。

路由就是 URL 地址和处理函数之间的映射关系,它决定了当用户访问某个特定网址时,服务器应该执行哪段代码来返回结果。

FastAPI 的路由定义基于 Python 的装饰器模式:

@app.get("/")
async def root():
    return {"message": "hello world"}
from fastapi import FastAPI
​
# 创建FastAPI实例
app = FastAPI()
​
​
@app.get("/")
async def root():
    return {"message": "Hello World 666"}
​
​
# @app.get("/hello/{name}")
# async def say_hello(name: str):
#     return {"message": f"Hello {name}"}
​
# 访问/hello响应结果 msg:你好 FastAPI
@app.get("/hello")
async def get_hello():
    return {"msg": "你好 FastAPI"}

然后再次访问接口文档,发现生成了 /hello 这个路径。


3. 参数

参数类型 位置 示例 作用 常用请求方法
路径参数 URL 路径的一部分 /book/{id} 指向唯一、特定的资源 GET
查询参数 URL 问号 ? 之后 k1=v1&k2=v2 对资源集合做过滤、排序、分页 GET
请求体 HTTP 消息体 (body) 内 JSON 格式数据 创建 / 更新资源,可携带大批量数据 POST、PUT
  • 路径参数:资源标识,用来精准定位单个资源,例:/book/12 获取 id=12 的书籍

  • 查询参数:筛选条件,例:/book?page=1&size=10 分页查书籍列表

  • 请求体 Body:批量传参,GET 规范上不能携带请求体,新增/修改接口优先用 POST/PUT 提交 JSON


3.1 路径参数

  • 位置:URL 路径的一部分,格式为 /资源/{参数名}(如 /book/{id}

  • 作用:指向唯一的、特定的资源(如获取某本具体书籍的信息)

  • 常用方法:GET(用于获取资源)

id: int — Python 原生限定 id 为 int 类型:

@app.get("/book/{id}") 
async def get_book(id: int): 
    return {"id": id, "title": f"这是第{id}本书"}
Path 函数

额外限制参数需求:可以导入 FastAPI 的 Path 函数来对请求参数进行限制:

@app.get("/book/{id}")
async def get_book(id: int = Path()):
    return {"id": id, "title": f"这是第{id}本书"}

限定范围 1 到 100 之间:

# 定义请求的的路径,请求方式为get
@app.get("/book/{id}")
# 定义这个接口,接收一个参数id,数据类型为int
async def get_book(id: int=Path(...,gt=0,lt=101)):
    # 返回响应结果
    return {"id": id, "title": f"这是第{id}本书"}
Path 参数 说明
... 必填
gt / ge 大于 / 大于等于
lt / le 小于 / 小于等于
description 描述
min_length / max_length 长度限制

添加描述(description):

# 定义请求的的路径,请求方式为get
@app.get("/book/{id}")
# 定义这个接口,接收一个参数id,数据类型为int
async def get_book(id: int=Path(...,gt=0,lt=101,description="书籍id,取值范围为1-100")):
    # 返回响应结果
    return {"id": id, "title": f"这是第{id}本书"}

长度限制:

# 需求:查找书籍的作者,路径参数 name,长度范围 2-10
@app.get("/book/{name}")
async def get_name(name: str = Path(..., min_length=2, max_length=10, description="书籍名称,长度范围为2-10")):
    return {"msg": f'这是{name}的信息'}

小结

  • 路径参数出现在什么位置?URL 路径的一部分

  • 如何为路径参数添加类型注解?Python 原生注解 和 Path 注解

练习

需求:定义两个接口,携带路径参数,并使用 Path 来实现类型注解,具体如下:

  • 接口1:以新闻分类 id 为参数设计 URL,id 范围为 1 ~ 100

  • 接口2:以新闻分类名称为参数设计 URL,分类名称长度为 2 ~ 10

from fastapi import FastAPI, Path
​
app = FastAPI()
​
# ---------------------------------------------------------
# 接口1:新闻分类 ID
# 需求:id 范围为 1 ~ 100
# URL 示例: /news/category/50
# ---------------------------------------------------------
@app.get("/news/category/{category_id}")
async def get_news_by_id(
    category_id: int = Path(
        ..., 
        title="新闻分类ID", 
        description="新闻分类的唯一标识符", 
        ge=1,   # 大于等于 1
        le=100  # 小于等于 100
    )
):
    return {"category_id": category_id, "message": "成功获取该分类下的新闻"}
​
​
# ---------------------------------------------------------
# 接口2:新闻分类名称
# 需求:分类名称长度为 2 ~ 10
# URL 示例: /news/search/科技
# ---------------------------------------------------------
@app.get("/news/search/{category_name}")
async def get_news_by_name(
    category_name: str = Path(
        ..., 
        title="新闻分类名称", 
        description="新闻分类的名称关键字", 
        min_length=2,  # 最小长度 2
        max_length=10  # 最大长度 10
    )
):
    return {"category_name": category_name, "message": "成功搜索该分类相关的新闻"}

3.2 查询参数

  • 位置:URL 中 ? 之后,格式为 key1=value1&key2=value2(如 ?page=1&sort=desc

  • 作用:对资源集合进行过滤、排序、分页等操作(如查询"第1页、按时间降序"的书籍列表)

  • 常用方法:GET(用于获取资源集合的筛选结果)

声明的参数不是路径参数时,路径操作函数会把该参数自动解释为查询参数。

news_list 后面拼接参数,如图:

# 需求:查询新闻页->分页,skip:跳过的记录数,limit:返回的记录数 10
@app.get("/news/news_list")
async def get_news_list(skip: int, limit: int = 10):  #limit设置默认值为10
    return {"skip": skip, "limit": limit}
Query 函数

导入 FastAPI 的 Query 函数:

# 需求:查询新闻页->分页,skip:跳过的记录数,limit:返回的记录数 10
@app.get("/news/news_list")
async def get_news_list(
    skip: int = Query(0, description="跳过的记录数",lt=100),#  0的位置是设置默认值为0,如果是...则是必填
    limit: int = Query(10, description="返回的记录数",lt=100)
):
    return {"skip": skip, "limit": limit}

小结

  • 查询参数出现在什么位置?URL ? 之后,k1=v1&k2=v2

  • 如何为查询参数添加类型注解?Python 原生注解 和 Query 注解

练习

需求:设计接口查询图书,要求携带两个查询参数:图书分类和价格

参数具体要求:

  • 图书分类:默认值为 Python开发,长度限制 5 ~ 255

  • 价格:限制大小范围 50 ~ 100

from fastapi import FastAPI, Query
​
app = FastAPI()
​
@app.get("/books/")
async def read_books(
    # 需求:图书分类,默认值为 "Python开发",长度限制 5 ~ 255
    category: str = Query(
        "Python开发", 
        min_length=5, 
        max_length=255,
        description="图书的分类名称"
    ),
    # 需求:价格,限制大小范围 50 ~ 100
    price: float = Query(
        ..., 
        ge=50, 
        le=100,
        description="图书的价格范围"
    )
):
    return {
        "category": category, 
        "price": price,
        "message": "查询成功"
    }

3.3 请求体参数

  • 位置:HTTP 请求的消息体(body)中,常以 JSON 等格式携带数据

  • 作用:用于创建、更新资源,或携带大量数据(如提交书籍的详细信息、更新用户资料)

  • 常用方法:POST(创建资源)、PUT(更新资源)等

在 HTTP 协议中,一个完整的请求由三部分组成:

  1. 请求行:包含方法、URL、协议版本

  2. 请求头:元数据信息(Content-Type、Authorization等)

  3. 请求体:实际要发送的数据内容

定义接收类型:

from pydantic import BaseModel
​
class User(BaseModel):
    username: str
    password: str

意思就是:定义一个叫 User 的数据模型,它继承 BaseModel 的所有能力。

BaseModelPydantic 库提供的一个"数据模板基类"

  1. 自动数据校验:传错类型会直接报错,比如给 username 传数字

  2. 自动类型提示:PyCharm / VSCode 会自动提示字段,不会写错名

  3. 自动转 JSON / 字典

    user.dict()  # 转字典
    user.json()  # 转 JSON 字符串
  4. FastAPI 专用请求:前端传 JSON,FastAPI 会自动解析并校验User 对象

添加类型注解:

@app.post("/register")
async def register(user: User):
    return user

响应结果如下:

需求:设计接口新增图书,图书信息包含:书名、作者、出版社、售价

from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
​
app = FastAPI()
​
# 1. 定义数据模型 (Pydantic Model)
class BookItem(BaseModel):
    title: str          # 书名
    author: str         # 作者
    publisher: str      # 出版社
    price: float        # 售价
​
# 2. 定义新增图书接口
@app.post("/books/", summary="新增图书")
async def create_book(book: BookItem):
    """
    接收 JSON 格式的图书信息并返回
    """
    # 这里通常会写数据库插入逻辑
    return {
        "message": "图书新增成功",
        "book_info": book
    }
Field 函数

类型注解 Field:导入 pydantic 的 Field 函数(上面的 Path 和 Query 函数都属于 FastAPI 的方法):

from pydantic import BaseModel, Field

class User(BaseModel):
    username: str = Field(...)
    password: str = Field(...)
# 注册:用户名和密码->str
class User(BaseModel):
    username: str = Field(default="张三", min_length=2, max_length=10, description="用户名,长度范围为2-10")
    password: str = Field(min_length=6, max_length=20, description="密码,长度范围为6-20")

@app.post("/register")
async def register(user: User):
    return user

小结

  • 请求体参数的作用是什么?创建、更新资源

  • 如何定义、使用请求体参数?

# 注册:用户名和密码->str
class User(BaseModel):
    username: str = Field(default="张三", min_length=2, max_length=10, description="用户名,长度范围为2-10")
    password: str = Field(min_length=6, max_length=20, description="密码,长度范围为6-20")
​
@app.post("/register")
async def register(user: User):
    return user
  • 如何为请求体参数添加类型注解?Python 原生注解 和 Field 注解

练习

需求:设计接口新增图书,图书信息包含:书名、作者、出版社、售价

具体要求如下:

  • 书名:不能为空;长度 2 ~ 20

  • 作者:长度 2 ~ 10

  • 出版社:默认值"黑马出版社"

  • 售价:不能为空;价格大于0元

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional
​
app = FastAPI()
​
# 1. 定义数据模型
class Book(BaseModel):
    # 书名:不能为空;长度 2 ~ 20
    book_name: str = Field(
        ..., 
        min_length=2, 
        max_length=20, 
        description="书名,必填,2-20字"
    )
    # 作者:长度 2 ~ 10
    author: str = Field(
        ..., 
        min_length=2, 
        max_length=10, 
        description="作者,必填,2-10字"
    )
    # 出版社:默认值"黑马出版社"
    publisher: str = Field(
        default="黑马出版社", 
        description="出版社,默认黑马出版社"
    )
    # 售价:不能为空;价格大于0元
    price: float = Field(
        ..., 
        gt=0, 
        description="价格,必填,大于0"
    )
​
# 2. 定义新增图书接口
@app.post("/books", summary="新增图书")
async def create_book(book: Book):
    """
    接收图书信息,校验通过后返回
    """
    # 这里可以添加保存到数据库的逻辑
    return {"message": "图书新增成功", "book_data": book}

4. 响应类型

默认情况下,FastAPI 会自动将路径操作函数返回的 Python 对象(字典、列表、Pydantic 模型等),经由 jsonable_encoder 转换为 JSON 兼容格式,并包装为 JSONResponse 返回。这省去了手动序列化的步骤,让开发者能更专注于业务逻辑。

如果需要返回非 JSON 数据(如 HTML、文件流),FastAPI 提供了丰富的响应类型来返回不同数据。

响应类型 用途 示例
JSONResponse 默认响应,返回 JSON 数据 return {"key": "value"}
HTMLResponse 返回 HTML 内容 return HTMLResponse(html_content)
PlainTextResponse 返回纯文本 return PlainTextResponse("text")
FileResponse 返回文件下载 return FileResponse(path)
StreamingResponse 流式响应 生成器函数返回数据
RedirectResponse 重定向 return RedirectResponse(url)

FastAPI 响应类型设置方式:

设置方式 说明 适用场景
装饰器中指定响应类 @app.get() 等装饰器中,通过 response_class 参数声明 固定返回类型(如 HTML、纯文本)
返回响应对象 在接口函数中直接 return 一个响应实例 文件下载、图片、流式响应

4.1 装饰器指定响应类(response_class

这种方式会让 FastAPI 自动将返回的数据包装成指定类型的响应,适合固定类型的接口。

from fastapi import FastAPI
from fastapi.responses import HTMLResponse, PlainTextResponse
​
app = FastAPI()
​
# 方式1:装饰器中指定HTML响应
@app.get("/html", response_class=HTMLResponse)
async def get_html():
    return "<h1>Hello, HTML!</h1>"
​
# 方式1:装饰器中指定纯文本响应
@app.get("/text", response_class=PlainTextResponse)
async def get_text():
    return "Hello, Plain Text!"

4.2 直接返回响应对象

这种方式更灵活,可以直接控制响应的细节,适合文件、流等特殊场景。

FileResponse 是 FastAPI 提供的专门用于高效返回文件内容(如图片、PDF、Excel、音视频等)的响应类。它能够智能处理文件路径、媒体类型推断、范围请求和缓存头部,是服务静态文件的推荐方式。

from fastapi import FastAPI
from fastapi.responses import FileResponse, StreamingResponse
import io
​
app = FastAPI()
​
# 方式2:直接返回文件响应
@app.get("/download")
async def download_file():
    return FileResponse("example.txt", filename="downloaded.txt")
​
# 方式2:直接返回流式响应
@app.get("/stream")
async def stream_data():
    def generator():
        yield b"Hello, "
        yield b"Streaming!"
    return StreamingResponse(generator(), media_type="text/plain")

装饰器指定(response_class):

  • 优点:代码更简洁,自动序列化,文档自动识别

  • 缺点:灵活性较低,无法精细控制响应头、文件路径等细节

直接返回响应对象:

  • 优点:高度灵活,可直接设置响应头、状态码、媒体类型等

  • 缺点:需要手动处理部分序列化逻辑,对文档自动识别的支持较弱

4.3 自定义响应数据格式

response_model 是路径操作装饰器(如 @app.get@app.post)的关键参数,它通过一个 Pydantic 模型来严格定义 API 端点的输出格式。这一机制在提供自动数据验证和序列化的同时,更是保障数据安全性的第一道防线。

response_model 的核心功能包括:

  • 强制输出格式:不管接口函数返回什么数据,最终给前端的响应都会被强制转换成 response_model 指定的结构

  • 自动数据验证:确保返回的数据完全符合 Pydantic 模型的类型、字段和校验规则

  • 自动序列化:自动处理对象转 JSON,同时过滤掉模型中未定义的额外字段,防止敏感信息泄露

from pydantic import BaseModel
from fastapi import FastAPI
​
app = FastAPI()
​
# 需求: 新闻接口 → 响应数据格式 id、title、content
class News(BaseModel):
    id: int
    title: str
    content: str
​
@app.get(path="/news/{id}", response_model=News)
async def get_news(id: int):
    return {
        "id": id,
        "title": f"这是第{id}本书",
        "content": "这是一本好书"
    }

如果响应时缺失字段:

@app.get("/news/{id}", response_model=News)
async def get_news(id: int):
    return {
        "id": id,
        "title": f"这是第{id}本书"
    }

FastAPI 的 response_model 在校验时发现必填字段缺失,直接抛出 ResponseValidationError,前端会收到 500 Internal Server Error

解决方法:

方法1:把模型字段设为可选(允许缺失)

修改 News 模型,给 content 字段加上默认值,让它变成非必填项:

from typing import Optional
​
class News(BaseModel):
    id: int
    title: str
    content: Optional[str] = None  # 允许缺失,默认返回 null

方法2:缺失字段不返回(用 response_model_exclude_unset

在装饰器中加上 response_model_exclude_unset=True,接口没返回的字段,最终响应里就不会出现:

@app.get(
    "/news/{id}", 
    response_model=News,
    response_model_exclude_unset=True  # 关键配置
)
async def get_news(id: int):
    return {"id": id, "title": f"这是第{id}本书"}

此时前端收到的响应是:

{"id": 66, "title": "这是第66本书"}

不会出现 content 字段,也不会报错。


5. 异常处理

对于客户端引发的错误(4xx,如资源未找到、认证失败),应使用 fastapi.HTTPException 来中断正常处理流程,并返回标准错误响应。

# 需求: 按 id 查询新闻 → 1 - 6
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/news/{id}")
async def get_news(id: int):
    id_list = [1, 2, 3, 4, 5, 6]
    if id not in id_list:
        raise HTTPException(status_code=404, detail="您查找的新闻不存在")
    return {"id": id}

HTTPException 的作用

这是 FastAPI 提供的标准异常类,专门用来抛出 HTTP 错误:

  • 接收 status_code:设置响应状态码(如 404)

  • 接收 detail:设置错误详情信息,会自动返回给前端

  • 抛出后会中断接口后续流程,直接返回标准错误响应

运行效果

  • id[1-6] 内时:正常返回 {"id": id}

  • id 不在 [1-6] 内时:抛出 404 错误,前端收到:

{
    "detail": "您查找的新闻不存在"
}

6. 中间件

中间件(Middleware)是一个在每次请求进入 FastAPI 应用时都会被执行的函数。

它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次。

中间件的执行顺序是按代码自下而上开始执行(类似 Java 的 AOP)。

  • 作用是什么? 为每个请求添加统一的处理逻辑(记录日志、身份认证、跨域、设置响应头、性能监控等)

中间件:函数的顶部使用装饰器 @app.middleware("http")

@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件1 start")
    response = await call_next(request)
    print("中间件1 end")
    return response
from fastapi import FastAPI
​
app = FastAPI()
​
@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件1 start")
    response = await call_next(request)
    print("中间件1 end")
    return response
​
@app.middleware("http")
async def middleware2(request, call_next):
    print("中间件2 start")
    response = await call_next(request)
    print("中间件2 end")
    return response
​
@app.get("/")
async def root():
    return {"message": "Hello World"}

由以上程序运行的结果可以看出,中间件开始是自下而上的,而结束是自上而下。

中间件2 start
  中间件1 start
    接口运行
  中间件1 end
中间件2 end

7. 依赖注入

依赖项:可重用的组件(函数/类),负责提供某种功能或数据。

注入:FastAPI 自动帮你调用依赖项,并将结果"注入"到路径操作函数中。

优点:

  • 代码复用:一次编写,多处使用

  • 解耦:业务逻辑与基础设施代码分离

  • 易于测试:轻松地用模拟依赖替换真实依赖进行测试

步骤:创建依赖项 → 导入 Depends → 声明依赖项

from fastapi import FastAPI, Query
from fastapi.params import Depends
​
app = FastAPI()
​
@app.get("/")
async def root():
    return {"message": "Hello World"}
​
# 分页参数逻辑共用:新闻列表和用户列表
# 依赖项
async def common_parameters(
    skip: int = Query(0, ge=0),    # ✅ 自动获取查询参数
    limit: int = Query(10, le=60)
):
    return {"skip": skip, "limit": limit}
​
# 声明依赖项->依赖注入
@app.get("/news/news_list")
async def news_list(commons = Depends(common_parameters)):
    return commons
​
@app.get("/user/user_list")
async def user_list(commons = Depends(common_parameters)):
    return commons

依赖注入 Depends 的好处(Depends ≈ Spring 的 @Bean + @Autowired):

  • 代码复用:不用重复写参数

  • 统一校验:Query 校验只写一次

  • 结构清晰:业务逻辑和公共逻辑分离

  • 可替换、可测试

普通公共函数 = 自己手动调用(拿不到请求、Query、Header、Cookie;不能做自动参数校验;不能做依赖嵌套;不能自动注入数据库、token 等资源)

Depends = FastAPI 自动帮你调用 + 自动传请求参数 + 自动校验

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐