FastAPI: প্রথম API
Route, Pydantic model, auto docs
এতদিন যা শিখেছো (function, type hint, Pydantic, async, decorator) সব এক জায়গায় মিলে যায় FastAPI তে। এটা Python এর সবচেয়ে জনপ্রিয় আধুনিক API framework: দ্রুত, type hint দিয়ে চলে, আর নিজে থেকেই API এর documentation বানিয়ে দেয়। Laravel এ যেমন routes/api.php আর Controller, FastAPI তে route function। এই অধ্যায়ে প্রথম API বানাবো আর দেখবো একটা request এর পেছনে FastAPI কী কী করে।
#Setup আর প্রথম API
uv init notes-api && cd notes-api
uv add "fastapi[standard]"# main.py
from fastapi import FastAPI
app = FastAPI(title="Notes API", version="0.1.0")
@app.get("/")
async def root():
return {"message": "স্বাগতম!"}
@app.get("/hello/{name}")
async def hello(name: str, times: int = 1):
return {"greeting": f"হ্যালো {name}! " * times}uv run fastapi dev main.pyBrowser এ খোলো:
http://127.0.0.1:8000/→{"message": "স্বাগতম!"}http://127.0.0.1:8000/hello/Rahim?times=2http://127.0.0.1:8000/docs→ Swagger UI: সব endpoint এর তালিকা, আর browser থেকেই চালিয়ে দেখা যায়
/docs page টা তোমার Flutter developer সত্তার জন্য দারুণ খবর: API কী নেয় আর কী দেয়, সব লেখা থাকে, আর বদল করলে নিজে আপডেট হয়।
#Parameter কোথা থেকে আসে
FastAPI function এর parameter দেখে বুঝে নেয় কোনটা কোথা থেকে আসবে:
from fastapi import FastAPI, Header, Query
from pydantic import BaseModel, Field
app = FastAPI()
class NoteCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
content: str = ""
tags: list[str] = []
@app.post("/notes/{folder_id}")
async def create_note(
folder_id: int, # path এ আছে → path parameter
note: NoteCreate, # Pydantic model → JSON body
pin: bool = False, # সাধারণ type, path এ নেই → query (?pin=true)
lang: str = Query("bn", pattern="^(bn|en)$"), # query, যাচাইসহ
user_agent: str | None = Header(None), # header
):
return {"folder": folder_id, "note": note, "pin": pin, "lang": lang, "ua": user_agent}| কোথা থেকে | কীভাবে বোঝে |
|---|---|
Path (/notes/{folder_id}) |
URL এ {} এর ভেতরের নামের সাথে মেলে |
Query (?pin=true) |
সাধারণ type (int, str, bool), path এ নেই |
| Body (JSON) | Pydantic model |
| Header, Cookie | Header(), Cookie() দিয়ে বলে দিতে হয় |
আর type অনুযায়ী যাচাই আর রূপান্তর নিজে থেকে হয়। folder_id তে "abc" পাঠালে:
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "folder_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
}Status 422 Unprocessable Entity, আর তুমি এক লাইনও যাচাইয়ের code লেখোনি।
#একটা request এর যাত্রা FastAPI এর ভেতরে
#response_model: কী ফেরত যাবে
from datetime import datetime
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
app = FastAPI()
class NoteCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
content: str = ""
class NoteOut(BaseModel):
id: int
title: str
content: str
created_at: datetime
# আপাতত memory তে রাখছি; পরের অধ্যায়ে database
_notes: dict[int, dict] = {}
_next_id = 1
@app.post("/notes", response_model=NoteOut, status_code=status.HTTP_201_CREATED)
async def create_note(data: NoteCreate):
global _next_id
note = {"id": _next_id, **data.model_dump(), "created_at": datetime.now(), "owner_secret": "xyz"}
_notes[_next_id] = note
_next_id += 1
return note # owner_secret বাদ পড়বে, কারণ NoteOut এ নেই
@app.get("/notes", response_model=list[NoteOut])
async def list_notes(limit: int = 20, offset: int = 0):
return list(_notes.values())[offset : offset + limit]
@app.get("/notes/{note_id}", response_model=NoteOut)
async def get_note(note_id: int):
note = _notes.get(note_id)
if note is None:
raise HTTPException(status_code=404, detail="Note পাওয়া যায়নি")
return note
@app.delete("/notes/{note_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_note(note_id: int):
if _notes.pop(note_id, None) is None:
raise HTTPException(status_code=404, detail="Note পাওয়া যায়নি")response_model দুইটা কাজ করে:
- নিরাপত্তা: model এ যা নেই তা বাদ। Database এর user object থেকে
password_hashভুল করে client এ চলে যাওয়ার ভয় থাকে না। - Documentation:
/docsএ দেখায় ঠিক কী ফেরত আসবে। Flutter এ model class বানাতে এটা কাজে লাগে।
#Dependency Injection: Depends
অনেক route এ একই জিনিস লাগে: database session, login করা user, pagination এর parameter। FastAPI তে এগুলো dependency হিসেবে লেখো:
from typing import Annotated
from fastapi import Depends
class Pagination(BaseModel):
limit: int
offset: int
def pagination(limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0)) -> Pagination:
return Pagination(limit=limit, offset=offset)
PageDep = Annotated[Pagination, Depends(pagination)]
@app.get("/notes")
async def list_notes(page: PageDep):
return list(_notes.values())[page.offset : page.offset + page.limit]এখন যেকোনো list endpoint এ page: PageDep লিখলেই একই নিয়মে limit (সর্বোচ্চ ১০০) আর offset পাবে। Database session আর current user ও এভাবে আসবে, পরের অধ্যায়গুলোয় দেখবো।
#Async নাকি sync route
@app.get("/a")
async def a(): # event loop এ চলে; ভেতরে শুধু await করা I/O
...
@app.get("/b")
def b(): # FastAPI আলাদা thread এ চালায়; sync library চলে
...নিয়ম (Async অধ্যায়ের সারাংশ):
- ভেতরে async library (asyncpg, httpx.AsyncClient, redis.asyncio) →
async def - ভেতরে sync library বা blocking কাজ → সাধারণ
def - কখনো
async defএর ভেতরে blocking কাজ না।
#CORS: browser থেকে call করতে দেওয়া
Flutter mobile app এর জন্য CORS লাগে না। কিন্তু Flutter web বা অন্য website থেকে call করলে browser অন্য domain এর API আটকে দেয়, যদি না server অনুমতি দেয়:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "http://localhost:3000"], # "*" production এ না
allow_methods=["*"],
allow_headers=["*"],
allow_credentials=True,
)#অনুশীলন
#নিজেকে যাচাই করো
FastAPI কীভাবে বোঝে একটা parameter path, query নাকি body থেকে আসবে?
URL path এ {name} থাকলে path parameter। Pydantic model হলে JSON body। সাধারণ type (int, str, bool) যেটা path এ নেই সেটা query parameter। Header বা Cookie এর জন্য আলাদা করে বলতে হয়।
`response_model` এর দুইটা কাজ কী?
Model এ নেই এমন field (যেমন password_hash) response থেকে বাদ দেয়, আর documentation এ দেখায় ঠিক কী ফেরত আসবে।
Input আর output এর জন্য আলাদা Pydantic model কেন?
Client যেন server এর ঠিক করা field (id, created_at, owner) নিজে পাঠাতে না পারে, আর output এ যেন ভেতরের গোপন field চলে না যায়। Update এর জন্য আলাদা model এ সব field ঐচ্ছিক রাখা যায়।
ভুল type এর data পাঠালে FastAPI কী status code দেয়?
422 Unprocessable Entity, সাথে কোন field এ কী সমস্যা তার বিস্তারিত।
চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।