Python Advance 📂 Important concepts · 4 of 6 357 min read

FastAPI Tutorial — From Zero to Production with Diagrams and Code

A practical, diagram-driven FastAPI tutorial covering every stage from your first Hello World to production deployment. Learn type-hint-driven validation, Pydantic models, path and query parameters, dependency injection, JWT authentication, SQLAlchemy integration, async vs sync endpoints, middleware, testing, and the request lifecycle — all with runnable Python examples and visual pipelines.

🌐
SectionSection第 01 节

The Story That Explains FastAPI 🛂FastAPI समझाने वाली कहानी 🛂一个讲清 FastAPI 的故事 🛂

The Airport Passport Control Deskहवाई अड्डे का Passport Control Desk机场的护照检查台
Imagine an old-style passport counter at a busy airport. The officer takes your passport, reads every field by hand, checks paper forms, looks at the photo, stamps it and files the paperwork. Slow. Easy to get wrong. Long queues.

Now picture a modern e-Gate. You place your passport on a scanner. In a moment it reads the fields, checks them against the rules, matches your face and opens the gate. If a field is wrong, the screen tells you exactly which one and why.

FastAPI is the e-Gate of Python web frameworks. You describe what a request should look like with type hints, and FastAPI reads it, checks it, documents it and sends it to the right function — automatically.
एक व्यस्त हवाई अड्डे पर पुराने ढंग का passport counter सोचिए। Officer आपका passport लेता है, हर field हाथ से पढ़ता है, काग़ज़ी forms जाँचता है, photo देखता है, ठप्पा लगाता है और कागज़ात दर्ज करता है। धीमा। ग़लती आसान। लंबी कतारें।

अब एक आधुनिक e-Gate सोचिए। आप passport scanner पर रखते हैं। पल भर में यह fields पढ़ता है, नियमों से जाँचता है, चेहरा मिलाता है और gate खोल देता है। कोई field ग़लत हो, तो screen ठीक-ठीक बताती है कौन-सा और क्यों।

FastAPI Python web frameworks का e-Gate है। आप type hints से बताते हैं कि request कैसी होनी चाहिए, और FastAPI उसे पढ़ता, जाँचता, उसका दस्तावेज़ बनाता और सही function तक भेजता है — अपने-आप।
想象繁忙机场里一个老式的护照柜台。工作人员接过你的护照,手工核对每一项,检查纸质表格,比对照片,盖章,再手工归档。慢,容易出错,队伍很长。

再想象一个现代的 自助闸机。你把护照放上扫描器,片刻之间它就 读取 各项信息,按规则 检查,比对你的面孔,然后开闸。哪一项有问题,屏幕会准确告诉你是哪一项、为什么。

FastAPI 就是 Python Web 框架中的自助闸机。你用类型提示描述请求应有的样子,FastAPI 就会自动读取它、检查它、为它生成文档,并把它交给正确的函数。

FastAPI is a modern web framework for building APIs with Python, based on standard type hints. It sits on top of Starlette (the web parts) and Pydantic (the data parts). You get async support, automatic validation and interactive docs — all from the function you would write anyway. Current versions need Python 3.10 or newer.

FastAPI Python में APIs बनाने का आधुनिक web framework है, जो सामान्य type hints पर आधारित है। यह Starlette (web वाला हिस्सा) और Pydantic (data वाला हिस्सा) के ऊपर बना है। आपको async support, अपने-आप validation और interactive docs मिलते हैं — उसी function से जो आप वैसे भी लिखते। मौजूदा versions को Python 3.10 या नया चाहिए।

FastAPI 是一个基于标准类型提示、用 Python 构建 API 的现代 Web 框架。它建立在 Starlette(Web 部分)和 Pydantic(数据部分)之上。你会得到异步支持、自动校验和交互式文档 — 全部来自你本来就要写的那个函数。当前版本需要 Python 3.10 或更新版本。

Figure 1 — One Type-Hinted Function, Four Jobs Doneचित्र 1 — एक Type-Hinted Function, चार काम图 1 — 一个带类型提示的函数,完成四项工作
One declaration, four resultsYOUR FUNCTIONआपका FUNCTION你的函数def read_item( item_id: int, q: str | None)PARSEPARSE解析"42" → 42VALIDATEVALIDATE校验"abc" → 422DOCUMENTDOCUMENT生成文档/docs, /redocSERIALIZESERIALIZE序列化dict → JSON

Type hints are the single source of truth.

Type hints ही सच का एक स्रोत हैं।

类型提示是唯一的事实来源。

💡
The Core Insightमूल बात核心要点

In many frameworks you write the endpoint, then the validation, then the documentation, then the JSON conversion. FastAPI uses your Python type hints as the single source of truth — one declaration gives you all four. Less code, fewer bugs, docs that never fall behind.

कई frameworks में आप endpoint लिखते हैं, फिर validation, फिर documentation, फिर JSON conversion। FastAPI आपके Python type hints को सच का एक स्रोत मानता है — एक घोषणा से चारों मिलते हैं। कम code, कम bugs, docs जो कभी पीछे नहीं छूटते।

在许多框架里,你要先写接口,再写校验,再写文档,再写 JSON 转换。FastAPI 把你的 Python 类型提示 当作唯一的事实来源 — 一次声明,四样都有。代码更少,bug 更少,文档永不落后。

▶️
Six Simulators and a Quizछह Simulators और एक Quiz六个模拟器和一个小测验

Look for the interactive boxes: Where Does Each Argument Come From? (Section 03), the Validation Playground (04), the Request Lifecycle (05), the CRUD Lab (06), the Concurrency Lab (14) and the Bug Doctor (19). Every output on this page is from a real run with FastAPI 0.162.4, Pydantic 2.13.5 and Uvicorn 0.53.0 on Python 3.13.16.

Interactive डिब्बे खोजें: हर Argument कहाँ से आता है? (Section 03), Validation Playground (04), Request Lifecycle (05), CRUD Lab (06), Concurrency Lab (14) और Bug Doctor (19)। इस page का हर output FastAPI 0.162.4, Pydantic 2.13.5 और Uvicorn 0.53.0 के साथ Python 3.13.16 पर असली run से है।

请留意这些互动框:每个参数从哪里来?(第 03 节)、校验游乐场(04)、请求生命周期(05)、CRUD 实验室(06)、并发实验室(14)和 Bug 医生(19)。本页每个输出都来自 FastAPI 0.162.4、Pydantic 2.13.5 和 Uvicorn 0.53.0 在 Python 3.13.16 上的真实运行。

🔧
Fixed: "Python 3.8+" and "Near-Go Speed"सुधार: "Python 3.8+" और "Go जैसी Speed"已修正:"Python 3.8+" 与"接近 Go 的速度"

The original said FastAPI works with Python 3.8+. Current FastAPI releases need Python 3.10 or newer (the version used here declares Requires-Python >=3.10). It also promised "near-Go/Node.js speed". FastAPI is among the fastest Python frameworks, but real speed depends on your code, database and server setup — treat benchmark claims with care.

मूल में कहा गया कि FastAPI Python 3.8+ के साथ चलता है। मौजूदा FastAPI releases को Python 3.10 या नया चाहिए (यहाँ इस्तेमाल version Requires-Python >=3.10 घोषित करता है)। इसने "Go/Node.js जैसी speed" का वादा भी किया। FastAPI सबसे तेज़ Python frameworks में से है, पर असली speed आपके code, database और server setup पर निर्भर है — benchmark दावों को सावधानी से लीजिए।

原文说 FastAPI 支持 Python 3.8+。当前的 FastAPI 版本需要 Python 3.10 或更新版本(这里使用的版本声明了 Requires-Python >=3.10)。原文还承诺"接近 Go/Node.js 的速度"。FastAPI 确实是最快的 Python 框架之一,但实际速度取决于你的代码、数据库和服务器配置 — 对基准测试的说法要谨慎看待。


SectionSection第 02 节

Installation and Your First API 🚀Installation और आपका पहला API 🚀安装与第一个 API 🚀

You need Python 3.10+ and two things: fastapi (the framework) and an ASGI server such as uvicorn that runs it.

आपको Python 3.10+ और दो चीज़ें चाहिए: fastapi (framework) और uvicorn जैसा ASGI server जो उसे चलाता है।

你需要 Python 3.10+ 以及两样东西:fastapi(框架)和运行它的 ASGI 服务器,例如 uvicorn。

# Recommended: FastAPI + Uvicorn + the fastapi CLI + common extras
pip install "fastapi[standard]"

# Or a minimal install (no "fastapi dev" command)
pip install fastapi uvicorn

Hello World — The Shortest Possible APIHello World — सबसे छोटा APIHello World — 最短的 API

Below is main.py, then a real run: the server log, three requests with curl, and a check that /docs exists.

नीचे main.py है, फिर एक असली run: server log, curl से तीन requests, और /docs मौजूद होने की जाँच।

下面是 main.py,然后是一次真实运行:服务器日志、用 curl 发出的三个请求,以及对 /docs 是否存在的检查。

# file: main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}
OUTPUT
$ uvicorn main:app --reload INFO: Uvicorn running on http://127.0.0.1:8011 (Press CTRL+C to quit) INFO: Started reloader process [PID] using WatchFiles INFO: Started server process [PID] INFO: Waiting for application startup. INFO: Application startup complete. $ curl http://127.0.0.1:8011/ {"message":"Hello, FastAPI!"} $ curl "http://127.0.0.1:8011/items/5?q=hi" {"item_id":5,"q":"hi"} $ curl http://127.0.0.1:8011/items/abc {"detail":[{"type":"int_parsing","loc":["path","item_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]} $ curl -o /dev/null -w "%{http_code}" http://127.0.0.1:8011/docs 200

Notice the third request: /items/abc never reached read_item. FastAPI answered with a 422 error that says which field failed and why. You can also start the server with fastapi dev main.py (included in fastapi[standard]).

तीसरी request देखिए: /items/abc कभी read_item तक नहीं पहुँची। FastAPI ने 422 error दिया जो बताता है कौन-सा field fail हुआ और क्यों। आप server fastapi dev main.py से भी चला सकते हैं (fastapi[standard] में शामिल)।

注意第三个请求:/items/abc 根本没有到达 read_item。FastAPI 直接返回 422 错误,说明哪个字段失败以及原因。你也可以用 fastapi dev main.py 启动服务器(包含在 fastapi[standard] 中)。

🎉
Free Superpowers — Right Nowमुफ़्त Superpowers — अभी立刻拥有的免费超能力

Open http://127.0.0.1:8000/docs and you already have an interactive Swagger UI where you can try every endpoint. /redoc shows the same API as a clean reference page. Both are generated from your code — no extra files.

http://127.0.0.1:8000/docs खोलिए और आपके पास पहले से interactive Swagger UI है जहाँ आप हर endpoint आज़मा सकते हैं। /redoc वही API साफ़ reference page की तरह दिखाता है। दोनों आपके code से बनते हैं — कोई अतिरिक्त files नहीं।

打开 http://127.0.0.1:8000/docs,你就已经拥有一个交互式的 Swagger UI,可以试用每个接口。/redoc 则把同一个 API 显示为简洁的参考页面。两者都由你的代码自动生成 — 无需额外文件。


SectionSection第 03 节

The Three Pillars of Every Endpoint 🏛️हर Endpoint के तीन खंभे 🏛️每个接口的三大支柱 🏛️

Most data comes into an endpoint from three places. Understanding them is a big part of understanding FastAPI.

Endpoint में ज़्यादातर data तीन जगहों से आता है। इन्हें समझना FastAPI समझने का बड़ा हिस्सा है।

进入接口的数据大多来自三个地方。理解它们,就理解了 FastAPI 的一大半。

📍
Pillar 1 — Path parametersखंभा 1 — Path parameters支柱 1 — 路径参数
/items/{item_id}
Values inside the URL path. They identify one resource. Always required. Written in curly braces in the route and matched by name to a function argument.
URL path के अंदर values। ये एक resource पहचानती हैं। हमेशा ज़रूरी। Route में curly braces में लिखी जाती हैं और नाम से function argument से मिलती हैं।
URL 路径中 的值,用来标识某个资源。总是必填。在路由中写在花括号里,按名字与函数参数对应。
🔍
Pillar 2 — Query parametersखंभा 2 — Query parameters支柱 2 — 查询参数
?limit=10&skip=0
Key-value pairs after the ?. Used for filtering, paging and sorting. A function argument that is not in the path and has a simple type (int, str, bool…) becomes a query parameter.
? के बाद key-value जोड़े। Filtering, paging और sorting के लिए। जो argument path में नहीं और जिसका type सरल हो (int, str, bool…), वह query parameter बनता है।
? 之后 的键值对,用于筛选、分页和排序。不在路径中、类型简单(int、str、bool…)的函数参数会成为查询参数。
📦
Pillar 3 — Request bodyखंभा 3 — Request body支柱 3 — 请求体
Pydantic modelPydantic modelPydantic 模型
The JSON sent by the client, usually with POST / PUT / PATCH. Declared as a Pydantic model. FastAPI parses the JSON, checks every field and gives you a typed Python object.
Client का भेजा JSON, आम तौर पर POST / PUT / PATCH के साथ। Pydantic model से घोषित। FastAPI JSON parse करता है, हर field जाँचता है और आपको typed Python object देता है।
客户端发送的 JSON,通常用于 POST / PUT / PATCH。用 Pydantic 模型声明。FastAPI 解析 JSON、检查每个字段,并交给你一个带类型的 Python 对象。
🔑
How FastAPI DecidesFastAPI कैसे तय करता हैFastAPI 如何判断

Name appears in the path → path parameter. Type is a Pydantic model → request body. Type is a simple value (int, str, float, bool, Enum, UUID, date…) → query parameter. Anything else — like list[str] or dict — is treated as body unless you mark it with Query(), Header() or Cookie().

नाम path में है → path parameter। Type Pydantic model है → request body। Type सरल value है (int, str, float, bool, Enum, UUID, date…) → query parameter। बाक़ी कुछ भी — जैसे list[str] या dict — body माना जाता है, जब तक आप उसे Query(), Header() या Cookie() से चिह्नित न करें।

名字出现在路径中 → 路径参数。类型是 Pydantic 模型 → 请求体。类型是简单值(int、str、float、bool、Enum、UUID、date…)→ 查询参数。其他任何类型 — 比如 list[str] 或 dict — 都会被当作 请求体,除非你用 Query()、Header() 或 Cookie() 标注。

🔧
Fixed: The "Else → Query" Rule Misses Listsसुधार: "बाक़ी → Query" नियम Lists भूल जाता है已修正:"否则 → 查询参数"的规则漏掉了列表

The original rule said every argument that is not in the path and not a Pydantic model becomes a query parameter. That is not true for list[str]: without Query(), FastAPI expects it in the request body, and ?tags=a&tags=b fails with a 422 "body: Field required". Try signature 4 and 5 in the simulator below.

मूल नियम में कहा गया कि path में न होने वाला और Pydantic model न होने वाला हर argument query parameter बनता है। list[str] के लिए यह सच नहीं: Query() के बिना FastAPI उसे request body में ढूँढता है, और ?tags=a&tags=b 422 "body: Field required" से fail होता है। नीचे simulator में signature 4 और 5 आज़माइए।

原规则说:既不在路径中、也不是 Pydantic 模型的参数都会成为查询参数。对 list[str] 来说并非如此:没有 Query() 时,FastAPI 会到请求体里找它,?tags=a&tags=b 会以 422"body: Field required"失败。请在下面的模拟器中试试第 4 和第 5 个签名。

🧭 Simulator: Where Does Each Argument Come From?🧭 Simulator: हर Argument कहाँ से आता है?🧭 模拟器:每个参数从哪里来?

Pick a function signature. The coloured tags come from FastAPI's own OpenAPI schema for that endpoint, and the request below was really sent.

एक function signature चुनिए। रंगीन tags उस endpoint के लिए FastAPI के अपने OpenAPI schema से आते हैं, और नीचे की request सच में भेजी गई।

选择一个函数签名。彩色标签来自 FastAPI 为该接口生成的 OpenAPI 模式,下面的请求也是真实发送的。

Where Does Each Argument Come From?हर Argument कहाँ से आता है?每个参数从哪里来?7 real endpoints7 असली endpoints7 个真实接口

SectionSection第 04 节

Type Hints — The Heart of FastAPI ❤️Type Hints — FastAPI का दिल ❤️类型提示 — FastAPI 的核心 ❤️

The Type Hint ContractType Hint का समझौता类型提示契约
Think of type hints as a signed contract between you and every caller of your API. You declare: "I need an integer here, a short string there, a real email address and a positive price." FastAPI enforces the contract on every request. If a client sends item_id=abc when you asked for int, FastAPI never calls your function — it returns a clear 422 error saying exactly what went wrong.
Type hints को आपके और आपके API के हर caller के बीच हस्ताक्षर किया समझौता मानिए। आप घोषित करते हैं: "यहाँ integer चाहिए, वहाँ छोटी string, एक असली email address और positive price।" FastAPI हर request पर समझौता लागू करता है। अगर client int की जगह item_id=abc भेजे, तो FastAPI आपका function बुलाता ही नहीं — यह साफ़ 422 error लौटाता है जो ठीक बताता है क्या ग़लत हुआ।
把类型提示想象成你与 API 每位调用者之间签下的合同。你声明:"这里要整数,那里要一个短字符串,一个真实的邮箱地址,一个正数价格。"FastAPI 在每个请求上执行这份合同。如果你要的是 int,客户端却发来 item_id=abc,FastAPI 根本不会调用你的函数 — 而是返回一个清晰的 422 错误,准确说明哪里出错。

The Same Function — Step by Step More Typedवही Function — कदम-दर-कदम ज़्यादा Typed同一个函数 — 逐步加上类型

from typing import Annotated
from fastapi import FastAPI, Path, Query

app = FastAPI()

# Level 0 - no hints: item_id arrives as a plain str
@app.get("/v0/items/{item_id}")
def get_item_v0(item_id):
    return {"id": item_id, "type": type(item_id).__name__}

# Level 1 - a type hint: FastAPI now converts AND validates
@app.get("/v1/items/{item_id}")
def get_item_v1(item_id: int):
    return {"id": item_id, "type": type(item_id).__name__}

# Level 2 - constraints with Path and Query
@app.get("/v2/items/{item_id}")
def get_item_v2(
    item_id: Annotated[int, Path(gt=0, le=1000)],
    q: Annotated[str | None, Query(max_length=10)] = None,
):
    return {"id": item_id, "q": q}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.get("/v0/items/42"))
show(client.get("/v1/items/42"))
show(client.get("/v1/items/abc"))
show(client.get("/v2/items/0"))
show(client.get("/v2/items/7?q=far-too-long-text"))
OUTPUT
GET /v0/items/42 -> 200 {'id': '42', 'type': 'str'} GET /v1/items/42 -> 200 {'id': 42, 'type': 'int'} GET /v1/items/abc -> 422 ['path.item_id: Input should be a valid integer, unable to parse string as an integer'] GET /v2/items/0 -> 422 ['path.item_id: Input should be greater than 0'] GET /v2/items/7?q=far-too-long-text -> 422 ['query.q: String should have at most 10 characters']

Level 0 handed you the text "42". Level 1 converted it to the number 42 and refused "abc". Level 2 also enforced limits — all before your function ran.

Level 0 ने आपको text "42" दिया। Level 1 ने उसे संख्या 42 में बदला और "abc" को ठुकराया। Level 2 ने सीमाएँ भी लागू कीं — सब आपका function चलने से पहले।

第 0 级给你的是文本 "42"。第 1 级把它转换成数字 42,并拒绝了 "abc"。第 2 级还强制了范围限制 — 全都发生在你的函数运行之前。

🧪 Simulator: Validation Playground🧪 Simulator: Validation Playground🧪 模拟器:校验游乐场

Pick a type and an input. Each result is FastAPI's real answer to GET /check?v=… — including a few surprises.

एक type और एक input चुनिए। हर result GET /check?v=… पर FastAPI का असली जवाब है — कुछ हैरानियों के साथ।

选择一种类型和一个输入。每个结果都是 FastAPI 对 GET /check?v=… 的真实回答 — 其中有几个出人意料。

Validation PlaygroundValidation Playground校验游乐场31 real requests31 असली requests31 个真实请求
inputinput输入
🔧
Fixed: The Accept / Reject Table, Checked by Real Runsसुधार: Accept / Reject Table, असली Runs से जाँची गई已修正:用真实运行核对了"接受 / 拒绝"表

Some details of the original table were wrong or missing. int also accepts "42.0" and spaces around the number. bool also accepts on/off. float accepts "nan" — and then the response crashes with a 500, because NaN cannot be written as JSON (add allow_inf_nan=False to stop it). date rejects impossible dates like 2026-02-30. EmailStr needs an extra package: pip install "pydantic[email]".

मूल table के कुछ विवरण ग़लत या अधूरे थे। int "42.0" और संख्या के आसपास spaces भी स्वीकार करता है। bool on/off भी लेता है। float "nan" स्वीकार करता है — और फिर response 500 से crash होता है, क्योंकि NaN JSON में लिखा नहीं जा सकता (इसे रोकने के लिए allow_inf_nan=False जोड़िए)। date 2026-02-30 जैसी असंभव तारीख़ें ठुकराता है। EmailStr को अतिरिक्त package चाहिए: pip install "pydantic[email]"।

原表的一些细节有误或缺失。int 还接受 "42.0" 以及数字两边的空格。bool 还接受 on/off。float 会接受 "nan" — 然后响应 以 500 崩溃,因为 NaN 无法写成 JSON(加上 allow_inf_nan=False 可以阻止)。date 会拒绝 2026-02-30 这样不存在的日期。EmailStr 需要额外的包:pip install "pydantic[email]"。


SectionSection第 05 节

Visual Diagram — The Request Lifecycle 🔄चित्र — Request का जीवन-चक्र 🔄图解 — 请求生命周期 🔄

Every HTTP request travels through the same pipeline. Knowing each stage helps you put middleware, dependencies and error handling in the right place.

हर HTTP request एक ही pipeline से गुज़रती है। हर stage जानने से आप middleware, dependencies और error handling सही जगह रख पाते हैं।

每个 HTTP 请求都经过同一条流水线。了解每个阶段,有助于你把中间件、依赖和错误处理放在正确的位置。

01
HTTP request arrives at UvicornHTTP request Uvicorn पर पहुँचती हैHTTP 请求到达 Uvicorn
The ASGI server (Uvicorn / Hypercorn) accepts the connection and parses the method, path, headers and body. Uvicorn uses the fast httptools C parser when it is installed, otherwise the pure-Python h11.ASGI server (Uvicorn / Hypercorn) connection स्वीकार करता है और method, path, headers और body parse करता है। Installed हो तो Uvicorn तेज़ httptools C parser लेता है, वरना pure-Python h11।ASGI 服务器(Uvicorn / Hypercorn)接受连接,解析方法、路径、请求头和请求体。安装了 httptools 时 Uvicorn 使用这个快速的 C 解析器,否则使用纯 Python 的 h11。
02
Middleware chain (outer layers)Middleware chain (बाहरी परतें)中间件链(外层)
CORS, GZip, logging, timing… Each middleware can answer early or change the request. The middleware added last is the outermost, so it runs first (Section 13 shows this).CORS, GZip, logging, timing… हर middleware जल्दी जवाब दे सकता है या request बदल सकता है। सबसे आख़िर में जोड़ा middleware सबसे बाहर होता है, इसलिए वह पहले चलता है (Section 13 यह दिखाता है)।CORS、GZip、日志、计时……每个中间件都可以提前响应或修改请求。最后添加的中间件位于最外层,所以它最先运行(第 13 节会演示)。
03
Route matchingRoute matching路由匹配
The router finds the endpoint whose method + path template match. No path matches → 404. Path matches but the method does not → 405. Path parameters are extracted here.Router वह endpoint ढूँढता है जिसका method + path template मेल खाए। कोई path न मिले → 404। Path मिले पर method नहीं → 405। Path parameters यहीं निकाले जाते हैं।路由器寻找方法和路径模板都匹配的接口。没有路径匹配 → 404。路径匹配但方法不对 → 405。路径参数在这里提取。
04
DependenciesDependencies依赖
FastAPI runs your Depends(...) tree — DB sessions, auth checks, settings — caching each result for the request. Any dependency can raise HTTPException to stop.FastAPI आपका Depends(...) tree चलाता है — DB sessions, auth checks, settings — हर result request के लिए cache करते हुए। कोई भी dependency रुकने के लिए HTTPException raise कर सकती है।FastAPI 运行你的 Depends(...) 依赖树 — 数据库会话、认证检查、配置 — 并在本次请求内缓存每个结果。任何依赖都可以抛出 HTTPException 来中止。
05
Validation of the inputsInputs का validation输入校验
Path, query, header, cookie and body values are checked against their type hints. Any error → 422 with a detailed JSON list. Your endpoint is not called.Path, query, header, cookie और body values उनके type hints से जाँचे जाते हैं। कोई error → विस्तृत JSON सूची के साथ 422। आपका endpoint नहीं बुलाया जाता।路径、查询、请求头、Cookie 和请求体的值都会按类型提示检查。有任何错误 → 返回带详细 JSON 列表的 422。你的接口不会被调用。
06
Your endpoint function runsआपका endpoint function चलता है你的接口函数运行
def endpoints run in a thread pool; async def endpoints run on the event loop. You get typed Python objects to work with.def endpoints thread pool में चलते हैं; async def endpoints event loop पर। आपको काम के लिए typed Python objects मिलते हैं।def 接口在线程池中运行;async def 接口在事件循环上运行。你拿到的是带类型的 Python 对象。
07
Response serializationResponse serialization响应序列化
Your return value is checked and filtered by response_model (if set), turned into JSON, and passes back out through the middleware.आपकी return value response_model (अगर लगा हो) से जाँची और छानी जाती है, JSON में बदली जाती है, और middleware से होकर बाहर जाती है।你的返回值会经 response_model(若已设置)检查和过滤,转换成 JSON,再经过中间件返回。
🔧
Fixed: Three Details of the Pipeline Correctedसुधार: Pipeline के तीन विवरण सुधारे गए已修正:修正了流水线的三处细节

(1) HTTP parsing is "pure C" only when httptools is installed; otherwise Uvicorn uses the pure-Python h11. (2) Middleware does not run in registration order: the last one added runs first. (3) Dependencies and validation happen in one step, and dependencies run first: in the simulator, a bad item_id still ran the auth dependency before the 422 was returned.

(1) HTTP parsing तभी "pure C" है जब httptools installed हो; वरना Uvicorn pure-Python h11 लेता है। (2) Middleware registration के क्रम में नहीं चलता: आख़िर में जोड़ा गया पहले चलता है। (3) Dependencies और validation एक ही step में होते हैं, और dependencies पहले चलती हैं: simulator में ग़लत item_id पर भी 422 लौटने से पहले auth dependency चली।

(1) 只有安装了 httptools 时,HTTP 解析才是"纯 C";否则 Uvicorn 使用纯 Python 的 h11。(2) 中间件 不是 按注册顺序运行的:最后添加的最先运行。(3) 依赖和校验在同一步中进行,而且依赖先运行:在模拟器中,即使 item_id 有误,认证依赖也在返回 422 之前就运行了。

🔄 Simulator: Request Lifecycle🔄 Simulator: Request Lifecycle🔄 模拟器:请求生命周期

One small app with a middleware, an auth dependency and a response_model. Pick a request and watch how far it gets. The log on the right is what the app really recorded.

एक छोटा app जिसमें एक middleware, एक auth dependency और एक response_model है। एक request चुनिए और देखिए वह कहाँ तक पहुँचती है। दाईं ओर का log वही है जो app ने सच में दर्ज किया।

一个小应用,带一个中间件、一个认证依赖和一个 response_model。选择一个请求,看看它能走多远。右侧的日志是应用真实记录的内容。

Request LifecycleRequest Lifecycle请求生命周期6 real requests6 असली requests6 个真实请求
app logapp log应用日志
responseresponse响应

SectionSection第 06 节

HTTP Methods — CRUD in FastAPI 🛠️HTTP Methods — FastAPI में CRUD 🛠️HTTP 方法 — FastAPI 中的 CRUD 🛠️

A REST API maps HTTP methods to data operations: Create, Read, Update, Delete (CRUD). FastAPI has one decorator per method.

REST API HTTP methods को data operations से जोड़ता है: Create, Read, Update, Delete (CRUD)। FastAPI में हर method के लिए एक decorator है।

REST API 把 HTTP 方法映射到数据操作:创建、读取、更新、删除(CRUD)。FastAPI 为每种方法提供一个装饰器。

HTTP methodHTTP methodHTTP 方法DecoratorDecorator装饰器CRUDTypical purposeआम उद्देश्य典型用途Body?Body?请求体?
GET@app.get()Readपढ़ना读取Fetch one resource or a listएक resource या सूची लाना获取单个资源或列表Noनहीं否
POST@app.post()Createबनाना创建Create a new resourceनया resource बनाना创建新资源Yesहाँ是
PUT@app.put()Update (full)बदलना (पूरा)更新(整体)Replace a whole resourceपूरा resource बदलना替换整个资源Yesहाँ是
PATCH@app.patch()Update (part)बदलना (हिस्सा)更新(部分)Change some fieldsकुछ fields बदलना修改部分字段Yesहाँ是
DELETE@app.delete()Deleteमिटाना删除Remove a resourceResource हटाना删除资源Rarelyकभी-कभार很少

Complete CRUD API — In-Memory Item Storeपूरा CRUD API — In-Memory Item Store完整的 CRUD API — 内存商品库

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from uuid import UUID, uuid4

app = FastAPI(title="Item API", version="1.0.0")

# -- Models ---------------------------------------------
class ItemIn(BaseModel):
    name:  str   = Field(min_length=1, max_length=80)
    price: float = Field(gt=0)
    stock: int   = Field(default=0, ge=0)

class ItemOut(ItemIn):
    id: UUID

# -- Fake in-memory DB ----------------------------------
DB: dict[UUID, ItemOut] = {}

# -- CREATE ---------------------------------------------
@app.post("/items", response_model=ItemOut, status_code=status.HTTP_201_CREATED)
def create_item(payload: ItemIn):
    new = ItemOut(id=uuid4(), **payload.model_dump())
    DB[new.id] = new
    return new

# -- READ (list) ----------------------------------------
@app.get("/items", response_model=list[ItemOut])
def list_items(skip: int = 0, limit: int = 20):
    return list(DB.values())[skip : skip + limit]

# -- READ (one) -----------------------------------------
@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: UUID):
    if item_id not in DB:
        raise HTTPException(404, "Item not found")
    return DB[item_id]

# -- UPDATE ---------------------------------------------
@app.put("/items/{item_id}", response_model=ItemOut)
def update_item(item_id: UUID, payload: ItemIn):
    if item_id not in DB:
        raise HTTPException(404, "Item not found")
    DB[item_id] = ItemOut(id=item_id, **payload.model_dump())
    return DB[item_id]

# -- DELETE ---------------------------------------------
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: UUID):
    if DB.pop(item_id, None) is None:
        raise HTTPException(404, "Item not found")

Trying it with real requestsअसली requests से आज़माना用真实请求试一试

OUTPUT
POST /items -> 201 {'name': 'Notebook', 'price': 2.99, 'stock': 100, 'id': '37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2'} GET /items -> 200 [{'name': 'Notebook', 'price': 2.99, 'stock': 100, 'id': '37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2'}] PUT /items/37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2 -> 200 {'name': 'Notebook A5', 'price': 3.49, 'stock': 80, 'id': '37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2'} DELETE /items/37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2 -> 204 (empty) GET /items/37d6ff17-7cfb-43e9-9596-5efc3e4ba2e2 -> 404 {'detail': 'Item not found'} POST /items -> 422 ['body.name: String should have at least 1 character', 'body.price: Input should be greater than 0']

Notice DELETE answered 204 with an empty body, and the bad POST listed both problems at once. The ids are random uuid4() values, so they change on every run.

देखिए DELETE ने ख़ाली body के साथ 204 दिया, और ग़लत POST ने दोनों समस्याएँ एक साथ बताईं। ids random uuid4() values हैं, इसलिए हर run पर बदलते हैं।

注意 DELETE 返回了空响应体的 204,而错误的 POST 一次列出了 两个 问题。id 是随机的 uuid4() 值,每次运行都会变。

🗃️ Simulator: CRUD Lab🗃️ Simulator: CRUD Lab🗃️ 模拟器:CRUD 实验室

Step through ten real requests to the Item API above. The table on the right is the in-memory DB after each request.

ऊपर के Item API पर दस असली requests step-by-step देखिए। दाईं ओर की table हर request के बाद in-memory DB है।

逐步查看发给上面 Item API 的十个真实请求。右侧表格是每个请求之后的内存 DB。

CRUD LabCRUD LabCRUD 实验室10 real requests10 असली requests10 个真实请求
responseresponse响应
DB after this requestइस request के बाद DB本次请求后的 DB

SectionSection第 07 节

Path and Query Parameters — Deep Dive 🔬Path और Query Parameters — गहराई से 🔬路径参数与查询参数 — 深入 🔬

Path parameter validationPath parameter validation路径参数校验

from enum import Enum
from typing import Annotated
from fastapi import FastAPI, Path

app = FastAPI()

class Category(str, Enum):
    electronics = "electronics"
    books       = "books"
    clothing    = "clothing"

@app.get("/products/{category}/{product_id}")
def get_product(
    category:   Category,                                  # only 3 allowed values
    product_id: Annotated[int, Path(ge=1, le=999999,      # must be 1..999999
                                    title="Product ID",
                                    description="Unique product identifier")],
):
    return {"category": category, "product_id": product_id}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.get("/products/books/42"))
show(client.get("/products/toys/42"))
show(client.get("/products/books/0"))
OUTPUT
GET /products/books/42 -> 200 {'category': 'books', 'product_id': 42} GET /products/toys/42 -> 422 ["path.category: Input should be 'electronics', 'books' or 'clothing'"] GET /products/books/0 -> 422 ['path.product_id: Input should be greater than or equal to 1']

The Enum limited category to three words — and the error message even lists them. We switched the original Path(..., ge=1) to the Annotated form, to match the advice below.

Enum ने category को तीन शब्दों तक सीमित किया — और error message उन्हें गिनाता भी है। नीचे की सलाह से मेल खाने के लिए हमने मूल Path(..., ge=1) को Annotated रूप में बदला।

Enum 把 category 限定为三个词 — 错误信息甚至把它们都列了出来。我们把原来的 Path(..., ge=1) 改成了 Annotated 写法,与下面的建议保持一致。

Query parameter patternsQuery parameter patterns查询参数的常见写法

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/search")
def search(
    # Required query param
    q:       Annotated[str,  Query(min_length=3, max_length=50)],
    # Optional with default
    limit:   Annotated[int,  Query(ge=1, le=100)] = 10,
    # Boolean flag
    exact:   Annotated[bool, Query()] = False,
    # List - accept ?tag=a&tag=b&tag=c
    tag:     Annotated[list[str] | None, Query()] = None,
    # Regex validation
    sort_by: Annotated[str, Query(pattern="^(name|price|date)$")] = "name",
):
    return {"q": q, "limit": limit, "exact": exact,
            "tag": tag or [], "sort_by": sort_by}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.get("/search?q=laptop"))
show(client.get("/search?q=laptop&limit=5&exact=yes&tag=new&tag=sale&sort_by=price"))
show(client.get("/search?q=tv&limit=500&sort_by=rating"))
show(client.get("/search"))
OUTPUT
GET /search?q=laptop -> 200 {'q': 'laptop', 'limit': 10, 'exact': False, 'tag': [], 'sort_by': 'name'} GET /search?q=laptop&limit=5&exact=yes&tag=new&tag=sale&sort_by=price -> 200 {'q': 'laptop', 'limit': 5, 'exact': True, 'tag': ['new', 'sale'], 'sort_by': 'price'} GET /search?q=tv&limit=500&sort_by=rating -> 422 ['query.q: String should have at least 3 characters', 'query.limit: Input should be less than or equal to 100', "query.sort_by: String should match pattern '^(name|price|date)$'"] GET /search -> 422 ['query.q: Field required']

exact=yes became True, and two tag= values became a list. The third request broke three rules at once, and FastAPI reported all three.

exact=yes True बना, और दो tag= values एक list बनीं। तीसरी request ने एक साथ तीन नियम तोड़े, और FastAPI ने तीनों बताए।

exact=yes 变成了 True,两个 tag= 值变成了列表。第三个请求同时违反了三条规则,FastAPI 把三条都报告了出来。

💬
Prefer Annotated — The Modern WayAnnotated लीजिए — आधुनिक तरीका优先使用 Annotated — 现代写法

Since FastAPI 0.95, Annotated[Type, Query(...)] is the recommended style instead of x: str = Query(...). It keeps the type and the extra rules apart, works well with type checkers, and lets you reuse one annotation in many endpoints.

FastAPI 0.95 से x: str = Query(...) की जगह Annotated[Type, Query(...)] सुझाया गया तरीका है। यह type और अतिरिक्त नियमों को अलग रखता है, type checkers के साथ अच्छा चलता है, और एक annotation को कई endpoints में दोबारा इस्तेमाल करने देता है।

自 FastAPI 0.95 起,推荐用 Annotated[Type, Query(...)] 代替 x: str = Query(...)。它把类型和额外规则分开,与类型检查器配合良好,还能在多个接口中复用同一个注解。


SectionSection第 08 节

Request Body with Pydantic Models 📦Pydantic Models के साथ Request Body 📦使用 Pydantic 模型的请求体 📦

Pydantic is FastAPI's validation engine. Declare a class that inherits from BaseModel, and every incoming JSON body is parsed, checked and turned into a typed Python object.

Pydantic FastAPI का validation engine है। BaseModel से बनी class घोषित कीजिए, और हर आने वाली JSON body parse, जाँची और typed Python object में बदली जाती है।

Pydantic 是 FastAPI 的校验引擎。声明一个继承自 BaseModel 的类,每个传入的 JSON 请求体都会被解析、检查,并变成带类型的 Python 对象。

from datetime import date
from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr, HttpUrl, field_validator

app = FastAPI()

class Address(BaseModel):
    street:      str
    city:        str
    postal_code: str = Field(pattern=r"^\d{5,6}$")
    country:     str = "IN"

class UserRegistration(BaseModel):
    username:   str = Field(min_length=3, max_length=20)
    email:      EmailStr                 # needs: pip install "pydantic[email]"
    password:   str = Field(min_length=8)
    birth_date: date
    website:    HttpUrl | None = None
    address:    Address                  # nested model
    tags:       list[str] = []

    @field_validator("password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if not any(c.isupper() for c in v):
            raise ValueError("Password needs at least one uppercase letter")
        if not any(c.isdigit() for c in v):
            raise ValueError("Password needs at least one digit")
        return v

@app.post("/register")
def register(user: UserRegistration):
    # user is fully validated by the time we get here
    return {"status": "created", "user": user.model_dump(exclude={"password"})}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

good = {"username": "mohit", "email": "mohit@example.com", "password": "Secret123",
        "birth_date": "1990-07-18", "address": {"street": "1 Mall Rd", "city": "Baddi",
        "postal_code": "173205"}}
show(client.post("/register", json=good))
bad = {"username": "a", "email": "not-an-email", "password": "weak",
       "birth_date": "32-13-2020", "address": {"street": "x", "city": "y", "postal_code": "12"}}
show(client.post("/register", json=bad))
show(client.post("/register", json={**good, "password": "longenough"}))
OUTPUT
POST /register -> 200 {'status': 'created', 'user': {'username': 'mohit', 'email': 'mohit@example.com', 'birth_date': '1990-07-18', 'website': None, 'address': {'street': '1 Mall Rd', 'city': 'Baddi', 'postal_code': '173205', 'country': 'IN'}, 'tags': []}} POST /register -> 422 ['body.username: String should have at least 3 characters', 'body.email: value is not a valid email address: An email address must have an @-sign.', 'body.password: String should have at least 8 characters', 'body.birth_date: Input should be a valid date or datetime, invalid character in year', "body.address.postal_code: String should match pattern '^\\d{5,6}$'"] POST /register -> 422 ['body.password: Value error, Password needs at least one uppercase letter']

What happens on invalid inputग़लत input पर क्या होता है输入无效时会发生什么

❌ Bad request bodyग़लत request body错误的请求体
FieldField字段Sentभेजा发送值
username"a"
email"not-an-email"
password"weak"
birth_date"32-13-2020"
address.postal_code"12"
✅ Real 422 response (all at once)असली 422 response (सब एक साथ)真实的 422 响应(一次全部返回)
FieldField字段MessageMessage信息
usernameString should have at least 3 characters
emailvalue is not a valid email address: An email address must have an @-sign.
passwordString should have at least 8 characters
birth_dateInput should be a valid date or datetime…
address.postal_codeString should match pattern '^\d{5,6}$'
🔧
Fixed: "weak" Never Reaches the Password Validatorसुधार: "weak" कभी Password Validator तक नहीं पहुँचता已修正:"weak" 根本到不了密码校验器

The original said the error for "weak" would be "Needs uppercase + digit". In the real run it is String should have at least 8 characters: the min_length=8 rule fails first, so the custom @field_validator never runs. Your validator only sees values that already passed the built-in rules — the third request shows it working on "longenough".

मूल में कहा गया कि "weak" का error "Needs uppercase + digit" होगा। असली run में यह String should have at least 8 characters है: min_length=8 नियम पहले fail होता है, इसलिए custom @field_validator कभी नहीं चलता। आपका validator सिर्फ़ वे values देखता है जो built-in नियम पहले ही पास कर चुकी हों — तीसरी request "longenough" पर इसे चलता दिखाती है।

原文说 "weak" 的错误会是"需要大写字母和数字"。真实运行中却是 String should have at least 8 characters:min_length=8 规则先失败,所以自定义的 @field_validator 根本不会运行。你的校验器只会看到已通过内置规则的值 — 第三个请求展示了它对 "longenough" 生效的情况。


SectionSection第 09 节

Response Models and Status Codes 🎯Response Models और Status Codes 🎯响应模型与状态码 🎯

A response_model gives three things at once: it checks your output, filters out fields that should not leave the server, and documents the response.

response_model एक साथ तीन चीज़ें देता है: आपका output जाँचता है, server से बाहर न जाने वाले fields छाँटता है, और response का दस्तावेज़ बनाता है।

response_model 同时带来三件事:检查你的输出、过滤掉不应离开服务器的字段,并为响应生成文档。

from fastapi import FastAPI, status
from pydantic import BaseModel, EmailStr

app = FastAPI()

# Two versions: one with the password, one without
class UserIn(BaseModel):
    username: str
    email:    EmailStr
    password: str                 # client sends this

class UserOut(BaseModel):
    username: str
    email:    EmailStr            # NO password field

@app.post("/users",
          response_model=UserOut,
          status_code=status.HTTP_201_CREATED,
          summary="Register a new user",
          tags=["users"])
def create_user(user: UserIn) -> UserIn:
    # Return the whole UserIn (with password) - FastAPI filters it
    # out because response_model=UserOut has no password field.
    return user

@app.post("/users-unsafe", status_code=201)
def create_user_unsafe(user: UserIn):
    return user                   # no response_model -> everything goes out

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.post("/users", json={"username": "ada", "email": "ada@x.io", "password": "S3cret!"}))
show(client.post("/users-unsafe", json={"username": "ada", "email": "ada@x.io", "password": "S3cret!"}))
OUTPUT
POST /users -> 201 {'username': 'ada', 'email': 'ada@x.io'} POST /users-unsafe -> 201 {'username': 'ada', 'email': 'ada@x.io', 'password': 'S3cret!'}

Same input, two endpoints. With response_model=UserOut the password was removed; without it, the password went straight back to the client.

एक ही input, दो endpoints। response_model=UserOut के साथ password हट गया; उसके बिना password सीधे client को वापस चला गया।

同样的输入,两个接口。有 response_model=UserOut 时密码被去掉了;没有它,密码就直接返回给了客户端。

Status codeStatus code状态码ConstantConstant常量When to useकब लें何时使用
200HTTP_200_OKDefault for a successful GET / PUT / PATCHसफल GET / PUT / PATCH का default成功的 GET / PUT / PATCH 的默认值
201HTTP_201_CREATEDA POST that created somethingकुछ बनाने वाला POST创建了资源的 POST
204HTTP_204_NO_CONTENTSuccess with no body (often DELETE)बिना body सफलता (अक्सर DELETE)成功但没有响应体(常用于 DELETE)
400HTTP_400_BAD_REQUESTA business rule the client can fixClient द्वारा सुधारने लायक business नियम客户端可以修正的业务规则错误
401HTTP_401_UNAUTHORIZEDMissing or invalid credentialsCredentials ग़ायब या ग़लत凭据缺失或无效
403HTTP_403_FORBIDDENLogged in, but not allowedLogin है, पर अनुमति नहीं已登录但无权限
404HTTP_404_NOT_FOUNDThe resource does not existResource मौजूद नहीं资源不存在
422HTTP_422_UNPROCESSABLE_CONTENTValidation failed (FastAPI does this for you)Validation fail (FastAPI आपके लिए करता है)校验失败(FastAPI 自动处理)
500HTTP_500_INTERNAL_SERVER_ERRORAn unhandled error in your codeआपके code में न संभाला error代码中未处理的异常
🔧
Fixed: HTTP_422_UNPROCESSABLE_ENTITY Is Deprecatedसुधार: HTTP_422_UNPROCESSABLE_ENTITY Deprecated है已修正:HTTP_422_UNPROCESSABLE_ENTITY 已弃用

The current HTTP standard (RFC 9110) calls 422 "Unprocessable Content", and recent Starlette versions warn: 'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. The number 422 is the same.

मौजूदा HTTP मानक (RFC 9110) 422 को "Unprocessable Content" कहता है, और नए Starlette versions चेतावनी देते हैं: 'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. संख्या 422 वही है।

当前的 HTTP 标准(RFC 9110)把 422 称为"Unprocessable Content",较新的 Starlette 版本会警告:'HTTP_422_UNPROCESSABLE_ENTITY' is deprecated. Use 'HTTP_422_UNPROCESSABLE_CONTENT' instead. 数字 422 本身不变。


SectionSection第 10 节

Dependency Injection — FastAPI's Secret Weapon 🧩Dependency Injection — FastAPI का गुप्त हथियार 🧩依赖注入 — FastAPI 的秘密武器 🧩

The Restaurant KitchenRestaurant की Kitchen餐厅的厨房
A good chef does not wash the vegetables, chop the onions and make the stock every time they cook a dish. Prep cooks do those steps, and the chef simply says what they need. FastAPI's Depends() works the same way: your endpoint says "I need a database session, the current user and the page settings", and FastAPI prepares them before your function starts.
अच्छा chef हर dish पकाते समय सब्ज़ियाँ नहीं धोता, प्याज़ नहीं काटता और stock नहीं बनाता। Prep cooks ये काम करते हैं, और chef बस बताता है कि उसे क्या चाहिए। FastAPI का Depends() भी ऐसे ही काम करता है: आपका endpoint कहता है "मुझे database session, मौजूदा user और page settings चाहिए", और FastAPI आपका function शुरू होने से पहले उन्हें तैयार कर देता है।
好厨师不会在每次做菜时都亲自洗菜、切洋葱、熬高汤。备菜厨师负责这些步骤,主厨只需 说出 需要什么。FastAPI 的 Depends() 也是这样:你的接口说"我需要数据库会话、当前用户和分页设置",FastAPI 就会在你的函数开始前把它们准备好。
Figure 2 — A Dependency Treeचित्र 2 — एक Dependency Tree图 2 — 依赖树
Dependency treelist_orders()get_paginationget_current_userget_auditget_db (yield)verify_api_keyalso needs it → reused, not run twiceइसे भी चाहिए → दोबारा इस्तेमाल, दो बार नहीं चलता也需要它 → 复用结果,不会运行两次FastAPI solves the tree from the leaves up, once per request.FastAPI tree को पत्तियों से ऊपर तक, हर request पर एक बार हल करता है।FastAPI 自下而上解析依赖树,每个请求一次。

Shared dependencies are solved once and reused.

साझा dependencies एक बार हल होकर दोबारा इस्तेमाल होती हैं।

共享的依赖只解析一次并被复用。

from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()
CALLS = {"get_current_user": 0}

# -- A simple dependency -------------------------------
def get_pagination(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": min(limit, 100)}

# -- A dependency with a sub-dependency ----------------
def verify_api_key(x_api_key: Annotated[str, Header()]):     # reads header X-Api-Key
    if x_api_key != "secret-key-123":
        raise HTTPException(401, "Invalid API key")
    return x_api_key

def get_current_user(api_key: Annotated[str, Depends(verify_api_key)]):
    CALLS["get_current_user"] += 1                # count how often it really runs
    return {"id": "u_42", "name": "Alice"}

def get_audit(user: Annotated[dict, Depends(get_current_user)]):
    return f"audit for {user['name']}"            # ALSO needs the current user

# -- Yield-based dependency (setup + cleanup) ----------
def get_db():
    db = {"conn": "fake-db-session"}              # real app: SessionLocal()
    print("  db: opened")
    try:
        yield db                                  # handed to the endpoint
    finally:
        print("  db: closed")                     # always runs, even on error

# -- Consuming all of them ------------------------------
@app.get("/orders")
def list_orders(
    pagination: Annotated[dict, Depends(get_pagination)],
    user:       Annotated[dict, Depends(get_current_user)],
    audit:      Annotated[str,  Depends(get_audit)],
    db:         Annotated[dict, Depends(get_db)],
):
    return {"user": user["name"], "page": pagination, "audit": audit}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.get("/orders?limit=500", headers={"X-Api-Key": "secret-key-123"}))
print("get_current_user ran", CALLS["get_current_user"], "time(s) for that request")
show(client.get("/orders", headers={"X-Api-Key": "wrong"}))
show(client.get("/orders"))
OUTPUT
db: opened db: closed GET /orders?limit=500 -> 200 {'user': 'Alice', 'page': {'skip': 0, 'limit': 100}, 'audit': 'audit for Alice'} get_current_user ran 1 time(s) for that request GET /orders -> 401 {'detail': 'Invalid API key'} db: opened db: closed GET /orders -> 422 ['header.x-api-key: Field required', 'header.x-api-key: Field required']
⚡
Dependencies Are Cached per RequestDependencies हर Request पर Cache होती हैं依赖按请求缓存

Both list_orders and get_audit need get_current_user — yet it ran once for that request. FastAPI reuses the result within one request, so a tree of dependencies does not repeat the same database lookup. (Use Depends(fn, use_cache=False) if you really need a fresh call.)

list_orders और get_audit दोनों को get_current_user चाहिए — फिर भी वह उस request के लिए एक बार चली। FastAPI एक request में result दोबारा इस्तेमाल करता है, इसलिए dependencies का tree वही database lookup नहीं दोहराता। (अगर सच में नई call चाहिए तो Depends(fn, use_cache=False) लीजिए।)

list_orders 和 get_audit 都需要 get_current_user — 但它在那次请求中只运行了 一次。FastAPI 在同一请求内复用结果,所以依赖树不会重复同样的数据库查询。(如果确实需要重新调用,用 Depends(fn, use_cache=False)。)

🔧
Fixed: The Original Snippet Could Not Runसुधार: मूल Snippet चल नहीं सकता था已修正:原代码片段无法运行

The original used SessionLocal, Session and app without defining or importing them. Above, a small fake session stands in for the database, and the prints show the yield dependency opening and closing around the request. Notice two more real details: with a wrong key the request stopped at verify_api_key (401) before the database was opened, and with a missing header FastAPI still reported the error for each place the header is needed.

मूल ने SessionLocal, Session और app को define या import किए बिना इस्तेमाल किया। ऊपर एक छोटा नक़ली session database की जगह है, और prints दिखाते हैं कि yield dependency request के आसपास कैसे खुलती और बंद होती है। दो और असली बातें देखिए: ग़लत key पर request database खुलने से पहले verify_api_key (401) पर रुक गई, और header न होने पर FastAPI ने हर उस जगह का error बताया जहाँ header चाहिए।

原文使用了 SessionLocal、Session 和 app,却没有定义或导入它们。上面用一个小小的假会话代替数据库,打印内容展示了 yield 依赖如何在请求前后打开和关闭。还有两个真实细节:密钥错误时,请求在打开数据库之前就停在了 verify_api_key(401);缺少请求头时,FastAPI 会为每个需要该请求头的地方分别报告错误。


SectionSection第 11 节

Authentication with JWT (OAuth2) 🔐JWT (OAuth2) के साथ Authentication 🔐使用 JWT(OAuth2)认证 🔐

FastAPI includes OAuth2 helpers. Combined with PyJWT (to create and check tokens) and pwdlib (to hash passwords with Argon2), you get a solid login flow in about 60 lines. This is the setup the FastAPI documentation uses today.

FastAPI में OAuth2 helpers शामिल हैं। PyJWT (tokens बनाने और जाँचने के लिए) और pwdlib (Argon2 से passwords hash करने के लिए) के साथ लगभग 60 lines में मज़बूत login flow मिलता है। FastAPI documentation आज यही setup लेता है।

FastAPI 自带 OAuth2 辅助工具。配合 PyJWT(创建和校验令牌)和 pwdlib(用 Argon2 哈希密码),大约 60 行就能写出可靠的登录流程。这正是 FastAPI 文档如今采用的方案。

Figure 3 — The JWT Login Flowचित्र 3 — JWT Login Flow图 3 — JWT 登录流程
JWT flowCLIENTCLIENT客户端browser / appbrowser / app浏览器 / 应用POST /tokencheck passwordpassword जाँच校验密码GET /meprotectedसुरक्षित受保护1. username + password2. signed JWT3. Authorization: Bearer eyJhbGci…4. check signature + expiry → {"username": "alice"}header . payload {"sub": "alice", "exp": …} . signaturereadable by anyone — but impossible to change without the secret keyकोई भी पढ़ सकता है — पर secret key के बिना बदलना असंभव任何人都能读 — 但没有密钥就无法篡改

Log in once; send the token with every request.

एक बार login; हर request के साथ token भेजिए।

登录一次;每个请求都带上令牌。

import os
from datetime import datetime, timedelta, timezone
from typing import Annotated

import jwt                                   # pip install pyjwt
from jwt.exceptions import InvalidTokenError
from pwdlib import PasswordHash              # pip install "pwdlib[argon2]"
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-change-me-" + "x" * 32)
ALGORITHM  = "HS256"
TOKEN_TTL  = timedelta(minutes=30)

password_hash = PasswordHash.recommended()   # Argon2
oauth2 = OAuth2PasswordBearer(tokenUrl="/token")
app = FastAPI()

# Fake user DB - real app: query PostgreSQL etc.
USERS = {"alice": {"username": "alice", "hashed_pw": password_hash.hash("wonderland")}}

def authenticate(username: str, password: str):
    user = USERS.get(username)
    if not user or not password_hash.verify(password, user["hashed_pw"]):
        return None
    return user

def create_token(data: dict, ttl: timedelta = TOKEN_TTL) -> str:
    payload = data.copy()
    payload["exp"] = datetime.now(timezone.utc) + ttl
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

@app.post("/token")
def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = authenticate(form.username, form.password)
    if not user:
        raise HTTPException(401, "Wrong username or password")
    return {"access_token": create_token({"sub": user["username"]}), "token_type": "bearer"}

def get_current_user(token: Annotated[str, Depends(oauth2)]):
    creds_exc = HTTPException(status.HTTP_401_UNAUTHORIZED, "Could not validate credentials",
                              headers={"WWW-Authenticate": "Bearer"})
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
    except InvalidTokenError:
        raise creds_exc
    if username is None or username not in USERS:
        raise creds_exc
    return USERS[username]

@app.get("/me")
def read_me(user: Annotated[dict, Depends(get_current_user)]):
    return {"username": user["username"]}

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.post("/token", data={"username": "alice", "password": "nope"}))
r = client.post("/token", data={"username": "alice", "password": "wonderland"})
token = r.json()["access_token"]
print("token:", token[:36] + "...", "| parts:", token.count(".") + 1)
show(client.get("/me", headers={"Authorization": f"Bearer {token}"}))
show(client.get("/me"))
old = create_token({"sub": "alice"}, ttl=timedelta(seconds=-1))   # already expired
show(client.get("/me", headers={"Authorization": f"Bearer {old}"}))
show(client.get("/me", headers={"Authorization": f"Bearer {token[:-3]}abc"}))   # tampered
print("stored hash:", USERS["alice"]["hashed_pw"][:30] + "...")
OUTPUT
POST /token -> 401 {'detail': 'Wrong username or password'} token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | parts: 3 GET /me -> 200 {'username': 'alice'} GET /me -> 401 {'detail': 'Not authenticated'} GET /me -> 401 {'detail': 'Could not validate credentials'} GET /me -> 401 {'detail': 'Could not validate credentials'} stored hash: $argon2id$v=19$m=65536,t=3,p=4...

The run shows the whole story: a wrong password gets 401, the right one gets a three-part token, /me works with it, and three bad cases fail — no token, an expired token, and a token whose last characters were changed.

Run पूरी कहानी दिखाता है: ग़लत password पर 401, सही पर तीन हिस्सों वाला token, उसके साथ /me चलता है, और तीन ग़लत मामले fail होते हैं — कोई token नहीं, expired token, और ऐसा token जिसके आख़िरी अक्षर बदले गए।

这次运行展示了完整过程:密码错误得到 401,密码正确得到三段式令牌,用它访问 /me 成功,三种错误情况都失败了 — 没有令牌、令牌过期、令牌末尾字符被篡改。

🔧
Fixed: python-jose and passlib Replaced — passlib Breaks with bcrypt 5सुधार: python-jose और passlib बदले गए — passlib bcrypt 5 के साथ टूटता है已修正:替换了 python-jose 和 passlib — passlib 与 bcrypt 5 不兼容

The original used python-jose and passlib. Both are barely maintained, and the FastAPI docs moved to PyJWT and pwdlib. With the current bcrypt 5 package, the original passlib code does not even start — here is the real run:

मूल ने python-jose और passlib लिए थे। दोनों का रख-रखाव लगभग बंद है, और FastAPI docs PyJWT और pwdlib पर आ गए। मौजूदा bcrypt 5 package के साथ मूल passlib code शुरू भी नहीं होता — यह असली run है:

原文使用了 python-jose 和 passlib。两者几乎都已无人维护,FastAPI 文档也已改用 PyJWT 和 pwdlib。在当前的 bcrypt 5 版本下,原来的 passlib 代码甚至无法启动 — 下面是真实运行结果:

# The original setup: passlib + bcrypt (bcrypt 5.x installed)
from passlib.context import CryptContext

pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
print(pwd_ctx.hash("wonderland"))
OUTPUT
(trapped) error reading bcrypt version ... (long traceback) ... ValueError: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72])
⚠️
Never Commit Secret KeysSecret Keys कभी Commit न करें永远不要提交密钥

Generate a real key with openssl rand -hex 32 and load it from an environment variable or a secrets manager, as the code above does with os.environ.get("SECRET_KEY"). If the key leaks, anyone can create valid tokens. Also remember: a JWT payload is only signed, not encrypted — never put passwords or private data in it.

openssl rand -hex 32 से असली key बनाइए और उसे environment variable या secrets manager से load कीजिए, जैसा ऊपर का code os.environ.get("SECRET_KEY") से करता है। Key leak हुई तो कोई भी मान्य tokens बना सकता है। यह भी याद रखिए: JWT payload सिर्फ़ signed है, encrypted नहीं — उसमें passwords या निजी data कभी मत रखिए।

用 openssl rand -hex 32 生成真正的密钥,并从环境变量或密钥管理器加载,就像上面代码用 os.environ.get("SECRET_KEY") 那样。密钥一旦泄露,任何人都能伪造有效令牌。还要记住:JWT 的载荷只是 签名,并没有加密 — 千万不要把密码或隐私数据放进去。


SectionSection第 12 节

Database Integration with SQLAlchemy 🗄️SQLAlchemy के साथ Database Integration 🗄️与 SQLAlchemy 集成数据库 🗄️

# file: database.py
from sqlalchemy import create_engine, Integer, String, Float
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker

DATABASE_URL = "sqlite:///./app.db"

engine       = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

class Item(Base):
    __tablename__ = "items"
    id:    Mapped[int]   = mapped_column(Integer, primary_key=True, index=True)
    name:  Mapped[str]   = mapped_column(String, nullable=False)
    price: Mapped[float] = mapped_column(Float, nullable=False)
    stock: Mapped[int]   = mapped_column(Integer, default=0)

Base.metadata.create_all(bind=engine)

# file: main.py
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel, ConfigDict

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:     yield db
    finally: db.close()

DB = Annotated[Session, Depends(get_db)]

class ItemIn(BaseModel):
    name:  str
    price: float
    stock: int = 0

class ItemOut(ItemIn):
    model_config = ConfigDict(from_attributes=True)   # read ORM objects directly
    id: int

@app.post("/items", response_model=ItemOut, status_code=201)
def create(payload: ItemIn, db: DB):
    item = Item(**payload.model_dump())
    db.add(item)
    db.commit()
    db.refresh(item)          # load the new id from the database
    return item

@app.get("/items/{item_id}", response_model=ItemOut)
def read(item_id: int, db: DB):
    item = db.get(Item, item_id)
    if not item:
        raise HTTPException(404, "Item not found")
    return item

# ---- try it (TestClient sends real HTTP requests to the app, no server needed) ----
from fastapi.testclient import TestClient
client = TestClient(app)
def show(r):
    u = r.request.url
    path = u.path + ("?" + u.query.decode() if u.query else "")
    body = r.json() if r.content else "(empty)"
    if r.status_code == 422:
        body = [".".join(map(str, e["loc"])) + ": " + e["msg"] for e in body["detail"]]
    print(f"{r.request.method:6} {path:30} -> {r.status_code} {body}")

show(client.post("/items", json={"name": "Pen", "price": 1.5, "stock": 40}))
show(client.get("/items/1"))
show(client.get("/items/99"))
OUTPUT
POST /items -> 201 {'name': 'Pen', 'price': 1.5, 'stock': 40, 'id': 1} GET /items/1 -> 200 {'name': 'Pen', 'price': 1.5, 'stock': 40, 'id': 1} GET /items/99 -> 404 {'detail': 'Item not found'}
🔧
Fixed: Pydantic v1 "class Config", a Missing id, and the Legacy Query APIसुधार: Pydantic v1 "class Config", ग़ायब id, और पुराना Query API已修正:Pydantic v1 的"class Config"、缺失的 id 和旧式查询 API

(1) The original used class Config: from_attributes = True. That is the Pydantic v1 style; Pydantic 2 warns Support for class-based config is deprecated, use ConfigDict instead. We use model_config = ConfigDict(from_attributes=True). (2) The response schema had no id, so a client could never learn the id of what it just created. We split ItemIn / ItemOut. (3) db.query(...).filter(...).first() is the legacy 1.x API; db.get(Item, item_id) is the SQLAlchemy 2.0 way to load by primary key.

(1) मूल ने class Config: from_attributes = True लिया। यह Pydantic v1 का तरीका है; Pydantic 2 चेतावनी देता है Support for class-based config is deprecated, use ConfigDict instead। हम model_config = ConfigDict(from_attributes=True) लेते हैं। (2) Response schema में id नहीं था, इसलिए client कभी नहीं जान पाता कि अभी बनाई चीज़ का id क्या है। हमने ItemIn / ItemOut अलग किए। (3) db.query(...).filter(...).first() पुराना 1.x API है; primary key से load करने का SQLAlchemy 2.0 तरीका db.get(Item, item_id) है।

(1) 原文使用了 class Config: from_attributes = True。这是 Pydantic v1 的写法;Pydantic 2 会警告 Support for class-based config is deprecated, use ConfigDict instead。我们改用 model_config = ConfigDict(from_attributes=True)。(2) 原响应模式中没有 id,客户端永远无法得知刚创建对象的 id。我们拆分成了 ItemIn / ItemOut。(3) db.query(...).filter(...).first() 是旧的 1.x API;按主键加载在 SQLAlchemy 2.0 中的写法是 db.get(Item, item_id)。

💡
Why check_same_thread=False?check_same_thread=False क्यों?为什么要 check_same_thread=False?

Plain def endpoints run in a thread pool, so a SQLite connection may be used by a different thread than the one that opened it. SQLite refuses that by default; check_same_thread=False allows it. Each request still gets its own session from get_db().

सादे def endpoints thread pool में चलते हैं, इसलिए SQLite connection को उसे खोलने वाले से अलग thread इस्तेमाल कर सकता है। SQLite default रूप से इसे मना करता है; check_same_thread=False इसकी अनुमति देता है। फिर भी हर request को get_db() से अपना session मिलता है।

普通的 def 接口在线程池中运行,所以 SQLite 连接可能被另一个线程使用,而不是打开它的那个线程。SQLite 默认拒绝这样做;check_same_thread=False 允许这样做。每个请求仍然通过 get_db() 获得自己的会话。


SectionSection第 13 节

Middleware and CORS 🧅Middleware और CORS 🧅中间件与 CORS 🧅

Middleware wraps every request — useful for logging, timing, CORS and compression. Think of an onion: the request goes in through each layer, and the response comes back out through the same layers in reverse.

Middleware हर request को लपेटता है — logging, timing, CORS और compression के लिए उपयोगी। प्याज़ जैसा सोचिए: request हर परत से अंदर जाती है, और response उन्हीं परतों से उल्टे क्रम में बाहर आता है।

中间件包裹着每个请求 — 适合做日志、计时、CORS 和压缩。可以想象成一个洋葱:请求穿过每一层进入,响应再按相反顺序穿过同样的层返回。

Figure 4 — The Middleware Onionचित्र 4 — Middleware का प्याज़图 4 — 中间件洋葱
Middleware onionlogger (added LAST → outermost)timingGZipCORS (added first)endpointrequest →request →请求 →→ response→ response→ 响应

The middleware added last is the outermost layer.

सबसे आख़िर में जोड़ा middleware सबसे बाहरी परत है।

最后添加的中间件是最外层。

import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()
LOG = []

# -- CORS - allow these browser origins to call your API -
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# -- GZip - compress large responses -------------------
app.add_middleware(GZipMiddleware, minimum_size=1000)

# -- Custom middlewares (registered in this order) -----
@app.middleware("http")
async def add_timing_header(request: Request, call_next):
    LOG.append("timing: in")
    start    = time.perf_counter()
    response = await call_next(request)
    response.headers["X-Process-Time"] = f"{time.perf_counter() - start:.4f}"
    LOG.append("timing: out")
    return response

@app.middleware("http")
async def log_requests(request: Request, call_next):
    LOG.append("logger: in")
    response = await call_next(request)
    LOG.append("logger: out")
    return response

@app.get("/data")
def data():
    LOG.append("endpoint")
    return {"numbers": list(range(400))}       # a large-ish JSON body

from fastapi.testclient import TestClient
client = TestClient(app)

r = client.get("/data", headers={"Accept-Encoding": "gzip", "Origin": "http://localhost:3000"})
print("order       :", " -> ".join(LOG))
print("status      :", r.status_code)
print("gzip        :", r.headers.get("content-encoding"), "| bytes on the wire:", r.headers.get("content-length"),
      "| JSON size:", len(r.content))
print("CORS header :", r.headers.get("access-control-allow-origin"))
print("timing hdr  :", "X-Process-Time" in r.headers)
r = client.options("/data", headers={"Origin": "https://evil.example",
                                      "Access-Control-Request-Method": "GET"})
print("preflight from evil.example ->", r.status_code, r.text)
OUTPUT
order : logger: in -> timing: in -> endpoint -> timing: out -> logger: out status : 200 gzip : gzip | bytes on the wire: 748 | JSON size: 1503 CORS header : http://localhost:3000 timing hdr : True preflight from evil.example -> 400 Disallowed CORS origin
🔧
Fixed: Middleware Runs in REVERSE Registration Orderसुधार: Middleware Registration के उल्टे क्रम में चलता है已修正:中间件按注册顺序的 反向 运行

The original said middleware runs "in registration order on the way in". The real log above says otherwise: timing was registered first and logger second, yet logger: in came first. Each new middleware wraps everything added before it, so the last one added is the outermost. Plan your order with that in mind — for example, add a timing or logging middleware last so that it wraps everything else.

मूल में कहा गया कि middleware "अंदर जाते समय registration के क्रम में" चलता है। ऊपर का असली log कुछ और कहता है: timing पहले और logger बाद में register हुआ, फिर भी logger: in पहले आया। हर नया middleware पहले जोड़ी गई हर चीज़ को लपेटता है, इसलिए सबसे आख़िर में जोड़ा गया सबसे बाहर होता है। क्रम यह ध्यान में रखकर तय कीजिए — जैसे, timing या logging middleware को आख़िर में जोड़िए ताकि वह बाक़ी सबको लपेटे।

原文说中间件"进入时按注册顺序"运行。上面的真实日志说明并非如此:timing 先注册,logger 后注册,但先出现的却是 logger: in。每个新中间件都会包裹之前添加的所有内容,所以 最后添加的位于最外层。安排顺序时要考虑这一点 — 例如,把计时或日志中间件最后添加,让它包裹住其他所有内容。

The run also shows the other middlewares working: the 1,503-byte JSON travelled as 748 gzip bytes, the allowed origin got its CORS header, and a preflight request from an origin not on the list was refused.

Run बाक़ी middlewares को भी काम करते दिखाता है: 1,503-byte JSON 748 gzip bytes बनकर गया, अनुमति वाले origin को CORS header मिला, और सूची में न होने वाले origin की preflight request ठुकरा दी गई।

这次运行也展示了其他中间件的作用:1,503 字节的 JSON 以 748 字节的 gzip 形式传输,被允许的来源拿到了 CORS 响应头,而来自名单外来源的预检请求被拒绝。


SectionSection第 14 节

Sync vs Async — When Speed Matters ⚡Sync बनाम Async — जब Speed मायने रखे ⚡同步 vs 异步 — 速度要紧时 ⚡

🟠 Sync endpoint — defSync endpoint — def同步接口 — def
Aspectपहलू方面Detailविवरण细节
SignatureSignature签名def get_data():
Runs inकहाँ चलता है运行于A thread pool (40 threads by default)Thread pool (default 40 threads)线程池(默认 40 个线程)
While waiting on I/OI/O के इंतज़ार में等待 I/O 时Blocks its own thread onlyसिर्फ़ अपना thread रोकता है只阻塞自己的线程
Best forकिसके लिए适合Blocking libraries, CPU workBlocking libraries, CPU काम阻塞库、CPU 计算
Examplesउदाहरण例子requests, sqlite3, Pillow
🚀 Async endpoint — async defAsync endpoint — async def异步接口 — async def
Aspectपहलू方面Detailविवरण细节
SignatureSignature签名async def get_data():
Runs inकहाँ चलता है运行于The event loop (one thread)Event loop (एक thread)事件循环(单线程)
While waiting on I/OI/O के इंतज़ार में等待 I/O 时await frees the loop for othersawait loop को दूसरों के लिए छोड़ता हैawait 让出事件循环给别人
Best forकिसके लिए适合Many waits: HTTP calls, async DB driversकई इंतज़ार: HTTP calls, async DB drivers大量等待:HTTP 调用、异步数据库驱动
Examplesउदाहरण例子httpx, asyncpg, aiofiles
import asyncio, time
import requests, httpx
from fastapi import FastAPI

app = FastAPI()

# -- SYNC - FastAPI runs it in a thread pool ------------
@app.get("/weather-sync/{city}")
def weather_sync(city: str):
    r = requests.get(f"https://api.example-weather.com/{city}", timeout=5)
    return r.json()

# -- ASYNC - awaits, so the event loop stays free --------
@app.get("/weather-async/{city}")
async def weather_async(city: str):
    async with httpx.AsyncClient(timeout=5) as client:
        r = await client.get(f"https://api.example-weather.com/{city}")
    return r.json()

# -- WRONG - a blocking call inside async def -----------
@app.get("/weather-broken/{city}")
async def weather_broken(city: str):
    r = requests.get(f"https://api.example-weather.com/{city}", timeout=5)  # blocks the loop!
    return r.json()

Those endpoints call an outside service, so here is the same idea measured for real: each endpoint waits 0.2 s, and 50 requests arrive at once.

वे endpoints बाहरी service बुलाते हैं, इसलिए यहाँ वही बात असल में मापी गई है: हर endpoint 0.2 s इंतज़ार करता है, और 50 requests एक साथ आती हैं।

这些接口会调用外部服务,所以下面用真实测量来说明同样的道理:每个接口等待 0.2 秒,50 个请求同时到达。

import asyncio, threading, time
import httpx, uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/sync")                    # def -> runs in the thread pool
def sync_endpoint():
    time.sleep(0.2)                  # pretend: a blocking I/O call (requests, sqlite3)
    return {"ok": True}

@app.get("/async")                   # async def + await -> runs on the event loop
async def async_endpoint():
    await asyncio.sleep(0.2)         # pretend: an awaited I/O call (httpx, asyncpg)
    return {"ok": True}

@app.get("/broken")                  # async def + BLOCKING call
async def broken_endpoint():
    time.sleep(0.2)                  # blocks the whole event loop!
    return {"ok": True}

server = uvicorn.Server(uvicorn.Config(app, port=8012, log_level="warning"))
threading.Thread(target=server.run, daemon=True).start()
time.sleep(1.5)

async def hammer(path, n=50):
    async with httpx.AsyncClient(base_url="http://127.0.0.1:8012", timeout=60) as c:
        t0 = time.perf_counter()
        await asyncio.gather(*(c.get(path) for _ in range(n)))
        return time.perf_counter() - t0

for path in ["/sync", "/async", "/broken"]:
    print(f"50 requests at once to {path:8s}: {asyncio.run(hammer(path)):5.2f} s")
server.should_exit = True
OUTPUT
50 requests at once to /sync : 0.47 s 50 requests at once to /async : 0.26 s 50 requests at once to /broken : 10.07 s
🔧
Fixed: "100 req/sec vs 5000 req/sec" Were Made-Up Numbersसुधार: "100 req/sec बनाम 5000 req/sec" गढ़े हुए Numbers थे已修正:"100 vs 5000 请求/秒"是编造的数字

The original headers promised "Sync — 100 req/sec" and "Async — 5000 req/sec", and said sync endpoints "block on I/O". In the real run, the def endpoint handled 50 waiting requests in about half a second — it blocks only its own thread, and FastAPI has a pool of them. The async version was a little faster. The real danger is the third case: a blocking call inside async def served the requests one by one, about 40 times slower.

मूल headers ने "Sync — 100 req/sec" और "Async — 5000 req/sec" का वादा किया, और कहा कि sync endpoints "I/O पर block" करते हैं। असली run में def endpoint ने 50 इंतज़ार करती requests लगभग आधे सेकंड में संभालीं — यह सिर्फ़ अपना thread रोकता है, और FastAPI के पास ऐसे threads का pool है। Async version थोड़ा तेज़ था। असली ख़तरा तीसरा मामला है: async def के अंदर blocking call ने requests एक-एक करके संभालीं, लगभग 40 गुना धीमे।

原文的标题宣称"同步 — 100 请求/秒""异步 — 5000 请求/秒",还说同步接口"会在 I/O 上阻塞"。真实运行中,def 接口约半秒就处理完 50 个等待中的请求 — 它只阻塞自己的线程,而 FastAPI 有一个线程池。异步版本稍快一些。真正的危险是第三种情况:async def 里的阻塞调用让请求只能一个接一个处理,慢了大约 40 倍。

⏱️ Simulator: Concurrency Lab⏱️ Simulator: Concurrency Lab⏱️ 模拟器:并发实验室

Each dot is one request finishing, placed at its real finish time. Each endpoint waits 0.2 s. Change how many requests arrive at once.

हर बिंदु एक पूरी हुई request है, अपने असली ख़त्म होने के समय पर। हर endpoint 0.2 s इंतज़ार करता है। बदलिए कि एक साथ कितनी requests आती हैं।

每个圆点代表一个请求完成,位于它真实完成的时间点。每个接口等待 0.2 秒。改变同时到达的请求数试试。

Concurrency LabConcurrency Lab并发实验室9 real runs on UvicornUvicorn पर 9 असली runs在 Uvicorn 上的 9 次真实运行
requests at once:एक साथ requests:同时请求数:
⚠️
Never Put Blocking Calls in async defasync def में Blocking Calls कभी न डालें绝不要在 async def 中放阻塞调用

requests.get() or time.sleep() inside an async def endpoint blocks the whole event loop — every other request waits. Use the async library (httpx, asyncio.sleep), wrap the call in await asyncio.to_thread(...), or simply write the endpoint as def — FastAPI then runs it in the thread pool.

async def endpoint के अंदर requests.get() या time.sleep() पूरे event loop को रोक देता है — हर दूसरी request इंतज़ार करती है। Async library (httpx, asyncio.sleep) लीजिए, call को await asyncio.to_thread(...) में लपेटिए, या endpoint को बस def लिखिए — तब FastAPI उसे thread pool में चलाता है।

在 async def 接口中调用 requests.get() 或 time.sleep() 会 阻塞整个事件循环 — 其他所有请求都得等。请使用异步库(httpx、asyncio.sleep),或把调用包进 await asyncio.to_thread(...),或者干脆把接口写成 def — FastAPI 会在线程池中运行它。


SectionSection第 15 节

Testing FastAPI Applications 🧪FastAPI Applications की Testing 🧪测试 FastAPI 应用 🧪

FastAPI includes TestClient, built on httpx. It sends real requests to your app in memory — no server needed. Pair it with pytest for a fast test loop. Below, main.py is the CRUD app from Section 06.

FastAPI में httpx पर बना TestClient शामिल है। यह memory में आपके app को असली requests भेजता है — server की ज़रूरत नहीं। तेज़ test loop के लिए इसे pytest के साथ लीजिए। नीचे main.py Section 06 का CRUD app है।

FastAPI 自带基于 httpx 的 TestClient。它在内存中向你的应用发送真实请求 — 不需要服务器。配合 pytest,测试循环又快又稳。下面的 main.py 就是第 06 节的 CRUD 应用。

# file: test_main.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_create_item():
    payload = {"name": "Notebook", "price": 2.99, "stock": 100}
    r = client.post("/items", json=payload)
    assert r.status_code == 201
    data = r.json()
    assert data["name"]  == "Notebook"
    assert data["price"] == 2.99
    assert "id" in data

def test_create_item_validation_error():
    payload = {"name": "", "price": -1}      # invalid
    r = client.post("/items", json=payload)
    assert r.status_code == 422
    errors = r.json()["detail"]
    assert len(errors) >= 2                  # name + price both wrong

def test_get_missing_item_returns_404():
    r = client.get("/items/00000000-0000-0000-0000-000000000000")
    assert r.status_code == 404
    assert r.json()["detail"] == "Item not found"
PYTEST OUTPUT
$ pytest -v test_main.py ======================================= test session starts ======================================== platform linux -- Python 3.13.16, pytest-9.2.0, pluggy-1.7.0 -- /usr/bin/python3 rootdir: /app plugins: anyio-4.15.1 collecting ... collected 3 items test_main.py::test_create_item PASSED [ 33%] test_main.py::test_create_item_validation_error PASSED [ 66%] test_main.py::test_get_missing_item_returns_404 PASSED [100%] ======================================== 3 passed in 0.xxs =========================================
ℹ️
A Real pytest Runएक असली pytest Run一次真实的 pytest 运行

The original output showed "Python 3.11.4, pytest-8.0.0" — a typed example, not a run. The output above is real (only the run time is hidden, because it changes each run). The same TestClient powers every "try it" block on this page.

मूल output में "Python 3.11.4, pytest-8.0.0" दिखाया गया — एक लिखा हुआ उदाहरण, run नहीं। ऊपर का output असली है (सिर्फ़ run का समय छिपाया गया, क्योंकि वह हर बार बदलता है)। इस page का हर "try it" block इसी TestClient से चलता है।

原输出显示的"Python 3.11.4, pytest-8.0.0"只是手写的示例,并不是真实运行。上面的输出是真实的(只隐藏了运行耗时,因为每次都不同)。本页所有"try it"代码块都由同一个 TestClient 驱动。


SectionSection第 16 节

FastAPI vs Flask vs Django REST ⚖️FastAPI बनाम Flask बनाम Django REST ⚖️FastAPI vs Flask vs Django REST ⚖️

Propertyगुण特性FastAPIFlaskDjango REST
Async viewsAsync views异步视图Yes — native (ASGI)हाँ — मूल (ASGI)是 — 原生(ASGI)Since 2.0, on top of WSGI2.0 से, WSGI के ऊपर自 2.0 起,基于 WSGIPartialआंशिक部分支持
Request validationRequest validation请求校验Pydantic — built inPydantic — शामिलPydantic — 内置Manual or an extensionहाथ से या extension手写或扩展SerializersSerializers序列化器
Interactive docsInteractive docs交互式文档Free at /docs/docs पर मुफ़्त在 /docs 免费提供Extension neededExtension चाहिए需要扩展drf-spectacular (add-on)drf-spectacular (add-on)drf-spectacular(附加包)
Driven by type hintsType hints से चलता由类型提示驱动Yes — core designहाँ — मूल design是 — 核心设计Noनहीं否Noनहीं否
Speed for JSON APIsJSON APIs की speedJSON API 速度Among the fastest in PythonPython में सबसे तेज़ मेंPython 中最快之一Goodअच्छी良好Goodअच्छी良好
Learning curveसीखना学习曲线Gentleआसान平缓Very gentleबहुत आसान非常平缓Steeperकठिन较陡
Best forकिसके लिए最适合APIs, microservices, ML servingAPIs, microservices, ML servingAPI、微服务、机器学习服务Small apps, quick prototypesछोटे apps, जल्दी prototypes小应用、快速原型Full websites + adminपूरी websites + admin完整网站 + 后台
Bundled ORMसाथ में ORM内置 ORMNo — pick your ownनहीं — ख़ुद चुनिए无 — 自行选择Noनहीं否Django ORMDjango ORMDjango ORM
Bundled adminसाथ में admin内置后台Noनहीं否Noनहीं否Django AdminDjango AdminDjango Admin
🔧
Fixed: Benchmark Numbers Removed, Flask Async Correctedसुधार: Benchmark Numbers हटाए, Flask Async सुधारा已修正:删除了基准数字,修正了 Flask 的异步说明

The original table gave "~30k / ~8k / ~6k req/s" with no source. Such numbers depend completely on hardware, server, workers and what each endpoint does, so we removed them. It also said Flask's async was "bolted on (3.0+)": Flask has supported async def views since 2.0; they run on WSGI, one request per worker thread.

मूल table ने बिना स्रोत के "~30k / ~8k / ~6k req/s" दिए। ऐसे numbers पूरी तरह hardware, server, workers और हर endpoint के काम पर निर्भर हैं, इसलिए हमने हटा दिए। इसमें यह भी कहा गया कि Flask का async "bolted on (3.0+)" है: Flask 2.0 से async def views support करता है; वे WSGI पर चलते हैं, हर worker thread पर एक request।

原表格给出了"约 30k / 8k / 6k 请求/秒",却没有来源。这类数字完全取决于硬件、服务器、worker 数量以及每个接口做什么,所以我们删除了它们。原表还说 Flask 的异步是"3.0+ 后补上的":Flask 从 2.0 起就支持 async def 视图;它们运行在 WSGI 上,每个工作线程处理一个请求。

🏆
The Practitioner's Ruleअनुभवी का नियम实践者法则

Choose FastAPI for JSON APIs, microservices and ML model serving — validation and docs come for free. Choose Django when you need a full website with admin, auth and templates out of the box. Choose Flask for the smallest, most familiar stack when you do not need automatic docs or validation.

JSON APIs, microservices और ML model serving के लिए FastAPI चुनिए — validation और docs मुफ़्त मिलते हैं। जब admin, auth और templates के साथ पूरी website तुरंत चाहिए, तो Django चुनिए। जब अपने-आप docs या validation नहीं चाहिए, तो सबसे छोटे और जाने-पहचाने stack के लिए Flask चुनिए।

JSON API、微服务和机器学习模型服务选 FastAPI — 校验和文档免费附送。需要开箱即用的后台、认证和模板的完整网站,选 Django。不需要自动文档或校验、想要最小最熟悉的技术栈时,选 Flask。


SectionSection第 17 节

Deployment — From Laptop to Production 🚢Deployment — Laptop से Production तक 🚢部署 — 从笔记本到生产环境 🚢

Production server: Gunicorn + Uvicorn workersProduction server: Gunicorn + Uvicorn workers生产服务器:Gunicorn + Uvicorn worker

# A tiny app to deploy
from fastapi import FastAPI
app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}
OUTPUT
$ gunicorn main:app -w 2 -k uvicorn_worker.UvicornWorker -b 0.0.0.0:8013 [PID] [INFO] Starting gunicorn 27.0.0 [PID] [INFO] Listening at: http://0.0.0.0:8013 (PID) [PID] [INFO] Using worker: uvicorn_worker.UvicornWorker [PID] [INFO] Booting worker with pid: PID [PID] [INFO] Booting worker with pid: PID [PID] [INFO] Control socket listening at /home/mohit/.gunicorn/gunicorn.ctl [PID] [INFO] Started server process [PID] [PID] [INFO] Waiting for application startup. [PID] [INFO] Application startup complete. $ curl http://127.0.0.1:8013/health {"status":"ok"} $ fastapi run main.py --workers 2 (the built-in alternative) INFO: Uvicorn running on http://0.0.0.0:8014 (Press CTRL+C to quit) INFO: Started parent process [PID] INFO: Started server process [PID] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Started server process [PID]
🔧
Fixed: uvicorn.workers.UvicornWorker Is Deprecatedसुधार: uvicorn.workers.UvicornWorker Deprecated है已修正:uvicorn.workers.UvicornWorker 已弃用

The original used -k uvicorn.workers.UvicornWorker. Uvicorn now warns: The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. Install uvicorn-worker and use -k uvicorn_worker.UvicornWorker, as above. Many deployments do not need Gunicorn at all: fastapi run --workers 4 or uvicorn main:app --workers 4 start several worker processes on their own.

मूल ने -k uvicorn.workers.UvicornWorker लिया। Uvicorn अब चेतावनी देता है: The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. uvicorn-worker install कीजिए और -k uvicorn_worker.UvicornWorker लीजिए, जैसा ऊपर। कई deployments को Gunicorn की ज़रूरत ही नहीं: fastapi run --workers 4 या uvicorn main:app --workers 4 ख़ुद कई worker processes शुरू करते हैं।

原文使用了 -k uvicorn.workers.UvicornWorker。Uvicorn 现在会警告:The uvicorn.workers module is deprecated. Please use uvicorn-worker package instead. 请安装 uvicorn-worker 并使用 -k uvicorn_worker.UvicornWorker,如上所示。很多部署根本不需要 Gunicorn:fastapi run --workers 4 或 uvicorn main:app --workers 4 自己就能启动多个 worker 进程。

DockerfileDockerfileDockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["fastapi", "run", "main.py", "--port", "8000", "--workers", "4"]
🛠️ Production Deployment ChecklistProduction Deployment Checklist生产部署清单
WorkersWorkersWorker
Start with about one worker per CPU core and measure. (The "2 × cores + 1" rule comes from Gunicorn's blocking workers; async workers usually need fewer.) Each worker is a separate process, so they run in parallel.
लगभग हर CPU core पर एक worker से शुरू कीजिए और मापिए। ("2 × cores + 1" नियम Gunicorn के blocking workers से आता है; async workers को आम तौर पर कम चाहिए।) हर worker अलग process है, इसलिए वे parallel चलते हैं।
从大约 每个 CPU 核一个 worker 开始,然后实测调整。("2 × 核数 + 1"规则来自 Gunicorn 的阻塞型 worker;异步 worker 通常需要更少。)每个 worker 是独立进程,所以它们并行运行。
HTTPS
Terminate TLS at a reverse proxy or load balancer (Nginx, Caddy, a cloud load balancer), not in Uvicorn.
TLS को reverse proxy या load balancer (Nginx, Caddy, cloud load balancer) पर ख़त्म कीजिए, Uvicorn में नहीं।
在 反向代理 或负载均衡器(Nginx、Caddy、云负载均衡)处终止 TLS,而不是在 Uvicorn 中。
Env
Load secrets from environment variables — pydantic-settings makes this typed and easy.
Secrets environment variables से load कीजिए — pydantic-settings इसे typed और आसान बनाता है।
从 环境变量 加载机密 — pydantic-settings 能让这件事带类型且简单。
Logs
Write logs to stdout (JSON is easiest to search) and let the platform collect them.
Logs stdout पर लिखिए (JSON खोजने में सबसे आसान) और platform को इकट्ठा करने दीजिए।
把日志写到标准输出(JSON 最便于检索),交给平台收集。
Health
Expose a /health endpoint. Load balancers and Kubernetes use it to check your app.
/health endpoint रखिए। Load balancers और Kubernetes आपके app की जाँच इसी से करते हैं।
提供一个 /health 接口。负载均衡器和 Kubernetes 用它来检查你的应用。
Docs
For an internal API, you may hide /docs and /redoc in production with FastAPI(docs_url=None, redoc_url=None).
अंदरूनी API के लिए production में FastAPI(docs_url=None, redoc_url=None) से /docs और /redoc छिपा सकते हैं।
对于内部 API,可以在生产环境中用 FastAPI(docs_url=None, redoc_url=None) 隐藏 /docs 和 /redoc。

SectionSection第 18 节

When to Use FastAPI 🧭FastAPI कब लें 🧭何时使用 FastAPI 🧭

✅
JSON REST APIsJSON REST APIsJSON REST API
SaaS backends, mobile APIsSaaS backends, mobile APIsSaaS 后端、移动端 API
A great fit for microservices, backends for web and mobile apps, and public APIs. The automatic docs make it quicker for others to use your API.
Microservices, web और mobile apps के backends, और public APIs के लिए बढ़िया। अपने-आप बनने वाले docs दूसरों के लिए आपका API इस्तेमाल करना आसान बनाते हैं।
非常适合微服务、Web 和移动应用的后端以及公共 API。自动文档让别人更快上手你的 API。
✅
Machine-learning servingMachine-learning serving机器学习模型服务
ML inference, LLM proxiesML inference, LLM proxies模型推理、LLM 代理
Wrap a scikit-learn or PyTorch model. Pydantic checks the inputs, so bad data never reaches your model. (Heavy CPU/GPU work should use def endpoints or worker processes.)
scikit-learn या PyTorch model लपेटिए। Pydantic inputs जाँचता है, इसलिए ग़लत data कभी model तक नहीं पहुँचता। (भारी CPU/GPU काम के लिए def endpoints या worker processes लीजिए।)
包装一个 scikit-learn 或 PyTorch 模型。Pydantic 检查输入,坏数据永远到不了模型。(繁重的 CPU/GPU 计算应使用 def 接口或 worker 进程。)
✅
Real-time and WebSocketsReal-time और WebSockets实时与 WebSocket
chat, notifications, streamingchat, notifications, streaming聊天、通知、流式传输
ASGI makes WebSockets, Server-Sent Events and streaming responses first-class. Good for chat, dashboards and live updates.
ASGI WebSockets, Server-Sent Events और streaming responses को पूरा support देता है। Chat, dashboards और live updates के लिए अच्छा।
ASGI 让 WebSocket、服务器推送事件和流式响应都得到一等支持。适合聊天、仪表盘和实时更新。
❌
Server-rendered websitesServer-rendered websites服务端渲染网站
blogs, CMS, e-commerceblogs, CMS, e-commerce博客、内容管理、电商
FastAPI can render Jinja2 templates, but it has no built-in admin, ORM, migrations or forms. For content-heavy sites, Django is often a better tool.
FastAPI Jinja2 templates render कर सकता है, पर उसमें built-in admin, ORM, migrations या forms नहीं। Content वाली sites के लिए Django अक्सर बेहतर औज़ार है।
FastAPI 可以渲染 Jinja2 模板,但没有内置后台、ORM、迁移或表单。对于内容繁多的网站,Django 往往更合适。
❌
Heavy legacy sync codeभारी पुराना sync code大量遗留同步代码
legacy integrationsपुराने integrations遗留集成
If every library you call is blocking, you lose most of the async benefit. FastAPI still works with def endpoints, but Flask may be the simpler choice.
अगर आपकी बुलाई हर library blocking है, तो async का ज़्यादातर फ़ायदा खो जाता है। FastAPI फिर भी def endpoints के साथ चलता है, पर Flask आसान विकल्प हो सकता है।
如果你调用的每个库都是阻塞的,异步的好处就基本没了。FastAPI 仍可用 def 接口工作,但 Flask 可能更简单。
❌
Static sitesStatic sites静态网站
docs, marketing pagesdocs, marketing pages文档、营销页面
Do not use any web framework for a purely static site. Put the built HTML/CSS/JS on a CDN or static host instead.
पूरी तरह static site के लिए कोई web framework मत लीजिए। बनी हुई HTML/CSS/JS को CDN या static host पर रखिए।
纯静态网站不要用任何 Web 框架。把构建好的 HTML/CSS/JS 放到 CDN 或静态托管上即可。

SectionSection第 19 节

Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real FastAPI mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.

छह असली FastAPI ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।

六个真实的 FastAPI 错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。

Bug DoctorBug DoctorBug 医生6 bugs · real outputs6 bugs · असली outputs6 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正
⚡ FastAPI — Rules to RememberFastAPI — याद रखने वाले नियमFastAPI — 必须记住的规则
1
Type-hint every parameter. In FastAPI, type hints are not just documentation — they drive conversion, validation and the OpenAPI docs. A parameter without a hint arrives as a plain string.
हर parameter को type hint दीजिए। FastAPI में type hints सिर्फ़ दस्तावेज़ नहीं — वे conversion, validation और OpenAPI docs चलाते हैं। बिना hint वाला parameter सादी string बनकर आता है।
给每个参数加类型提示。在 FastAPI 中,类型提示不只是文档 — 它们驱动着类型转换、校验和 OpenAPI 文档。没有提示的参数会以普通字符串的形式到达。
2
Use Pydantic models for request bodies and responses. A raw dict body loses validation, docs and editor help all at once.
Request bodies और responses के लिए Pydantic models लीजिए। Raw dict body से validation, docs और editor की मदद एक साथ खो जाती है।
请求体和响应都用 Pydantic 模型。用原始 dict 做请求体,会同时失去校验、文档和编辑器提示。
3
Prefer Annotated[Type, Query(...)] over the older default-value style — and remember that lists and dicts need Query() to be read from the URL.
पुराने default-value ढंग की जगह Annotated[Type, Query(...)] लीजिए — और याद रखिए कि lists और dicts को URL से पढ़ने के लिए Query() चाहिए।
优先使用 Annotated[Type, Query(...)],而不是旧的默认值写法 — 并记住,列表和字典要用 Query() 才会从 URL 中读取。
4
Never block inside async def. Mixing def and async def endpoints in one app is fine. Inside async def, only await non-blocking calls; for blocking libraries write a def endpoint or use asyncio.to_thread().
async def के अंदर कभी block न करें। एक app में def और async def endpoints मिलाना ठीक है। async def के अंदर सिर्फ़ non-blocking calls को await कीजिए; blocking libraries के लिए def endpoint लिखिए या asyncio.to_thread() लीजिए।
绝不要在 async def 中阻塞。同一个应用里混用 def 和 async def 接口没有问题。在 async def 中只 await 非阻塞调用;遇到阻塞库就写成 def 接口,或使用 asyncio.to_thread()。
5
Set a response_model (or a return type) for every public endpoint. It filters out fields like passwords and locks the shape of your response.
हर public endpoint के लिए response_model (या return type) लगाइए। यह passwords जैसे fields छाँटता है और आपके response का आकार तय करता है।
为每个公开接口设置 response_model(或返回类型)。它会过滤掉密码之类的字段,并锁定响应的结构。
6
Use Depends() for anything reusable — DB sessions, auth, pagination, settings. Dependencies are cached per request and easy to replace in tests.
दोबारा इस्तेमाल होने वाली हर चीज़ के लिए Depends() लीजिए — DB sessions, auth, pagination, settings। Dependencies हर request पर cache होती हैं और tests में आसानी से बदली जा सकती हैं।
任何可复用的东西都用 Depends() — 数据库会话、认证、分页、配置。依赖按请求缓存,测试时也容易替换。
7
Split large apps into routers: APIRouter(prefix="/users", tags=["users"]) in its own file, added with app.include_router(users.router). Put fixed paths like /users/me before /users/{id}.
बड़े apps को routers में बाँटिए: अपनी file में APIRouter(prefix="/users", tags=["users"]), app.include_router(users.router) से जोड़ा गया। /users/me जैसे तय paths को /users/{id} से पहले रखिए।
把大型应用拆分成路由器:在单独文件中写 APIRouter(prefix="/users", tags=["users"]),用 app.include_router(users.router) 引入。把 /users/me 这样的固定路径放在 /users/{id} 之前。
8
Deploy with several worker processes behind a reverse proxy — fastapi run --workers N, or Gunicorn with uvicorn_worker.UvicornWorker. Let the proxy or load balancer handle TLS.
Reverse proxy के पीछे कई worker processes के साथ deploy कीजिए — fastapi run --workers N, या uvicorn_worker.UvicornWorker के साथ Gunicorn। TLS को proxy या load balancer संभालने दीजिए।
在反向代理后面用多个 worker 进程部署 — fastapi run --workers N,或使用 uvicorn_worker.UvicornWorker 的 Gunicorn。让代理或负载均衡器处理 TLS。
🔧
Fixed: Rules 1, 4 and 8 Correctedसुधार: नियम 1, 4 और 8 सुधारे गए已修正:规则 1、4 和 8 已修正

Rule 1 said skipping hints "turns FastAPI into a slow Flask" — the real effect is that values arrive as strings without checks (Section 04). Rule 4 said "choose async or sync per endpoint, never mix", which confused two ideas: mixing endpoint styles is fine, but blocking inside async def is not (Section 14). Rule 8 used the deprecated uvicorn.workers module (Section 17).

नियम 1 ने कहा कि hints छोड़ने से "FastAPI धीमा Flask बन जाता है" — असली असर यह है कि values बिना जाँच के strings बनकर आती हैं (Section 04)। नियम 4 ने कहा "हर endpoint के लिए async या sync चुनिए, कभी न मिलाइए", जिसने दो बातें उलझा दीं: endpoint शैलियाँ मिलाना ठीक है, पर async def के अंदर block करना नहीं (Section 14)। नियम 8 ने deprecated uvicorn.workers module लिया (Section 17)।

规则 1 说省略类型提示会"把 FastAPI 变成慢吞吞的 Flask"— 实际后果是值以字符串形式到达且不经检查(第 04 节)。规则 4 说"每个接口选异步或同步,绝不混用",把两件事混为一谈:混用接口风格没问题,但在 async def 中阻塞不行(第 14 节)。规则 8 使用了已弃用的 uvicorn.workers 模块(第 17 节)。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

FastAPI turns type hints into parsing, validation, docs and JSON output. Path, query and body parameters are chosen from your signature; Pydantic models check bodies and filter responses; Depends() shares setup like auth and DB sessions. Keep async def free of blocking calls, test with TestClient, and deploy several workers behind a proxy.

FastAPI type hints को parsing, validation, docs और JSON output में बदलता है। Path, query और body parameters आपके signature से चुने जाते हैं; Pydantic models bodies जाँचते और responses छाँटते हैं; Depends() auth और DB sessions जैसा setup साझा करता है। async def को blocking calls से दूर रखिए, TestClient से test कीजिए, और proxy के पीछे कई workers deploy कीजिए।

FastAPI 把类型提示变成解析、校验、文档和 JSON 输出。路径、查询和请求体参数由函数签名决定;Pydantic 模型校验请求体并过滤响应;Depends() 共享认证、数据库会话等准备工作。让 async def 远离阻塞调用,用 TestClient 测试,并在代理后面部署多个 worker。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.