FastAPI — Tutorial User Guide
FastAPI dibangun di atas Pydantic dan async — dua hal yang sudah kamu kuasai, jadi fase ini akan terasa cepat.
Intisari
- FastAPI membaca type hint fungsimu untuk memvalidasi request, menserialisasi respons, dan membuat dokumentasi.
- Model Pydantic sebagai parameter = request body. Sebagai return type = respons yang tervalidasi.
- Dokumentasi interaktif gratis di
/docs— tanpa konfigurasi apa pun. - Path parameter, query parameter, dan body dibedakan otomatis dari tipe dan tanda tangan fungsi.
- Jalankan dengan
uv run uvicorn main:app --reload.
Aplikasi minimal
uv add fastapi uvicorn
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root() -> dict[str, str]:
return {"pesan": "halo"}
uv run uvicorn main:app --reload
Buka http://127.0.0.1:8000/docs — dokumentasi interaktif sudah jadi, tanpa konfigurasi.
Ide inti FastAPI: type hint bukan sekadar dokumentasi, tapi sumber kebenaran. Dari anotasi yang sama, FastAPI menurunkan validasi request, serialisasi respons, skema OpenAPI, dan halaman dokumentasi. Satu deklarasi, empat kegunaan.
Request body dengan Pydantic
from pydantic import BaseModel, Field
class ChatRequest(BaseModel):
pesan: str = Field(min_length=1, max_length=4000)
session_id: str
stream: bool = False
class ChatResponse(BaseModel):
balasan: str
token_terpakai: int
biaya_usd: float
@app.post("/chat")
async def chat(req: ChatRequest) -> ChatResponse:
hasil = await panggil_llm(req.pesan)
return ChatResponse(
balasan=hasil.teks,
token_terpakai=hasil.usage.output_tokens,
biaya_usd=hitung_biaya(hasil.usage),
)
Dari kode itu FastAPI otomatis:
- Mem-parse body JSON menjadi
ChatRequest - Mengembalikan
422dengan detail lengkap kalau tidak valid - Menserialisasi
ChatResponseke JSON - Mendokumentasikan kedua skema di
/docs
Path, query, dan body
from typing import Annotated
from fastapi import Query, Path
@app.get("/sesi/{session_id}/pesan")
async def daftar_pesan(
session_id: Annotated[str, Path(description="ID sesi")],
limit: Annotated[int, Query(ge=1, le=100)] = 20,
sebelum: str | None = None,
) -> list[Pesan]:
...
Cara FastAPI membedakannya:
| Aturan | Jadi |
|---|---|
| Nama parameter ada di path | Path parameter |
Tipe sederhana (int, str, bool) | Query parameter |
| Model Pydantic | Request body |
Dibungkus Depends() | Dependency |
Status code dan error
from fastapi import HTTPException, status
@app.post("/sesi", status_code=status.HTTP_201_CREATED)
async def buat_sesi(req: BuatSesi) -> Sesi:
...
@app.get("/sesi/{id}")
async def ambil_sesi(id: str) -> Sesi:
s = await db.cari(id)
if s is None:
raise HTTPException(status_code=404, detail=f"sesi {id} tidak ditemukan")
return s
Exception handler global
from fastapi import Request
from fastapi.responses import JSONResponse
import anthropic
@app.exception_handler(anthropic.RateLimitError)
async def rate_limit_handler(request: Request, exc: anthropic.RateLimitError):
return JSONResponse(
status_code=429,
content={"detail": "layanan sedang sibuk, coba beberapa saat lagi"},
headers={"Retry-After": "60"},
)
@app.exception_handler(RetrievalError)
async def retrieval_handler(request: Request, exc: RetrievalError):
logger.error("retrieval gagal: %s", exc)
return JSONResponse(status_code=503, content={"detail": "pencarian dokumen gagal"})
Handler global menjaga path operation tetap bersih. Alih-alih membungkus tiap endpoint dengan try/except yang sama, terjemahkan exception domain-mu jadi respons HTTP di satu tempat. Ini juga mencegah detail internal (traceback, nama tabel) bocor ke klien.
Struktur proyek: APIRouter
# src/myapp/routes/chat.py
from fastapi import APIRouter
router = APIRouter(prefix="/chat", tags=["chat"])
@router.post("")
async def chat(req: ChatRequest) -> ChatResponse: ...
@router.get("/riwayat/{session_id}")
async def riwayat(session_id: str) -> list[Pesan]: ...
# src/myapp/main.py
from fastapi import FastAPI
from myapp.routes import chat, dokumen
app = FastAPI(title="RAG Chatbot", version="1.0.0")
app.include_router(chat.router)
app.include_router(dokumen.router)
CORS
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins, # jangan ["*"] di production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Async atau sync?
@app.get("/a")
async def a():
return await client.messages.create(...) # ✅ I/O async
@app.get("/b")
def b():
return library_sinkron.proses() # ✅ FastAPI jalankan di threadpool
@app.get("/c")
async def c():
return library_sinkron.proses() # ❌ MEMBEKUKAN event loop
Aturannya sederhana: kalau fungsimu memanggil sesuatu yang blocking dan kamu tidak
membungkusnya, deklarasikan sebagai def biasa — FastAPI akan menjalankannya di
threadpool. Yang berbahaya adalah async def yang berisi panggilan blocking:
tidak ada yang menyelamatkanmu di situ, dan seluruh server ikut membeku.
Bagian tutorial yang perlu dibaca
| Bagian | Prioritas |
|---|---|
| First Steps, Path Parameters, Query Parameters | ✅ Baca cepat |
| Request Body, Response Model | ✅ Baca teliti |
| Handling Errors | ✅ Baca |
| Dependencies | ✅ Baca teliti — materi terpisah |
| Bigger Applications (APIRouter) | ✅ Baca saat proyek membesar |
| Background Tasks | ✅ Baca sekilas |
| Security / OAuth2 | ⚠️ Nanti, saat butuh autentikasi |
| SQL Databases | ⚠️ Fase 6 |
| Static Files, Templates | ❌ Lewati — API-mu melayani frontend terpisah |
Menjalankan
uv run uvicorn myapp.main:app --reload # development
uv run uvicorn myapp.main:app --host 0.0.0.0 --port 8000 --workers 4 # production
--reload hanya untuk development. Di production pakai beberapa worker,
dan biarkan orkestrator (Docker, ECS, Kubernetes) yang mengurus restart dan health check.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.