Skip to content

09 · 服务端语言上手:FastAPI + Python 服务端 ​

你已经会前端,现在要写第一个后端服务。本篇用 FastAPI 把"从 Hello World 到可部署服务"一次讲通,全程用前端心智做对照。

速览卡 ​

项内容
一句话定位前端转后端的第一个动手篇:FastAPI 的最小完整链路 + 与前端心智的差异点
核心内容FastAPI 为什么适合前端转 AI 全栈、最小服务、请求校验(Pydantic)、依赖注入、异步模型、配置与部署最小步骤
读完能回答FastAPI 和 Express 有什么本质区别?Python 的 async 和 JS 的事件循环一样吗?Pydantic 是什么?
相关板块C00 · 学习全景 | E01 · Python 基础 | C02 · API 设计

一、为什么是 FastAPI ​

维度FastAPI说明
生态衔接 AI 全家桶LangChain、OpenAI SDK、向量库全是 Python 优先
类型友好Pydantic 校验 = 运行时的 TS 类型前端最容易上手的 Python 后端框架
异步原生 async/await心智模型和 JS 几乎一致
自带文档OpenAPI + Swagger UI写接口自带可点页面,调试成本低
性能基于 Starlette/Uvicorn非性能瓶颈场景足够

一句话:你做 AI 全栈,后端主线就选 Python + FastAPI。C01 已经论证过"AI 生态在 Python 一侧",这里不再重复。


二、最小可运行服务 ​

2.1 初始化 ​

bash
mkdir my-api && cd my-api
python -m venv .venv                 # 类似 npm 的局部 node_modules
source .venv/bin/activate            # Windows: .venv\Scripts\activate
pip install fastapi "uvicorn[standard]"

venv 类比 npm:.venv/ 是项目的独立 Python 环境,装包不污染全局。依赖清单用 pip freeze > requirements.txt(对应 package.json + lockfile)。

2.2 Hello World ​

python
# main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "hello from backend"}
bash
uvicorn main:app --reload --port 8000

访问 http://localhost:8000/docs,你会看到自动生成的 Swagger 文档——这是 FastAPI 与 Express 最直观的差异:类型注解即文档。


三、请求校验:Pydantic 就是"运行时的 TypeScript" ​

3.1 定义一个模型 ​

python
from typing import Optional
from pydantic import BaseModel, Field


class ItemCreate(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)
    tag: Optional[str] = None

3.2 在路由中使用 ​

python
from fastapi import FastAPI

app = FastAPI()


@app.post("/items")
def create_item(item: ItemCreate):
    # 请求体会被自动校验;不合法直接返回 422
    return {"id": 1, **item.model_dump()}

与 TS 的关键差异:TS 的类型在编译期检查、运行时空空如也;Pydantic 在运行时校验——非法请求会被挡在业务代码之外,返回标准 422。这就是"运行时的 TypeScript"的含义。

⚠️ model_dump() 返回 dict(旧版 dict()),不要用 item.json() 存库,那会返回字符串。


四、依赖注入:从"手动取"到"声明我要" ​

前端里你会手动 new Service() 或从容器拿实例。FastAPI 用依赖注入自动装配:

python
from fastapi import Depends


def get_db():
    db = DBSession()
    try:
        yield db          # 请求结束自动关闭
    finally:
        db.close()


@app.get("/items/{item_id}")
def read_item(item_id: int, db=Depends(get_db)):
    return db.query(item_id)

类比:Depends(get_db) 像前端框架的依赖容器——你不写"创建连接、用完关闭",框架按声明帮你做。yield 语法用于保证请求结束后的清理。


五、异步模型:像 JS,但有一条铁律 ​

概念JS(Node)Python(asyncio)
单线程事件循环✅✅(一个线程跑事件循环)
协程关键字async/awaitasync/await
真正并行靠 worker 线程/进程靠多进程 / 多事件循环
阻塞陷阱同步阻塞调用卡事件循环同理:time.sleep 会卡住整个事件循环
python
import asyncio


@app.get("/slow")
async def slow():
    await asyncio.sleep(1)      # ✅ 正确:让出事件循环
    return {"ok": True}
python
import time


@app.get("/wrong")
async def wrong():
    time.sleep(1)               # ❌ 阻塞整个事件循环,所有请求都卡住
    return {"ok": True}

铁律:在 async 函数里绝不能调同步阻塞库(time.sleep、requests.get)。要么用异步版本(httpx.AsyncClient、aiosqlite),要么把函数声明成普通 def(FastAPI 会丢给线程池)。


六、配置与环境变量 ​

python
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    app_name: str = "My API"
    database_url: str
    openai_api_key: str = ""

    model_config = {"env_file": ".env"}


settings = Settings()

.env 文件对应前端的 .env 用法;密钥绝不能硬编码进代码。BaseSettings 会自动读环境变量和 .env。


七、部署最小步骤 ​

dockerfile
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
bash
docker build -t my-api .
docker run -p 8000:8000 --env-file .env my-api

部署细节(多环境、CI/CD、反向代理)见 C17 · 部署与 CI/CD。


自检清单 ​

  • [ ] 能写出一个带请求体校验的 POST 接口,并知道 422 是谁返回的
  • [ ] 能用 Depends 写依赖注入,并解释 yield 的作用
  • [ ] 能说清 Python async 与 JS 事件循环的相同点和"阻塞陷阱"这条铁律
  • [ ] 能用 BaseSettings 读环境变量,不硬编码密钥
  • [ ] 能写 Dockerfile 并本地跑起容器

常见误区 ​

  • 误区:Python 类型注解会在运行时校验。 不会。运行时校验靠 Pydantic 显式声明,普通注解只是给人和 IDE 看。
  • 误区:async def 就自动并发。 不是。里面的同步阻塞调用照样卡死事件循环。
  • 误区:.venv 和 node_modules 一样可以删了重建。 可以,但要记得有 requirements.txt 才能恢复,先 pip freeze。
  • 误区:FastAPI 只适合小项目。 它一样能做中大型服务,只是生态和组织方式要自己搭(见 C05)。

动手练习 ​

练习 A:Todo API ​

写一个内存版 Todo API:GET /todos、POST /todos(带校验)、DELETE /todos/{id}。在 /docs 里逐个调试。

练习 B:加一个异步慢接口 ​

写一个 GET /slow(用 asyncio.sleep)和 GET /wrong(用 time.sleep),用两个并发请求对比差异,确认你理解了阻塞陷阱。

练习 C:容器化 ​

给 Todo API 写 Dockerfile,本地 docker build + docker run 跑通。


相关文档 ​

本区 ​

其他域 ​