Lama sudah tidak membuat materi pembelajaran, kali ini aku datang dengan membawa materi daging yang bener-bener daging. Anjai. Oke kali ini aku akan membahas dan memberikan suatu sedikit tutorial tentang backend, yaitu FastAPI.
Artikel ini ditujukan untuk pemula yang ingin mulai belajar backend menggunakan Python maupun developer yang ingin mencoba framework baru selain Django atau Flask.
Table of Contents
- Table of Contents
- Kenapa FastAPI?
- Domain Driven Architecture
- Setup Proyek FastAPI dengan Domain Driven Architecture
- Eksekusi Proyek
- Step 5: Testing API
- Kesimpulan
Kenapa FastAPI?
Sebelum mulai, mari kita kenalan dulu dengan FastAPI.
FastAPI merupakan framework backend modern yang dibangun di atas Starlette untuk web framework dan Pydantic untuk validasi data. Kombinasi tersebut membuat FastAPI menjadi sangat cepat sekaligus nyaman digunakan.
Beberapa alasan mengapa banyak developer memilih FastAPI:
- Performa tinggi karena berjalan di atas ASGI. ASGI adalah (Asynchronous Server Gateway Interface) yaitu standar komunikasi antara aplikasi Python dan web server yang dirancang untuk mendukung pemrosesan asynchronous. ASGI merupakan penerus dari WSGI yang hanya mendukung aplikasi sinkron (synchronous).
- Dokumentasi API otomatis.
- Validasi request menggunakan type hint Python.
- Mudah dipelajari.
- Sangat cocok untuk membangun REST API maupun microservices.
- Mendukung asynchronous programming (async/await) secara native.
Kalau kamu sudah familiar dengan Python, belajar FastAPI akan terasa mudah.
Domain Driven Architecture
Pada materi kali ini saya menggunakan pendekatan DDD (Domain-Driven Design) atau yang sering dikenal sebagai Domain Driven Architecture. Seperti apa sih arsitektur ini?
Simpelnya gini, ketika kita pertama kali membuat aplikasi, biasanya struktur project masih sederhana. Contoh.
fastapi-apps/
├── models/
├── schemas/
├── services/
├── routers/
├── database.py
└── main.py
Struktur tersebut sudah cukup baik untuk aplikasi sederhana. Namun, ketika aplikasi mulai berkembang—misalnya aplikasi e-commerce yang memiliki fitur products, categories, cart, orders, payments, hingga authentication—setiap fitur akan memiliki model, schema, service, dan router masing-masing.
Akibatnya, ketika ingin mengembangkan satu fitur, misalnya Order, kita harus berpindah-pindah folder sehingga project menjadi semakin sulit dinavigasi.
Dengan DDD, setiap fitur dikelompokkan ke dalam satu folder sehingga seluruh kode yang berkaitan dengan fitur tersebut berada di tempat yang sama.
fastapi-apps/
├── domains/
│ ├── auth/
│ ├── products/
│ ├── categories/
│ ├── orders/
│ └── payments/
├── core/
├── config/
└── main.py
Melalui pendekatan ini, struktur project menjadi lebih rapi, mudah dipahami, dan lebih mudah dikembangkan seiring bertambahnya kompleksitas aplikasi. Seluruh materi pada seri ini juga akan menggunakan struktur tersebut agar sejak awal kita terbiasa membangun project yang terorganisir.
Setup Proyek FastAPI dengan Domain Driven Architecture
Struktur folder
app/
├── modules/
│ └── users/
│ ├── user_router.py
│ ├── user_service.py
│ ├── user_models.py
│ └── user_schemas.py
├── db/
│ └── conn.py
├── .env
├── __init__.py
└── main.py
Buat folder kosong
mkdir fastapi-apps # (ini bebas mau kalian beri nama apa)
Setup python virtual environment
python3 -m venv .venv
Lalu masuk ke virtual environment
source .venv/bin/activate
Install Dependency yang dibutuhkan
pip install "fastapi[standard]" SQLAlchemy cuid2 "psycopg[binary]"
Kita menggunakan SQLAlchemy untuk database engine, dan psycopg untuk koneksi ke postgresql (kita menggunakan postgresql untuk database)
Buat file .env untuk menyimpan environment kita
touch .env
Lalu isikan
DATABASE_URL="postgresql+psycopg://[user]:[password]@localhost:5432/[db-name]"
Eksekusi Proyek
Pada bagian ini kita mulai mengeksekusi proyeknya, step pertama yaitu konfigurasi koneksi db
Step 1: Setup DB Connection
Pada file conn.py kita bisa taruh kode berikut
import os
import dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
dotenv.load_dotenv()
# URL
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+psycopg://[user]:[password]@localhost:5432/[db-name]")
# engine
engine = create_engine(DATABASE_URL)
# session maker
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# base for models
Base = declarative_base()
# dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Koneksi ini digunakan sebagai konfigurasi utama database pada aplikasi. Nantinya seluruh module yang membutuhkan akses database akan menggunakan konfigurasi ini.
create_engine()digunakan untuk membuat koneksi ke database berdasarkanDATABASE_URL.SessionLocaldigunakan untuk membuat session yang akan dipakai saat menjalankan operasi database seperti SELECT, INSERT, UPDATE, dan DELETE.Basemenjadi parent class untuk seluruh model SQLAlchemy yang akan kita buat.get_db()merupakan dependency FastAPI yang bertugas membuat session di awal request dan menutupnya secara otomatis setelah request selesai diproses.
Dengan konfigurasi ini, kita tidak perlu lagi membuat atau menutup koneksi database secara manual pada setiap endpoint.
Step 2: Membuat Schema dan Model users
Apa itu schema dan Model? Schema digunakan untuk memvalidasi data request dan membentuk response API, sedangkan Model adalah representasi struktur tabel di database
-
Buat file
user_schema.pydi foldermodules/users/from datetime import datetime from pydantic import BaseModel, EmailStr class UserBase(BaseModel): name: str username: str email: EmailStr class UserCreate(UserBase): password: str class UserUpdate(BaseModel): name: str | None = None email: EmailStr | None = None password: str | None = None class UserResponse(UserBase): id: str created_at: datetime updated_at: datetime # allow population by attribute name (e.g. from ORM objects) model_config = { "from_attributes": True }Pada Schema, kita mendefinisikan struktur data yang diterima dan dikembalikan oleh API.
UserCreatedigunakan saat membuat user baru.UserUpdatedigunakan saat mengubah data user.UserResponsedigunakan sebagai format response yang dikirim ke client.
-
Buat file
user_model.pydi foldermodules/users/from sqlalchemy import Column, String, DateTime from sqlalchemy.sql import func from cuid2 import Cuid from app.db.conn import Base # model class User(Base): __tablename__ = "users" id = Column(String, primary_key=True, default=lambda: Cuid().generate()) name = Column(String, nullable=False) username = Column(String, unique=True, nullable=False) email = Column(String, unique=True, nullable=False) password = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)Sementara itu, Model mendefinisikan struktur tabel users pada database menggunakan SQLAlchemy. Field seperti id, created_at, dan updated_at akan dikelola secara otomatis, sedangkan username dan email diberi constraint unique agar tidak ada data yang duplikat.
Step 3: Service dan Route user
Nah ini bagian paling krusial dari aplikasi ini, yaitu Service dan Route. Service berisi seluruh logika bisnis aplikasi, sedangkan Route bertugas menerima request dari client, memanggil service yang sesuai, lalu mengembalikan response.
-
Buat file
user_service.pydi foldermodules/users/from fastapi import HTTPException # ambil session dari sqlalchemy ORM from sqlalchemy.orm import Session # import model User from app.modules.users.user_model import User # import model Schema from app.modules.users.user_schema import UserCreate, UserUpdate # create def create_user(user: UserCreate, db: Session): payload = User(**user.model_dump()) db.add(payload) db.commit() db.refresh(payload) return payload # find all def find_all_user(db: Session): users = db.query(User).all() if not users: raise HTTPException(status_code=404, detail="Users not found") return users # find one def find_one_user(id: str, db: Session): user = db.query(User).filter(User.id == id).first() if not user: raise HTTPException(status_code=404, detail="User not found") return user # update def update_user(id: str, user: UserUpdate, db: Session): payload = find_one_user(id, db) if not payload: raise HTTPException(status_code=404, detail="User not found") update_data = user.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(payload, key, value) db.commit() db.refresh(payload) return payload # delete def delete_user(id: str, db: Session): payload = find_one_user(id, db) if not payload: raise HTTPException(status_code=404, detail="User not found") db.delete(payload) db.commit() -
Buat file
user_route.pydi foldermodules/users/from fastapi import APIRouter, Depends, status from sqlalchemy.orm import Session from app.db.conn import get_db # import schema from app.modules.users.user_schema import UserCreate, UserUpdate, UserResponse # import service from app.modules.users.user_service import create_user, find_all_user, find_one_user, update_user, delete_user # inisialisasi prefix untuk users router = APIRouter(prefix="/users", tags=["users"]) # create @router.post("/", response_model=UserResponse) async def create(user: UserCreate, db: Session = Depends(get_db)): return create_user(user, db) # find all @router.get("/", response_model=list[UserResponse]) async def find_all(db: Session = Depends(get_db)): return find_all_user(db) # find one @router.get("/{id}", response_model=UserResponse) async def find_one(id: str, db: Session = Depends(get_db)): return find_one_user(id, db) # update @router.patch("/{id}", response_model=UserResponse) async def update(id: str, user: UserUpdate, db: Session = Depends(get_db)): return update_user(id, user, db) # delete @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) async def delete(id: str, db: Session = Depends(get_db)): delete_user(id, db) return status.HTTP_204_NO_CONTENT
Step 4: Entry point
Setelah semua sudah selesai, kita akan membuat entry point dari apps ini. Kita buat file main.py dan isikan ini
import time
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.db.conn import engine, SessionLocal
# users
from app.modules.users.user_route import router as user_router
from app.modules.users.user_model import User
app = FastAPI()
# Allow CORS for all origins, methods, and headers
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# create tables
User.metadata.create_all(bind=engine)
@app.get("/")
async def health_check():
return {"message": f"Health checked at {int(time.time() * 1000)} ms", "status": 200}
app.include_router(user_router)
@app.on_event("startup")
async def startup_event():
db = SessionLocal()
print("App started at", int(time.time() * 1000), "ms")
db.close()
Step 5: Testing API
Pada tahap ini kita uji API yang sudah kita buat, kita bisa menggunakan postman, REST Client, tapi FastAPI sendiri sudah menyediakan UI untuk testing menggunakan swagger miliknya yang dapat kalian akses, pertama jalankan dulu aplikasinya
fastapi dev
Lalu buka URL http://localhost:8000/docs untuk membuka swagger UI dan langsung dapat kalian test untuk API tersebut.
Kesimpulan
Sampai tahap ini kita telah berhasil membangun sebuah REST API sederhana menggunakan FastAPI dengan struktur Domain Driven Architecture (DDD). Mulai dari menyiapkan project, mengonfigurasi koneksi database, membuat Schema dan Model, hingga mengimplementasikan Service dan Route untuk operasi CRUD.
Tentu saja aplikasi ini masih sangat sederhana. Masih banyak hal yang perlu ditambahkan agar siap digunakan pada lingkungan production, seperti autentikasi, otorisasi, validasi yang lebih kompleks, logging, testing, migrasi database, hingga deployment.
Namun, fondasi yang kita bangun pada artikel ini sudah cukup baik sebagai titik awal untuk mengembangkan aplikasi FastAPI yang lebih besar dan terstruktur.
Pada artikel selanjutnya kita akan membahas beberapa topik lanjutan, seperti:
- Hashing password menggunakan bcrypt.
- Authentication menggunakan JWT.
- Database migration dengan Alembic.
- Pagination, filtering, dan searching.
- Upload file.
- Deployment menggunakan Docker dan Nginx.
Semoga artikel ini bermanfaat dan sampai jumpa di seri Learn FastAPI with Me berikutnya. 🚀
Disclaimer
Agar artikel ini tetap fokus pada dasar-dasar FastAPI, ada beberapa hal yang sengaja tidak dibahas secara mendalam. Salah satunya adalah konfigurasi CORS (Cross-Origin Resource Sharing). Jika backend akan diakses oleh aplikasi frontend (misalnya React, Vue, Next.js, atau Flutter Web), maka kamu perlu menambahkan middleware CORS agar browser mengizinkan komunikasi antara frontend dan backend. Konfigurasi tersebut akan dibahas pada artikel tersendiri bersamaan dengan pembahasan deployment dan keamanan aplikasi.