← Semua pembelajaran / Python untuk AI Engineer
Fase 5 · Bangun API dengan FastAPI

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:

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:

AturanJadi
Nama parameter ada di pathPath parameter
Tipe sederhana (int, str, bool)Query parameter
Model PydanticRequest 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

BagianPrioritas
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.