用 FastAPI 从 0 到 1 写一个真正可用的接口服务

在这里插入图片描述

上一篇里,我把 FastAPIFlaskStreamlit 的定位做了一次整体梳理。

如果说第 1 篇解决的是“到底该怎么选”,那这一篇就开始真正落地:如果你已经确定自己要做接口服务,FastAPI 应该怎么入门,才能尽快写出一个像样的项目?

很多人第一次接触 FastAPI,会觉得它“看起来很简单”。

确实,写一个最基础的接口并不难;但从“能跑”到“可用”,中间其实差着几个关键台阶:

  • 请求参数能不能自动校验;
  • 返回结构是不是清晰稳定;
  • 文档能不能自动生成;
  • 别人拿到你的接口后,能不能直接联调;
  • 代码后面还能不能继续扩展。

这篇文章我不准备只写一个 Hello World,而是带你从 0 到 1 写一个真正可用的图书接口服务,把 FastAPI 最核心的开发体验一次走通。

一、为什么很多人第一次做 API,会喜欢 FastAPI

如果你做的是前后端分离项目、移动端后端、小程序接口,或者想把某段 Python 能力封装成服务,你最常见的需求通常是:

  • 接口参数要明确;
  • 非法输入要被拦住;
  • 返回结构要统一;
  • 文档最好自动生成;
  • 调试尽量不要太折腾。

FastAPI 在这些点上给出的体验,确实非常顺手。

它的核心优势不只是“性能不错”,而是:

  • 用 Python 类型注解描述参数和数据结构;
  • Pydantic 自动完成校验和序列化;
  • 自动生成 Swagger 文档和 ReDoc 文档;
  • 写法直观,适合把接口边界写清楚。

换句话说,FastAPI 很适合拿来做一件事:

把“接口应该是什么样”这件事,从口头约定,变成代码里的明确规则。

二、先搭一个最小可运行环境

先装依赖。

pip install fastapi "uvicorn[standard]"

如果你准备新建一个项目,目录可以先简单一点:

book-api/
├── main.py
└── requirements.txt

其中 main.py 是入口文件,先写一个最小版本:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "Book API is running"}

启动命令:

uvicorn main:app --reload

启动后访问:

  • http://127.0.0.1:8000/
  • http://127.0.0.1:8000/docs
  • http://127.0.0.1:8000/redoc

很多人第一次打开 /docs 时会眼前一亮,因为这意味着你不需要手写一份接口文档,再让前端照着猜参数。只要接口和数据模型写清楚,文档就能自动跟上。

三、一个真正可用的接口服务,至少要具备什么

如果只是为了演示,写几个路由函数就够了。

但如果想让这个接口服务更像一个“能给别人用”的项目,至少要有下面这些东西:

  • 明确的请求模型;
  • 明确的响应模型;
  • 合理的状态码;
  • 常见错误的处理方式;
  • 可调试、可阅读的自动文档。

所以接下来,我们用一个很小的图书管理 API 把这些点串起来。

目标很简单:

  • 查询图书列表;
  • 按 ID 查询图书;
  • 新增图书;
  • 更新图书;
  • 删除图书。

为了让示例容易理解,这里先不用数据库,直接用内存列表模拟数据。这样你可以把注意力集中在 FastAPI 的核心机制上。

四、先定义数据模型:这是 FastAPI 最关键的一步

很多人写接口时,一上来先写路由;但在 FastAPI 里,先把数据模型定义好,通常更顺

来看一版完整示例:

from typing import Literal

from fastapi import FastAPI, HTTPException, Path, Query, status
from pydantic import BaseModel, Field

app = FastAPI(
    title="Book API",
    version="1.0.0",
    description="一个用于演示 FastAPI 核心功能的图书接口服务",
)


class BookCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=100, description="书名")
    author: str = Field(..., min_length=1, max_length=50, description="作者")
    price: float = Field(..., gt=0, description="价格")
    category: Literal["Python", "Web", "AI", "Database"]


class Book(BookCreate):
    id: int


books: list[Book] = [
    Book(id=1, title="Fluent Python", author="Luciano Ramalho", price=88.0, category="Python"),
    Book(id=2, title="FastAPI 实战", author="张三", price=59.9, category="Web"),
]

这段代码的价值非常大,因为它已经把接口规则写出来了:

  • title 不能为空;
  • author 不能为空;
  • price 必须大于 0;
  • category 只能是几个固定值之一。

也就是说,数据是否合法,不再靠你在函数里写一堆 if 去手动判断,而是直接交给模型声明来处理。

这就是 FastAPI 很舒服的一点:接口边界写得越清楚,后面越省事。

五、开始写接口:先做查询,再做写入

1. 查询图书列表

@app.get("/books", response_model=list[Book], summary="获取图书列表")
def list_books(category: str | None = Query(default=None, description="按分类筛选")):
    if category is None:
        return books
    return [book for book in books if book.category == category]

这里有两个很值得注意的点:

  • Query(...) 用来声明查询参数;
  • response_model=list[Book] 用来约束返回结构。

response_model 很重要。它不仅影响文档展示,还能帮你把输出结构控制得更稳定,避免接口返回越来越随意。

2. 按 ID 查询单本图书

@app.get("/books/{book_id}", response_model=Book, summary="根据 ID 获取图书")
def get_book(book_id: int = Path(..., gt=0, description="图书 ID")):
    for book in books:
        if book.id == book_id:
            return book
    raise HTTPException(status_code=404, detail="Book not found")

这里的重点是 Path(...)HTTPException

  • Path(...) 让路径参数也能直接做校验;
  • HTTPException 用来返回明确的错误信息和状态码。

这样当前端传了非法 ID,或者请求了不存在的数据时,接口行为会更清晰。

3. 新增图书

@app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED, summary="新增图书")
def create_book(payload: BookCreate):
    new_id = max(book.id for book in books) + 1 if books else 1
    book = Book(id=new_id, **payload.model_dump())
    books.append(book)
    return book

这里的关键点是:

  • 请求体直接使用 BookCreate
  • 返回结构使用 Book
  • 状态码明确设为 201 Created

这其实就是一个很典型的“请求模型和响应模型分离”思路:

  • 创建时不需要前端传 id
  • 返回时又需要把 id 带回去。

如果你以后接数据库、做审计字段、做用户信息隔离,这种分层会非常有用。

4. 更新图书

@app.put("/books/{book_id}", response_model=Book, summary="更新图书")
def update_book(book_id: int, payload: BookCreate):
    for index, book in enumerate(books):
        if book.id == book_id:
            updated_book = Book(id=book_id, **payload.model_dump())
            books[index] = updated_book
            return updated_book
    raise HTTPException(status_code=404, detail="Book not found")

5. 删除图书

@app.delete("/books/{book_id}", summary="删除图书")
def delete_book(book_id: int):
    for index, book in enumerate(books):
        if book.id == book_id:
            del books[index]
            return {"message": "Book deleted successfully"}
    raise HTTPException(status_code=404, detail="Book not found")

到这里,一个最基本但已经可用的 CRUD 接口服务就齐了。

六、把完整代码放在一起,你就能直接跑

为了方便你复制和验证,我把完整示例合在一起:

from typing import Literal

from fastapi import FastAPI, HTTPException, Path, Query, status
from pydantic import BaseModel, Field

app = FastAPI(
    title="Book API",
    version="1.0.0",
    description="一个用于演示 FastAPI 核心功能的图书接口服务",
)


class BookCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=100, description="书名")
    author: str = Field(..., min_length=1, max_length=50, description="作者")
    price: float = Field(..., gt=0, description="价格")
    category: Literal["Python", "Web", "AI", "Database"]


class Book(BookCreate):
    id: int


books: list[Book] = [
    Book(id=1, title="Fluent Python", author="Luciano Ramalho", price=88.0, category="Python"),
    Book(id=2, title="FastAPI 实战", author="张三", price=59.9, category="Web"),
]


@app.get("/")
def root():
    return {"message": "Book API is running"}


@app.get("/books", response_model=list[Book], summary="获取图书列表")
def list_books(category: str | None = Query(default=None, description="按分类筛选")):
    if category is None:
        return books
    return [book for book in books if book.category == category]


@app.get("/books/{book_id}", response_model=Book, summary="根据 ID 获取图书")
def get_book(book_id: int = Path(..., gt=0, description="图书 ID")):
    for book in books:
        if book.id == book_id:
            return book
    raise HTTPException(status_code=404, detail="Book not found")


@app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED, summary="新增图书")
def create_book(payload: BookCreate):
    new_id = max(book.id for book in books) + 1 if books else 1
    book = Book(id=new_id, **payload.model_dump())
    books.append(book)
    return book


@app.put("/books/{book_id}", response_model=Book, summary="更新图书")
def update_book(book_id: int, payload: BookCreate):
    for index, book in enumerate(books):
        if book.id == book_id:
            updated_book = Book(id=book_id, **payload.model_dump())
            books[index] = updated_book
            return updated_book
    raise HTTPException(status_code=404, detail="Book not found")


@app.delete("/books/{book_id}", summary="删除图书")
def delete_book(book_id: int):
    for index, book in enumerate(books):
        if book.id == book_id:
            del books[index]
            return {"message": "Book deleted successfully"}
    raise HTTPException(status_code=404, detail="Book not found")

保存成 main.py 之后,执行:

uvicorn main:app --reload

然后你就可以直接打开 /docs 来测试所有接口。

七、为什么说自动文档是 FastAPI 的一个大优势

很多教程提到 FastAPI 的自动文档时,往往一笔带过。但在真实开发里,这个能力其实非常实用。

因为它解决的不是“看起来高级”,而是几个特别现实的问题:

  • 你不用再手动维护一份容易过期的接口说明;
  • 前端可以直接看参数、类型、示例和返回结构;
  • 测试时可以直接在页面里调接口;
  • 团队协作时,沟通成本会低很多。

对于个人项目来说,自动文档的价值是让你省时间;
对于团队项目来说,它的价值是减少误解。

很多人第一次用 FastAPI,真正留下好印象的点,不是语法本身,而是这种“接口和文档自然同步”的开发体验。

八、初学者最容易忽略的几个点

1. 只写路由,不写数据模型

这是最常见的问题。

如果你只是把请求体当成一个普通字典去收,短期看起来更快,但很快你就会发现:

  • 参数是否合法没人兜底;
  • 文档信息不完整;
  • 代码越写越散;
  • 联调时容易出现边界不一致。

所以在 FastAPI 里,模型不是附属品,而是接口设计本身的一部分。

2. 不区分请求模型和响应模型

很多初学者会让前端传什么,就原样返回什么。

这在简单演示里没问题,但到了真实项目里,通常会有这些需求:

  • 创建时不传 id
  • 返回时要补 id
  • 某些内部字段不能直接暴露;
  • 不同接口的返回粒度不一样。

所以请求和响应分开定义,是一个非常值得尽早养成的习惯。

3. 以为 FastAPI 的价值只是“异步”和“性能”

FastAPI 当然支持异步,也有不错的性能表现。

但对大多数业务项目来说,它更重要的价值其实是:

  • 接口定义清晰;
  • 参数校验自然;
  • 文档自动生成;
  • 开发协作成本低。

如果把注意力只放在“快不快”,反而会错过它最有实际意义的部分。

九、什么时候适合用 FastAPI,什么时候不要硬上

到这里你会发现,FastAPI 特别适合下面这些场景:

  • 做 REST API;
  • 做前后端分离后端;
  • 做微服务;
  • 把数据处理、模型推理、业务逻辑封装成 HTTP 服务;
  • 需要比较清晰的接口边界和文档能力。

但它也不是所有项目的最优解。

比如:

  • 如果你只是想快速做一个带表单和结果页的小工具,Streamlit 可能更快;
  • 如果你想先用非常轻的方式理解 Web 服务结构,Flask 依然很好;
  • 如果你的重点根本不在“接口治理”,那就没必要为了追新而硬套 FastAPI。

框架选型这件事,最终还是回到那个问题:

你现在最想解决的,究竟是 API 规范问题,还是页面交互问题,还是快速验证问题。

十、写在最后

如果你以前对后端接口开发的印象还是“写几个路由、返回几个 JSON”,那 FastAPI 很适合帮你建立一个更现代的 API 开发认知:

  • 参数有定义;
  • 输入有校验;
  • 输出有约束;
  • 文档能自动同步;
  • 接口能直接调试。

它并没有把后端开发变简单到“没有门槛”,但它确实把很多原本零散、容易出错的工作,整合成了一套更顺手的开发流程。

对于刚入门 Python Web 的人来说,我很建议你至少亲手把这样一个小项目跑一遍。等你真正写过、调过、踩过参数校验和文档联调的坑之后,你对 FastAPI 的理解会比看十篇概念文章都扎实。

下一篇,我会继续写 Flask:为什么它到今天依然值得学,以及它和 FastAPI 的差异到底应该怎么理解。

Logo

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

更多推荐