代码语言

知识点思维导图

28 个知识节点

Python(14) - FastAPI 入门

读完后,你应能完成以下任务:

  • 绘制“Python(14) - FastAPI 入门 / 边界一:装饰器即路由(@app.get 到底是什么)”的关键对象与数据流,解释“Express 里注册路由是「调函数」:app.get(path, fn),把 fn 当参数传进去。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(14) - FastAPI 入门 / 边界二:没有 req/res——参数靠函数签名,响应靠 return”设计正常与异常输入,验证“这是从 Express 转过来最大的认知转变,务必吃透。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(14) - FastAPI 入门 / 白送的接口文档:/docs(Express 要装 Swagger 才有)”的最小代码或配置,检验“另有 http://127.0.0.1:8000/redoc 是另一种风格的只读文档。”,输出命令、结果与 Diff,并说明不适用边界。

你在 Node 端写过 app.get('/users', handler),用 Express 撸过几十个接口。FastAPI 的路由长得几乎一样——但它把「参数校验」「类型转换」「接口文档」这些你在 Express 里要手写或装一堆中间件才能搞定的事,靠 Python 的类型注解直接内置了。本篇帮你把 Express 的心智模型平移过来,并立刻划清三处关键差异:装饰器即路由、函数签名即参数、返回 dict 即响应。

一、先给锚点:FastAPI 路由 ≈ Express 路由

最小可跑的一个接口,左边 Express、右边 FastAPI 并排看:

// Express (Node)
const express = require('express')
const app = express()

// 注册一个 GET 路由,第一个参数是路径,第二个是处理函数
app.get('/', (req, res) => {
  res.json({ message: 'hello' })   // 手动调 res.json 序列化
})

app.listen(3000)   // 监听 3000 端口

启动它(FastAPI 自己不带服务器,靠 uvicorn 这个 ASGI 服务器跑,详见第 13 篇):

# 安装:fastapi 是框架,uvicorn 是跑它的服务器(类比 node 直接内置了 http server,Python 要单独装)
pip install "fastapi[standard]" uvicorn

# 启动:main 是文件名(main.py),app 是上面那个实例名,--reload 类似 nodemon 热重载
uvicorn main:app --reload

第一组对照表,先建立直觉:

Express (Node) FastAPI (Python) 说明
const app = express() app = FastAPI() 创建应用实例
app.get('/x', fn) @app.get("/x") 注册路由(FastAPI 用装饰器)
app.post / put / delete @app.post / .put / .delete HTTP 方法一一对应
req / res 对象 函数参数 / return 关键差异,见第三节
app.listen(3000) uvicorn main:app 启动方式(外部服务器)
手动 res.json(obj) 直接 return dict 自动序列化

二、边界一:装饰器即路由(@app.get 到底是什么)

Express 里注册路由是「调函数」:app.get(path, fn),把 fn 当参数传进去。FastAPI 里是「贴装饰器」:@app.get(path) 写在函数头顶上。两者效果一样,但写法不同,别被 @ 吓到。

装饰器机制详见第 10 篇。这里只需记住一个心智模型:@app.get("/") 就是「把下面这个函数登记到 app 的路由表里,绑定到 GET /」。如果你写过 Angular/TS 的 @Component@Get()(NestJS),这个语法你已经见过了——长得一模一样。

// Express 对照:同路径不同方法,链式或分开注册
app.get('/users', (req, res) => res.json([{ id: 1, name: 'Tom' }]))
app.post('/users', (req, res) => res.json({ ok: true }))
app.delete('/users/:user_id', (req, res) => res.json({ deleted: req.params.user_id }))

路径参数写法差异:Express 用冒号 :user_id,FastAPI 用花括号 {user_id}。仅此而已。


三、边界二:没有 req/res——参数靠函数签名,响应靠 return

这是从 Express 转过来最大的认知转变,务必吃透。

Express 里,所有输入都从 req 这个大对象里掏(req.params / req.query / req.body),所有输出都往 res 上写(res.json / res.status)。FastAPI 反过来:你想要什么参数,就在函数签名里声明什么,FastAPI 看类型注解自动从对的位置取值、自动转类型。

// Express 对照:全从 req 掏,且全是字符串,要自己转类型
app.get('/users/:user_id', (req, res) => {
  const userId = parseInt(req.params.user_id)   // 手动转 int
  const q = req.query.q || ''                    // 手动取 query、给默认值
  const limit = parseInt(req.query.limit) || 10  // 手动转 + 默认
  res.json({ user_id: userId, q, limit })
})

FastAPI 区分参数来源的规则(先记住前两条,body 见第四节):

参数特征 FastAPI 判定为 对应 Express 例子
出现在路径 {xxx} 路径参数 req.params.xxx /users/{user_id}
简单类型、不在路径里 查询参数 req.query.xxx ?q=tom&limit=5
类型是 Pydantic 模型 请求体 req.body 见第四节

✅ 这套「声明即获取」最大的好处:类型注解(详见第 11 篇)不再只是给编辑器看的提示,FastAPI 把它当成运行时的校验与转换规则。Express 里你写 req.query.limit 永远是 string,要自己 parseInt;FastAPI 写 limit: int 就真的拿到 int,转不动直接 422。这相当于 Express 里你得手动装 + 配一堆校验中间件才有的能力。


四、请求体:Pydantic 模型 ≈ TS interface + zod 二合一

POST/PUT 要收 JSON body 时,Express 里你装 body-parser、从 req.body 掏、再自己写校验。FastAPI 让你先定义一个 Pydantic 模型类,把它写进函数签名,body 的接收、解析、校验一步到位。

Pydantic 是 FastAPI 的数据校验基石,本篇只给最小用法,完整的字段约束、嵌套模型详见第 15 篇。

// Express + TS 对照:interface 只在编译期存在,运行时校验得另写(或上 zod)
interface UserIn {
  name: string
  age: number
  email?: string
}
app.post('/users', (req, res) => {
  const user = req.body as UserIn   // 仅类型断言,运行时 body 可能根本不符!
  // 想要真正的运行时校验,得手写 if,或引入 zod 单独定义 schema
  if (typeof user.name !== 'string' || typeof user.age !== 'number') {
    return res.status(422).json({ error: '参数不合法' })
  }
  res.json({ created: user.name, age: user.age })
})

⚠️ 关键差异:TS 的 interface 编译后就消失了,运行时拦不住一个乱传的 body——这是前端最容易误以为「类型已经保我了」的坑。Pydantic 模型是真实存在于运行时的对象,FastAPI 用它在请求进函数前就把关。所以 FastAPI 里你几乎不用写「参数校验 if」,这部分逻辑被模型吃掉了。


五、白送的接口文档:/docs(Express 要装 Swagger 才有)

把服务跑起来后,浏览器打开 http://127.0.0.1:8000/docs,你会看到一个自动生成的、可交互的 Swagger UI——所有路由、参数类型、请求体结构、能直接点「Try it out」发请求。

这不是额外配置出来的。FastAPI 把你写的类型注解 + Pydantic 模型,自动转成了 OpenAPI 规范并渲染成文档。

能力 Express FastAPI
接口文档 swagger-jsdoc + 手写注释 内置,零配置,/docs 直接看
文档与代码同步 容易写完忘了更新 文档由代码生成,天然同步
在线调试 另开 Postman /docs 里直接 Try it out

对前端的实际价值:你转后端后写的接口,前端同学(或未来的你)打开 /docs 就能看清参数和返回结构,不用再追着问「这个接口要传啥」。另有 http://127.0.0.1:8000/redoc 是另一种风格的只读文档。


六、async:语法和 JS 一模一样(机制差异留到后面)

FastAPI 的处理函数既可以是普通 def,也可以是 async def。如果你的函数里要 await 别的异步操作(查数据库、调外部 API),就写 async def

// JS 对照:几乎逐行映射
app.get('/proxy', async (req, res) => {
  const resp = await fetch('https://httpbin.org/get')
  res.json(await resp.json())
})

边界提醒:现在你只需把 async/await 当成「和 JS 写法一样」来用即可——能 await 的就 async def,纯 CPU 计算或同步代码用普通 def(FastAPI 会自动用线程池跑普通 def,不会卡住)。至于「Python 有 GIL、async 到底怎么并发的、和 Node 单线程有何不同」这些机制层面的东西,详见第 17 篇,本篇不展开,先建立「写法照搬 JS」的直觉就够了。


七、把一个 CRUD 接口串起来

综合前面所有要点,写一个最小但完整的用户接口(内存存储,数据库版详见第 16 篇):

四个接口跑起来后,去 /docs 就能直接点着测——这就是阶段三要求你能独立撸出的 CRUD 雏形。


八、总结

  • 先给锚点:FastAPI 路由 ≈ Express 路由:最小可跑的一个接口,左边 Express、右边 FastAPI 并排看:
  • 边界一:装饰器即路由(@app.get 到底是什么):Express 里注册路由是「调函数」:app.get(path, fn),把 fn 当参数传进去。
  • 边界二:没有 req/res——参数靠函数签名,响应靠 return:这是从 Express 转过来最大的认知转变,务必吃透。
  • 请求体:Pydantic 模型 ≈ TS interface + zod 二合一:Pydantic 是 FastAPI 的数据校验基石,本篇只给最小用法,完整的字段约束、嵌套模型详见第 15 篇。
  • 白送的接口文档:/docs(Express 要装 Swagger 才有):另有 http://127.0.0.1:8000/redoc 是另一种风格的只读文档。
  • async:语法和 JS 一模一样(机制差异留到后面):FastAPI 的处理函数既可以是普通 def,也可以是 async def。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“FastAPI 入门”中,需要同时满足“先给锚点:FastAPI 路由 ≈ Express 路由”与“边界一:装饰器即路由(@app.get 到底是什么)”。给定正文约束“最小可跑的一个接口,左边 Express、右边 FastAPI 并排看。”,哪些判断保持了原有处理机制?多选
2“FastAPI 入门”出现偏差:“在“FastAPI 入门 / 边界二:没有 req/res——参数靠函数签名,响应靠 retu”中,即使不满足“这是从 Express 转过来最大的认知转变,务必吃透”,结果与副作用仍会保持不变。”已成为实际行为。围绕“边界二:没有 req/res——参数靠函数签名,响应靠 return”与“请求体:Pydantic 模型 ≈ TS interface + zod 二合一”,哪些判断能定位被改变的职责或边界?多选
3评审“FastAPI 入门”方案时,验收条件包含“把服务跑起来后,浏览器打开 http://127.0.0.1:8000/docs,你会看到一个自动生成的、可交互的 Swagger UI——所有路由、参数类型、请求体结构、能直接点「Try it out」发请求。”。关于“白送的接口文档:/docs(Express 要装 Swagger 才有)”与“async:语法和 JS 一模一样(机制差异留到后面)”的哪些决策符合正文机制?多选