Selamat, kamu sudah punya API yang jalan end-to-end — dari endpoint sederhana sampai CRUD lengkap dengan validasi dan database sungguhan. Di artikel penutup ini, kita bahas hal-hal yang sering bikin pemula terjebak, cara menulis test yang efektif, dan ke mana kamu bisa lanjut belajar setelah ini.
Kesalahan Umum Pemula
async def tapi memanggil kode blocking di dalamnya. Ini yang paling sering bikin performa aplikasi FastAPI jadi lebih lambat, bukan lebih cepat. Kalau di dalam fungsi kita ada panggilan library sinkron (misalnya driver database yang tidak mendukung async, atau time.sleep()), itu akan memblokir seluruh event loop — bukan cuma request itu, tapi SEMUA request lain yang sedang ditangani server. Kalau ragu, pakai def biasa; FastAPI otomatis menjalankannya di thread pool terpisah.response_model. Tanpa response_model, gampang tidak sengaja mengembalikan field internal yang seharusnya tidak pernah keluar ke publik (password hash, catatan internal, dan sejenisnya). Selalu definisikan skema response secara eksplisit, seperti TaskRead yang kita pakai di Bagian 4.pydantic-settings untuk membaca konfigurasi dari environment variable dengan validasi tipe yang sama seperti model biasa.500 Internal Server Error generik. Kalau ada kemungkinan error yang bisa diprediksi (data tidak ditemukan, konflik, tidak punya izin), tangani eksplisit dengan HTTPException dan status code yang tepat — jangan biarkan traceback mentah bocor ke konsumen API.Testing dengan Pytest dan TestClient
FastAPI menyediakan TestClient yang memungkinkan kita memanggil endpoint tanpa benar-benar menjalankan server — cocok dipakai bersama pytest. Untuk aplikasi yang terhubung ke database, pola standarnya adalah override dependency get_session dengan database in-memory khusus testing, supaya test tidak menyentuh tasks.db sungguhan:
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel.pool import StaticPool
from app.database import get_session
from app.main import app
@pytest.fixture(name="session")
def session_fixture():
engine = create_engine(
"sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
@pytest.fixture(name="client")
def client_fixture(session: Session):
def get_session_override():
return session
app.dependency_overrides[get_session] = get_session_override
with TestClient(app) as client:
yield client
app.dependency_overrides.clear()
def test_create_task(client: TestClient):
response = client.post("/api/tasks", json={"title": "Belajar FastAPI"})
assert response.status_code == 201
assert response.json()["title"] == "Belajar FastAPI"
Poin kuncinya: app.dependency_overrides[get_session] = get_session_override mengganti dependency asli dengan versi test, tanpa mengubah satu baris pun kode endpoint. Inilah manfaat konkret dependency injection yang dibahas di Bagian 5 — desain yang memisahkan "cara mendapatkan resource" dari "logic endpoint" membuat kode jadi mudah diuji secara terisolasi.
Jalankan dengan:
pip install -r requirements.txt
pytest -v
Checklist Debug Ketika Error
Kalau API kamu mengembalikan error dan bingung mulai dari mana, coba urutan ini:
/docs, bukan cuma status code-nya. Response 422 biasanya sudah menyertakan field mana yang gagal validasi dan kenapa — jangan langsung menebak.422) dari error business logic (404, 400, dst). Kalau errornya 422, masalahnya di bentuk data yang dikirim vs skema Pydantic. Kalau 404/400/dst yang kita lempar manual, cek logic di path operation function-nya./docs atau curl dulu, sebelum curiga ke kode frontend/klien. Ini mengisolasi apakah masalahnya di API atau di sisi pemanggil.id yang memang tidak ada, bukan bug di endpoint-nya.session.add() → session.commit() → session.refresh(). Lupa commit() adalah penyebab umum data "kelihatan tersimpan" di kode tapi tidak benar-benar masuk database.Merapikan Konfigurasi dengan pydantic-settings
Sebagai langkah lanjutan dari yang dibahas di best practice #4, pydantic-settings memberi pola yang konsisten untuk membaca environment variable dengan validasi tipe:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str = "sqlite:///./tasks.db"
debug: bool = False
settings = Settings()
Nilai-nilai ini otomatis dibaca dari environment variable (DATABASE_URL, DEBUG) atau file .env, dengan validasi tipe yang sama seperti model Pydantic biasa — kalau DEBUG diisi nilai yang bukan boolean, aplikasi gagal start dengan pesan error yang jelas, bukan bug tersembunyi yang baru ketahuan belakangan.
Roadmap Lanjutan — Mau Belajar Apa Lagi?
Seri ini sengaja fokus ke dasar-dasar biar kamu punya fondasi kuat. Kalau mau lanjut lebih dalam, ini beberapa arah yang bisa dieksplorasi:
fastapi.security, plus dependency injection yang sama persis untuk melindungi endpoint tertentu (Depends(get_current_user)).asyncpg untuk PostgreSQL), dan ubah endpoint jadi async def sungguhan.Task berubah di production (nambah kolom, dst.), butuh cara terkontrol untuk mengubah struktur tabel tanpa kehilangan data. Alembic adalah tool standar untuk ini di ekosistem SQLAlchemy/SQLModel.BackgroundTasks bawaan untuk kasus sederhana, dan bisa naik ke Celery/RQ untuk kasus yang lebih berat.Kabar baiknya, karena kamu sudah paham konsep dasarnya — path operation, Pydantic model, dependency injection — mempelajari fitur FastAPI lain nantinya tinggal soal memperdalam pola yang sama, bukan belajar dari nol lagi.
Penutup
Kita sudah menempuh perjalanan dari "kenapa FastAPI penting", memahami anatomi aplikasi, praktik membuat endpoint pertama, memperketat validasi data, sampai menyambungkan ke database sungguhan lengkap dengan dependency injection. Yang paling penting, kamu sekarang punya template API nyata yang bisa dipakai ulang dan dikembangkan untuk proyekmu sendiri.
Terima kasih sudah mengikuti seri FastAPI for Beginners ini. Selamat mencoba di proyek kamu sendiri!