FastAPI 入门:给寒柳别苑搭一个小型 API

FastAPI 入门:给寒柳别苑搭一个小型 API

2026年07月29日
3467 字 · 17 分钟

FastAPI 入门:给寒柳别苑搭一个小型 API

最近开始系统学习 FastAPI。与其把知识点停留在“会写一个 Hello World”,不如给一个具体的网站设计一组接口。

所以这篇学习记录选择 zylatent.com 作为例子,假设我们要为寒柳别苑提供一层轻量的 API:

  • 返回站点基本信息;
  • 查询文章列表和文章详情;
  • 接收访客留言;
  • 自动生成接口文档;
  • 让前端能够明确知道请求格式和错误原因。

这里的域名不是抽象的占位符:这组学习接口已经部署在 api.zylatent.com,文章末尾的面板会直接调用它。真正复杂的业务仍然需要数据库、认证和更严格的安全策略;这里先把最小的请求—响应链路跑通。

1. FastAPI 解决的是什么问题

如果不用框架,单纯用 Python 内置的 http.server 写一个接口,需要自己处理很多与业务无关的细节:

  1. 判断请求方法和路径;
  2. 读取请求体;
  3. 解析 JSON;
  4. 检查字段是否存在、类型是否正确;
  5. 设置状态码和响应头;
  6. 把 Python 对象序列化成 JSON;
  7. 为每个接口补充说明文档。

真正的业务可能只有一句话:访问 /api/site,返回站点信息。

FastAPI 的价值就在于把这些通用工作收拢起来,让我们用 Python 类型标注和装饰器描述接口。它建立在 Starlette 和 Pydantic 之上:前者负责 Web 层能力,后者负责数据模型与校验。

2. 创建项目和运行环境

FastAPI 官方目前推荐使用 uv 管理项目和虚拟环境。使用 pipvenv 也可以,关键是不要把项目依赖直接装进系统 Python。

使用 uv

Terminal window
uv init fastapi-zylatent --bare
cd fastapi-zylatent
uv add "fastapi[standard]"

fastapi[standard] 会安装 FastAPI 的标准依赖,其中包括运行服务的 Uvicorn 和提供 fastapi 命令的 CLI。

使用 venv 和 pip

Terminal window
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
# macOS / Linux
source .venv/bin/activate
pip install "fastapi[standard]"

如果你已经在使用 Conda,也可以把环境隔离交给 Conda,只要保证 FastAPI 和项目其他依赖安装在同一个环境中即可。

3. 第一个接口:让服务先跑起来

在项目根目录新建 main.py

from fastapi import FastAPI
app = FastAPI(
title="寒柳别苑 API",
description="为 zylatent.com 提供的学习型接口",
version="0.1.0",
)
@app.get("/")
def read_root():
return {"message": "寒柳别苑 API 正在运行"}

启动开发服务器:

Terminal window
# 使用 uv
uv run fastapi dev
# 如果已经激活虚拟环境,也可以直接运行
fastapi dev

本地开发时,服务默认监听 http://127.0.0.1:8000。浏览器访问根路径,可以看到:

{
"message": "寒柳别苑 API 正在运行"
}

这篇文章对应的学习 API 已经部署在 api.zylatent.com。线上访问使用 HTTPS 的 443 端口,外部请求由 Nginx 转发到服务器内部的 FastAPI 8000 端口;读者不需要直接访问或暴露内部端口,可以直接运行:

Terminal window
curl.exe https://api.zylatent.com/health
curl.exe https://api.zylatent.com/api/site

如果你看到 {"status":"ok"},说明请求已经从命令行经过域名、HTTPS 和反向代理抵达 FastAPI。

这里的 @app.get("/") 是一个路径操作声明:当请求方法是 GET、路径是 / 时,调用下面的 read_root 函数。

4. /docs:接口文档不是额外工作

线上体验可以直接打开:

https://api.zylatent.com/docs

如果你是在自己的电脑上启动开发服务,再使用本地地址:

http://127.0.0.1:8000/docs

可以看到 Swagger UI。它会根据代码里的路由、类型标注和 Pydantic 模型自动生成:

  • 接口列表;
  • 请求参数结构;
  • 响应格式;
  • 状态码说明;
  • 可以直接执行的测试表单。

FastAPI 同时会生成 OpenAPI Schema。也就是说,文档不是额外维护的一份 Markdown,而是从接口代码中派生出来的描述。

5. 用一个站点接口理解响应模型

先定义站点资料模型。这里使用 Pydantic 的 BaseModel 作为响应模型:

from pydantic import BaseModel
class SiteProfile(BaseModel):
name: str
domain: str
description: str
topics: list[str]
SITE_PROFILE = SiteProfile(
name="寒柳别苑",
domain="zylatent.com",
description="记录学习、工作与思考。",
topics=["Python", "C++", "Claude Code Skills", "诗歌", "写作"],
)
@app.get("/api/site", response_model=SiteProfile)
def read_site_profile():
return SITE_PROFILE

请求:

Terminal window
curl.exe https://api.zylatent.com/api/site

响应:

{
"name": "寒柳别苑",
"domain": "zylatent.com",
"description": "记录学习、工作与思考。",
"topics": ["Python", "C++", "Claude Code Skills", "诗歌", "写作"]
}

response_model 的作用不只是给编辑器看。它还会参与响应数据的校验和 OpenAPI 文档生成,帮助我们明确“接口允许返回什么”。

6. 路径参数和查询参数:查询文章列表

接下来把示例扩展成一个简化的文章 API。与其把文章摘要继续写死在 main.py,不如单独维护一个 JSON 文件:接口负责读取它,新增文章时只需要增加一条记录。

import json
from pathlib import Path
from fastapi import Query
class PostSummary(BaseModel):
slug: str
title: str
category: str
tags: list[str]
class PostDetail(PostSummary):
content: str
POSTS_FILE = Path(__file__).parent / "data" / "posts.json"
def load_posts() -> list[PostDetail]:
with POSTS_FILE.open(encoding="utf-8") as file:
return [PostDetail.model_validate(item) for item in json.load(file)]
POSTS = load_posts()
@app.get("/api/posts", response_model=list[PostSummary])
def list_posts(
category: str | None = None,
limit: int = Query(default=10, ge=1, le=50),
offset: int = Query(default=0, ge=0),
):
result = POSTS
if category is not None:
result = [post for post in result if post.category == category]
return result[offset : offset + limit]

对应的 api/data/posts.json 可以这样写:

[
{
"slug": "fastapi-basics",
"title": "FastAPI 入门:给寒柳别苑搭一个小型 API",
"category": "尺蠖",
"tags": ["Python", "FastAPI"],
"content": "这是一篇 FastAPI 学习记录。"
}
]

这样,写完一篇新文章后,只要把摘要追加到 JSON,重新部署 API,文章列表就会同步更新。后续如果接入数据库,还可以把这个文件替换成数据库查询,而不必改变调用方的接口格式。

这里出现了三种不同来源的参数:

参数例子FastAPI 的判断方式
路径参数/api/posts/{slug}参数名出现在路径模板中
查询参数?category=尺蠖&limit=10普通函数参数,且不属于路径
请求体{"message": "..."}参数类型是 Pydantic 模型

查询接口可以这样调用:

Terminal window
curl.exe "https://api.zylatent.com/api/posts?category=尺蠖&limit=10"

Query(ge=1, le=50) 表示 limit 的最小值是 1,最大值是 50。如果传入 limit=0,FastAPI 会自动返回 422,而不是让错误参数继续进入业务逻辑。

7. 路径参数和 404:文章详情接口

列表接口只能返回摘要,详情接口需要根据 slug 找到一篇文章:

from fastapi import HTTPException
class PostDetail(PostSummary):
content: str
@app.get("/api/posts/{slug}", response_model=PostDetail)
def get_post(slug: str):
post = next((post for post in POSTS if post.slug == slug), None)
if post is None:
raise HTTPException(status_code=404, detail="文章不存在")
return post

访问:

Terminal window
curl.exe https://api.zylatent.com/api/posts/fastapi-basics

不存在的 slug:

Terminal window
curl.exe https://api.zylatent.com/api/posts/not-found

服务会返回:

{
"detail": "文章不存在"
}

这比在函数里直接返回一个空字典更好,因为调用方能通过 404 明确知道资源不存在。

8. POST 请求:给网站加入留言接口

GET 主要用于读取资源,POST 常用于提交新数据。现在设计一个简化的留言接口:

from datetime import datetime, timezone
from pydantic import Field
class FeedbackCreate(BaseModel):
name: str = Field(min_length=1, max_length=30)
message: str = Field(min_length=1, max_length=1000)
page: str = Field(default="/", pattern=r"^/")
class FeedbackOut(FeedbackCreate):
id: int
created_at: datetime
FEEDBACKS: list[FeedbackOut] = []
@app.post("/api/feedback", response_model=FeedbackOut, status_code=201)
def create_feedback(payload: FeedbackCreate):
feedback = FeedbackOut(
id=len(FEEDBACKS) + 1,
created_at=datetime.now(timezone.utc),
**payload.model_dump(),
)
FEEDBACKS.append(feedback)
return feedback

提交请求:

Terminal window
curl.exe -X POST https://api.zylatent.com/api/feedback `
-H "Content-Type: application/json" `
-d '{"name":"访客","message":"这篇文章很有帮助。","page":"/blog/fastapi-basics/"}'

PowerShell 中反引号用于换行。如果不想处理换行,也可以直接写成一行。

这个模型同时承担三项工作:

  1. 把 JSON 请求体解析成 Python 对象;
  2. 校验字符串长度和 page 的格式;
  3. 把请求结构显示在 /docs 中。

9. 422、404 和 500:错误应该怎么读

422:请求格式不符合模型

例如漏掉 message

Terminal window
curl.exe -X POST https://api.zylatent.com/api/feedback `
-H "Content-Type: application/json" `
-d '{"name":"访客"}'

这是请求方的问题。FastAPI 会在业务函数执行前完成校验,并返回 422 以及具体字段的位置。你可以直接对线上演示接口运行上面的命令,不会创建留言。

404:资源不存在

这是我们主动通过 HTTPException 返回的明确结果,常见于文章、用户或文件找不到的情况。

500:服务端代码真的出错了

如果把 payload.message 手误写成 payload.msg,程序会触发属性错误。开发环境中可以查看 traceback;生产环境中不应该把完整堆栈返回给访客。

读 traceback 时,我会先看最后一行,再回到自己代码的文件名和行号:

  1. 最后一行通常告诉你错误类型和直接原因;
  2. 往上找到项目目录中的代码行;
  3. 再判断是请求数据不对,还是服务内部状态不对。

报错不是事故本身,而是定位事故的线索。

10. 让 zylatent.com 调用 API:CORS 与部署边界

如果前端和 API 使用同一个 origin,例如:

https://zylatent.com 前端
https://zylatent.com/api FastAPI 反向代理

浏览器通常不需要额外的跨域配置。

如果以后把 API 单独部署在:

https://api.zylatent.com

那么前端从 zylatent.com 请求 api.zylatent.com 就属于跨域,需要显式配置允许的 origin:

from fastapi.middleware.cors import CORSMiddleware
origins = [
"https://zylatent.com",
"https://www.zylatent.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Content-Type"],
)

前端调用可以是:

const response = await fetch("https://api.zylatent.com/api/site");
const site = await response.json();
console.log(site.name);

不要在学习阶段直接写 allow_origins=["*"] 然后忘记收紧。公开 API 的 origin、认证、速率限制和日志策略,都应该随着部署环境明确配置。

11. 从示例代码到真实项目,还缺什么

为了在一篇入门文章里看清完整链路,接口逻辑暂时集中在 main.py,文章数据已经单独放到 JSON。真实项目可以进一步拆分:

fastapi-zylatent/
├── app/
│ ├── main.py # 创建 FastAPI 应用
│ ├── models.py # Pydantic 请求和响应模型
│ ├── routers/
│ │ ├── site.py # 站点接口
│ │ ├── posts.py # 文章接口
│ │ └── feedback.py # 留言接口
│ ├── services/ # 业务逻辑
│ └── db.py # 数据库连接
├── tests/
├── pyproject.toml
└── uv.lock

下一步通常是:

  • 用 SQLModel 或 SQLAlchemy 接入数据库;
  • 用依赖注入共享数据库会话;
  • 为接口添加测试;
  • 加入认证和权限;
  • 配置生产服务器与反向代理;
  • 用环境变量管理域名、密钥和数据库连接串。

这些内容不应该在第一篇入门文章里全部展开。先把“请求如何进入路由,数据如何被校验,响应如何返回”这条链路走通,后续的数据库和认证才有落脚点。

12. 学习记录

这次学习让我对 FastAPI 的理解从“一个写 API 的 Python 框架”变得更具体:

  1. 路由装饰器把 HTTP 方法和路径绑定到 Python 函数;
  2. 类型标注让路径参数、查询参数和请求体的边界变得清楚;
  3. Pydantic 模型把数据合约、校验和接口文档连接起来;
  4. HTTPException 让错误成为 API 合约的一部分;
  5. /docs 让接口可以在没有额外前端页面的情况下被观察和测试;
  6. 域名只是访问入口,真正的部署还需要服务器、反向代理、跨域和安全配置。

对我来说,比较有价值的不是记住几条命令,而是把一个熟悉的网站想象成一组可以被其他程序调用的资源:站点资料、文章列表和留言,都可以先用清晰的接口表达出来。

  • 用 FastAPI 创建应用,通过一个 GET 路由返回 JSON。



  • 使用 Pydantic 模型定义站点资料、文章摘要和留言请求。



  • 区分路径参数、查询参数和请求体,并用 404 与 422 表达不同错误。



  • 围绕 zylatent.com 设计站点、文章和留言 API,同时意识到内存存储不能直接用于生产。



  • 继续接入数据库、测试、认证和生产部署。


13. 让读者实际调用一次

现在 API 已经部署,浏览器端可以直接调用它。下面这个小面板只开放读取接口,点击按钮后会把真实响应显示出来:

动手试试:调用寒柳别苑 API

当前地址:https://api.zylatent.com

GET

它本质上就是下面这段 JavaScript:

const response = await fetch("https://api.zylatent.com/api/site");
const site = await response.json();
console.log(site);

如果你更熟悉 Python,也可以直接运行下面的脚本。先安装一个轻量的 HTTP 客户端:

Terminal window
python -m pip install requests

然后保存为 try_zylatent_api.py

import requests
BASE_URL = "https://api.zylatent.com"
def get_json(path: str, **kwargs):
response = requests.get(f"{BASE_URL}{path}", timeout=10, **kwargs)
response.raise_for_status()
return response.json()
site = get_json("/api/site")
print(f"{site['name']}{site['description']}")
print("主题:", "、".join(site["topics"]))
posts = get_json(
"/api/posts",
params={"category": "尺蠖", "limit": 10},
)
for index, post in enumerate(posts, start=1):
print(f"{index}. {post['title']} [{post['slug']}]")

这个例子对应的就是一次普通的 GET 请求:Python 负责发出请求和解析 JSON,FastAPI 负责读取文章文件、筛选参数并返回结构化响应。你也可以把 /api/posts 换成 /api/posts/fastapi-basics,进一步试试路径参数。

也可以在终端里调用同一个接口:

Terminal window
curl.exe https://api.zylatent.com/health
curl.exe "https://api.zylatent.com/api/posts?category=尺蠖&limit=10"

为了让这个公开演示保持简单,页面暂时只调用 GET 接口;读者可以通过线上 /docs 或命令行体验留言接口。当前留言只保存在服务器进程的内存中,重启后会消失,也没有验证码、频率限制和内容审核,因此它仍然只是学习演示,不应直接当作正式留言系统使用。

参考资料


Thanks for reading!

FastAPI 入门:给寒柳别苑搭一个小型 API

2026年07月29日
3467 字 · 17 分钟
加载中...

评论 (需 GitHub 账号登录)

正在加载评论...