Pemrograman Python

FastAPI: Membuat REST API Modern dengan Python

TOKEN

Panduan lengkap membangun REST API modern menggunakan FastAPI β€” dari hello world hingga autentikasi JWT, integrasi SQLite, dan deployment ke production

1. Pengenalan FastAPI

FastAPI adalah framework web modern untuk membangun API dengan Python yang dirancang untuk kecepatan tinggi dan pengembangan yang efisien. Dibuat oleh SebastiΓ‘n RamΓ­rez pada tahun 2018, FastAPI dengan cepat menjadi salah satu framework Python paling populer untuk membangun REST API berkat performanya yang setara dengan Node.js dan Go.

FastAPI dibangun di atas dua pilar utama: Starlette untuk fitur web asynchronous dan Pydantic untuk validasi data otomatis. Dengan memanfaatkan type hints Python secara maksimal, FastAPI mampu memberikan autocompletion yang luar biasa di IDE, validasi data otomatis, dan dokumentasi API interaktif tanpa perlu konfigurasi tambahan.

Mengapa Memilih FastAPI?

Keunggulan Penjelasan
Performa TinggiBerbasis ASGI dan async β€” performa sebanding dengan Node.js dan Go
Validasi OtomatisPydantic memvalidasi request body secara otomatis berdasarkan type hints
Dokumentasi AutoSwagger UI dan ReDoc di-generate otomatis dari kode sumber
Type SafetyMemanfaatkan Python type hints untuk autocompletion dan deteksi error
Async NativeDukungan penuh async/await tanpa konfigurasi tambahan
Standar TerbukaBerbasis OpenAPI dan JSON Schema β€” kompatibel dengan semua tools API
Kurva Belajar RendahSintaks intuitif dan mirip Flask β€” mudah dipelajari untuk pemula

FastAPI vs Framework Lain

Aspek FastAPI Flask Django REST
Async Supportβœ… Native⚠️ Terbatas (v2.x)βœ… (v4.x)
Validasi Dataβœ… Otomatis (Pydantic)❌ Manual / Marshmallowβœ… Serializer
Auto Documentationβœ… Swagger + ReDoc❌ Perlu ekstensiβœ… drf-spectacular
Performa🟒 Sangat cepat🟑 Sedang🟑 Sedang
Kurva BelajarMudahSangat mudahCuram
Cocok untukAPI, MicroservicesWeb app kecil, API sederhanaFull-stack web app
Diagram: Arsitektur FastAPI
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      FASTAPI STACK                          β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Client      β”‚   β”‚   FastAPI   β”‚   β”‚  Database        β”‚ β”‚
β”‚  β”‚  (Browser /  │──►│  (Router)   │──►│  (SQLite /       β”‚ β”‚
β”‚  β”‚   Mobile /   │◄──│             │◄──│   PostgreSQL)    β”‚ β”‚
β”‚  β”‚   Frontend)  β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚                                β”‚
β”‚                           β–Ό                                β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚
β”‚                    β”‚  Pydantic   β”‚  ← Validasi data         β”‚
β”‚                    β”‚  (Schema)   β”‚                          β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                          β”‚
β”‚                           β–Ό                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              Starlette (ASGI Framework)              β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚
β”‚  β”‚  β”‚  Uvicorn β”‚ β”‚  Router  β”‚ β”‚Middleware β”‚ β”‚Dep.Inj.β”‚ β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Instalasi & Setup Environment

Sebelum mulai membangun API, kita perlu menyiapkan lingkungan kerja yang bersih menggunakan virtual environment. Ini adalah praktik terbaik agar dependensi proyek tidak konflik dengan package lain di sistem.

Langkah 1: Buat Virtual Environment

Bash
# Buat direktori proyek
mkdir fastapi-project && cd fastapi-project

# Buat virtual environment
python3 -m venv venv

# Aktifkan virtual environment
# Linux / macOS:
source venv/bin/activate

# Windows (PowerShell):
# .\venv\Scripts\Activate.ps1

# Windows (CMD):
# .\venv\Scripts\activate.bat

Langkah 2: Instalasi FastAPI dan Dependencies

Berikut daftar package yang dibutuhkan beserta fungsinya:

Package Fungsi Instalasi
fastapiFramework web utamapip install fastapi
uvicornASGI server untuk menjalankan FastAPIpip install "uvicorn[standard]"
pydanticValidasi dan serialisasi data (sudut tergabung dengan FastAPI)pip install pydantic
python-joseEncoding/decoding JWT tokenpip install python-jose[cryptography]
passlibHashing password (bcrypt)pip install "passlib[bcrypt]"
python-multipartForm data parsing untuk OAuth2pip install python-multipart
aiosqliteSQLite async driverpip install aiosqlite
Bash
# Instalasi semua dependencies sekaligus
pip install fastapi "uvicorn[standard]" \
  "python-jose[cryptography]" "passlib[bcrypt]" \
  python-multipart aiosqlite

# Simpan ke requirements.txt
pip freeze > requirements.txt

# Untuk reinstal di perangkat lain:
# pip install -r requirements.txt

Langkah 3: Struktur Proyek

Berikut struktur direktori yang akan kita gunakan untuk proyek ini:

File Structure
fastapi-project/
β”œβ”€β”€ venv/
β”œβ”€β”€ main.py              ← Entry point aplikasi
β”œβ”€β”€ database.py          ← Konfigurasi database SQLite
β”œβ”€β”€ models.py            ← Model SQLAlchemy/Pydantic
β”œβ”€β”€ schemas.py           ← Schema validasi Pydantic
β”œβ”€β”€ auth.py              ← Logika autentikasi JWT
β”œβ”€β”€ routers/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ items.py         ← Endpoint untuk items
β”‚   └── users.py         ← Endpoint untuk users
└── requirements.txt
πŸ’‘ Tips

Untuk tutorial ini, kita akan menulis semua kode dalam satu file main.py agar mudah dipahami. Untuk proyek nyata, sebaiknya pisahkan kode ke beberapa file seperti struktur di atas menggunakan APIRouter dari FastAPI.

3. Hello World API Pertama

Mari kita mulai dengan membuat API paling sederhana. Cukup buat file main.py dan jalankan dengan uvicorn. FastAPI mendokumentasikan API Anda secara otomatis β€” buka /docs untuk Swagger UI atau /redoc untuk ReDoc.

Python β€” main.py
"""
Hello World API dengan FastAPI
BeebaneLabs - https://beebanelabs.pages.dev
"""

from fastapi import FastAPI

# Membuat instance aplikasi FastAPI
app = FastAPI(
    title="BeebaneLabs API",
    description="Tutorial REST API dengan FastAPI",
    version="1.0.0"
)


# Endpoint root β€” GET /
@app.get("/")
async def root():
    """Endpoint sederhana untuk mengecek apakah API berjalan."""
    return {"message": "Hello World!", "status": "running"}


# Endpoint health check β€” GET /health
@app.get("/health")
async def health_check():
    """Endpoint health check untuk monitoring."""
    return {"status": "healthy", "service": "fastapi-tutorial"}


# Jalankan dengan:
# uvicorn main:app --reload

Menjalankan Server

Bash
# Jalankan dengan reload otomatis saat kode berubah
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Output:
# INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
# INFO:     Started reloader process
# INFO:     Started server process
# INFO:     Waiting for application startup.
# INFO:     Application startup complete.
Test dengan curl: $ curl http://localhost:8000/ {"message": "Hello World!", "status": "running"} $ curl http://localhost:8000/health {"status": "healthy", "service": "fastapi-tutorial"}
πŸ“– Dokumentasi Otomatis

Setelah server berjalan, buka browser dan akses:

  • Swagger UI: http://localhost:8000/docs β€” dokumentasi interaktif untuk mencoba API
  • ReDoc: http://localhost:8000/redoc β€” dokumentasi dalam format yang lebih formal
  • OpenAPI JSON: http://localhost:8000/openapi.json β€” schema OpenAPI mentah

4. Path Parameters & Query Parameters

FastAPI memudahkan definisi parameter pada endpoint. Path parameters adalah bagian dari URL (misalnya /items/42), sedangkan query parameters dikirim setelah tanda tanya (misalnya /items?limit=10).

Path Parameters

Path parameters ditulis langsung dalam definisi fungsi dengan tipe data yang eksplisit. FastAPI akan otomatis memvalidasi dan mengkonversi tipe data:

Python β€” Path Parameters
from fastapi import FastAPI, HTTPException

app = FastAPI()

# Simpan data di memory (sementara)
items_db = {
    1: {"id": 1, "name": "Laptop ASUS", "price": 12000000},
    2: {"id": 2, "name": "Keyboard Mekanik", "price": 850000},
    3: {"id": 3, "name": "Monitor 27 inch", "price": 4500000},
}


@app.get("/items/{item_id}")
async def get_item(item_id: int):
    """Mengambil item berdasarkan ID (path parameter)."""
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")
    return items_db[item_id]


# Contoh: GET /items/1
# Output: {"id": 1, "name": "Laptop ASUS", "price": 12000000}

# Contoh: GET /items/99
# Output: HTTP 404 β€” {"detail": "Item tidak ditemukan"}

Query Parameters

Query parameters ditentukan oleh argumen fungsi yang tidak ada di path. FastAPI otomatis mendeteksi mana yang path dan mana yang query:

Python β€” Query Parameters
from typing import Optional

@app.get("/items/")
async def list_items(
    skip: int = 0,              # Default: 0
    limit: int = 10,            # Default: 10
    search: Optional[str] = None,  # Opsional
    sort_by: str = "name"       # Default: "name"
):
    """
    Mengambil daftar item dengan pagination dan filter.
    Contoh: GET /items/?skip=0&limit=5&search=laptop&sort_by=price
    """
    results = list(items_db.values())

    # Filter berdasarkan search
    if search:
        results = [
            item for item in results
            if search.lower() in item["name"].lower()
        ]

    # Sorting
    if sort_by == "price":
        results.sort(key=lambda x: x["price"])
    else:
        results.sort(key=lambda x: x["name"])

    # Pagination
    total = len(results)
    results = results[skip : skip + limit]

    return {
        "total": total,
        "skip": skip,
        "limit": limit,
        "items": results
    }
Contoh Request: $ curl "http://localhost:8000/items/?search=laptop&limit=5" {"total": 1, "skip": 0, "limit": 5, "items": [{"id": 1, "name": "Laptop ASUS", "price": 12000000}]}
⚠️ Perhatian Urutan Deklarasi

FastAPI memproses endpoint secara berurutan. Jika Anda mendeklarasikan /items/ (tanpa parameter) SETELAH /items/{item_id}, maka URL /items/ akan dianggap sebagai path parameter dengan nilai string kosong. Selalu deklarasikan endpoint yang lebih spesifik terlebih dahulu.

5. Request Body & Validasi Pydantic

Pydantic adalah library validasi dan serialisasi data yang menjadi fondasi FastAPI. Dengan mendefinisikan model Pydantic, FastAPI otomatis memvalidasi request body, mengkonversi tipe data, dan menghasilkan dokumentasi OpenAPI.

Definisi Model Pydantic

Python β€” Schemas Pydantic
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime


class ItemCreate(BaseModel):
    """Schema untuk membuat item baru (request body)."""
    name: str = Field(..., min_length=2, max_length=100, 
                      contoh="Laptop ASUS ROG")
    price: int = Field(..., gt=0, contoh=15000000)
    description: Optional[str] = Field(None, max_length=500)
    is_available: bool = True

    class Config:
        # Contoh untuk dokumentasi Swagger
        json_schema_extra = {
            "contoh": {
                "name": "Laptop ASUS ROG",
                "price": 15000000,
                "description": "Laptop gaming high-end",
                "is_available": True
            }
        }


class ItemUpdate(BaseModel):
    """Schema untuk update item (semua field opsional)."""
    name: Optional[str] = Field(None, min_length=2, max_length=100)
    price: Optional[int] = Field(None, gt=0)
    description: Optional[str] = None
    is_available: Optional[bool] = None


class ItemResponse(BaseModel):
    """Schema untuk response (output) item."""
    id: int
    name: str
    price: int
    description: Optional[str] = None
    is_available: bool
    created_at: str

CRUD Endpoint dengan Pydantic

Python β€” CRUD Operations
from fastapi import FastAPI, HTTPException, status

app = FastAPI()
next_id = 4  # ID auto-increment sederhana

# Database sementara (di memory)
items_db = {
    1: {"id": 1, "name": "Laptop ASUS", "price": 12000000,
        "description": None, "is_available": True,
        "created_at": "2026-06-25T10:00:00"},
    2: {"id": 2, "name": "Keyboard Mekanik", "price": 850000,
        "description": "Cherry MX Red", "is_available": True,
        "created_at": "2026-06-25T10:05:00"},
}


# CREATE β€” POST /items
@app.post("/items/", response_model=ItemResponse, 
          status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemCreate):
    """Membuat item baru. FastAPI otomatis memvalidasi body."""
    global next_id

    new_item = {
        "id": next_id,
        "name": item.name,
        "price": item.price,
        "description": item.description,
        "is_available": item.is_available,
        "created_at": datetime.now().isoformat()
    }
    items_db[next_id] = new_item
    next_id += 1

    return new_item


# UPDATE β€” PUT /items/{item_id}
@app.put("/items/{item_id}", response_model=ItemResponse)
async def update_item(item_id: int, item: ItemUpdate):
    """Update item. Hanya field yang dikirim yang berubah."""
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")

    existing = items_db[item_id]

    # Update hanya field yang tidak None (partial update)
    update_data = item.model_dump(exclude_unset=True)
    for key, value in update_data.items():
        existing[key] = value

    return existing


# DELETE β€” DELETE /items/{item_id}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    """Menghapus item berdasarkan ID."""
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")
    del items_db[item_id]
    return None
Test CREATE dengan curl: $ curl -X POST http://localhost:8000/items/ \ -H "Content-Type: application/json" \ -d '{"name": "Mouse Logitech", "price": 350000}' {"id": 4, "name": "Mouse Logitech", "price": 350000, "description": null, "is_available": true, "created_at": "2026-06-25T14:30:00"}
πŸ’‘ Keunggulan Pydantic

Jika Anda mengirim {"price": "bukan_angka"}, FastAPI akan otomatis mengembalikan error 422 dengan pesan yang jelas: "Input should be a valid integer". Tidak perlu menulis kode validasi manual! Coba buka Swagger UI di /docs untuk mencoba langsung.

6. Integrasi Database SQLite

Selama ini kita menyimpan data di memory (variabel Python) yang hilang saat server restart. Sekarang kita akan menggunakan SQLite β€” database ringan berbasis file yang cocok untuk development dan aplikasi skala kecil. Kita akan menggunakan SQLAlchemy sebagai ORM (Object Relational Mapper).

Diagram: Alur Request dengan Database
  Client               FastAPI              SQLAlchemy           SQLite
    β”‚                    β”‚                      β”‚                   β”‚
    β”‚  POST /items/      β”‚                      β”‚                   β”‚
    β”‚ ─────────────────► β”‚                      β”‚                   β”‚
    β”‚                    β”‚  Pydantic validasi    β”‚                   β”‚
    β”‚                    β”‚ ──────── βœ“ ────────►  β”‚                   β”‚
    β”‚                    β”‚                      β”‚  INSERT INTO items β”‚
    β”‚                    β”‚                      β”‚ ─────────────────► β”‚
    β”‚                    β”‚                      β”‚  ◄── row id ────── β”‚
    β”‚                    β”‚  ◄──── obj ───────── β”‚                   β”‚
    β”‚  ◄── JSON ──────── β”‚                      β”‚                   β”‚

Konfigurasi Database

Python β€” database.py
"""
Konfigurasi Database SQLAlchemy + SQLite
BeebaneLabs - https://beebanelabs.pages.dev
"""

from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime

# SQLite database URL β€” file.db akan dibuat otomatis
DATABASE_URL = "sqlite:///./app.db"

# Buat engine SQLAlchemy
engine = create_engine(
    DATABASE_URL,
    connect_args={"check_same_thread": False}  # Diperlukan untuk SQLite
)

# Buat session factory
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Base class untuk model ORM
Base = declarative_base()


# ===== MODEL DATABASE =====
class Item(Base):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    name = Column(String(100), nullable=False, index=True)
    price = Column(Integer, nullable=False)
    description = Column(String(500), nullable=True)
    is_available = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.now)
    owner_id = Column(Integer, nullable=True)


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    username = Column(String(50), unique=True, nullable=False, index=True)
    email = Column(String(100), unique=True, nullable=False)
    hashed_password = Column(String(255), nullable=False)
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.now)


def init_db():
    """Membuat semua tabel di database jika belum ada."""
    Base.metadata.create_all(bind=engine)


def get_db():
    """Dependency injection untuk mendapatkan database session."""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Endpoint dengan Database

Python β€” main.py (dengan database)
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from database import init_db, get_db, Item, User
from schemas import ItemCreate, ItemResponse, ItemUpdate

app = FastAPI(title="BeebaneLabs API", version="1.0.0")


@app.on_event("startup")
async def startup():
    """Inisialisasi database saat server pertama kali start."""
    init_db()
    print("[OK] Database terinisialisasi")


# CREATE β€” POST /items/
@app.post("/items/", response_model=ItemResponse,
          status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
    """Membuat item baru dan menyimpan ke database SQLite."""
    db_item = Item(
        name=item.name,
        price=item.price,
        description=item.description,
        is_available=item.is_available
    )
    db.add(db_item)        # Tambahkan ke session
    db.commit()            # Simpan ke database
    db.refresh(db_item)    # Refresh untuk mendapatkan ID dan default values
    return db_item


# READ ALL β€” GET /items/
@app.get("/items/", response_model=list[ItemResponse])
async def list_items(
    skip: int = 0,
    limit: int = 10,
    search: str = None,
    db: Session = Depends(get_db)
):
    """Mengambil daftar item dari database dengan pagination."""
    query = db.query(Item)

    if search:
        query = query.filter(Item.name.contains(search))

    items = query.offset(skip).limit(limit).all()
    return items


# READ ONE β€” GET /items/{item_id}
@app.get("/items/{item_id}", response_model=ItemResponse)
async def get_item(item_id: int, db: Session = Depends(get_db)):
    """Mengambil satu item berdasarkan ID."""
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")
    return item


# UPDATE β€” PUT /items/{item_id}
@app.put("/items/{item_id}", response_model=ItemResponse)
async def update_item(
    item_id: int,
    item_update: ItemUpdate,
    db: Session = Depends(get_db)
):
    """Update item di database."""
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")

    update_data = item_update.model_dump(exclude_unset=True)
    for key, value in update_data.items():
        setattr(item, key, value)

    db.commit()
    db.refresh(item)
    return item


# DELETE β€” DELETE /items/{item_id}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int, db: Session = Depends(get_db)):
    """Menghapus item dari database."""
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item tidak ditemukan")

    db.delete(item)
    db.commit()
    return None
⚠️ Dependency Injection

Perhatikan penggunaan Depends(get_db) β€” ini adalah dependency injection FastAPI. Setiap request akan mendapatkan session database baru, dan session akan otomatis ditutup setelah request selesai. Ini mencegah kebocoran koneksi database.

7. Autentikasi JWT

JWT (JSON Web Token) adalah metode autentikasi stateless yang paling umum digunakan di REST API. Alurnya: user login dengan username/password β†’ server mengirim JWT token β†’ client menyertakan token di setiap request berikutnya.

Diagram: Alur Autentikasi JWT
  Client                    FastAPI
    β”‚                         β”‚
    β”‚  POST /auth/login       β”‚
    β”‚  {username, password}   β”‚
    β”‚ ──────────────────────► β”‚
    β”‚                         β”‚  1. Verifikasi password (bcrypt)
    β”‚                         β”‚  2. Generate JWT token
    β”‚                         β”‚  3. Return token
    β”‚  {access_token, type}   β”‚
    β”‚ ◄────────────────────── β”‚
    β”‚                         β”‚
    β”‚  GET /items/            β”‚
    β”‚  Authorization:         β”‚
    β”‚  Bearer <jwt_token>     β”‚
    β”‚ ──────────────────────► β”‚
    β”‚                         β”‚  1. Decode JWT
    β”‚                         β”‚  2. Verifikasi expiry
    β”‚                         β”‚  3. Ambil data user
    β”‚  [data items]           β”‚
    β”‚ ◄────────────────────── β”‚

Modul Autentikasi

Python β€” auth.py
"""
Modul Autentikasi JWT
BeebaneLabs - https://beebanelabs.pages.dev
"""

from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from database import get_db, User

# ===== KONFIGURASI =====
SECRET_KEY = "ganti-dengan-kunci-rahasia-yang-panjang-dan-acak"  # Gunakan env var!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# Password hashing dengan bcrypt
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Skema OAuth2 β€” menunjuk ke endpoint login
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")


def hash_password(password: str) -> str:
    """Meng-hash password menggunakan bcrypt."""
    return pwd_context.hash(password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Memverifikasi password terhadap hash."""
    return pwd_context.verify(plain_password, hashed_password)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Membuat JWT access token."""
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
):
    """Dependency: Mendapatkan user yang sedang login dari JWT token."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Token tidak valid",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        # Decode JWT token
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    # Cari user di database
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception

    return user

Endpoint Login & Registrasi

Python β€” auth endpoints (tambahkan di main.py)
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from database import get_db, User
from auth import (
    hash_password, verify_password,
    create_access_token, get_current_user,
    ACCESS_TOKEN_EXPIRE_MINUTES
)
from pydantic import BaseModel, EmailStr
from datetime import timedelta

router = APIRouter(prefix="/auth", tags=["Autentikasi"])


class UserCreate(BaseModel):
    """Schema untuk registrasi user baru."""
    username: str
    email: str
    password: str


class Token(BaseModel):
    """Schema response JWT token."""
    access_token: str
    token_type: str


@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register(user_data: UserCreate, db: Session = Depends(get_db)):
    """Registrasi user baru."""
    # Cek apakah username sudah ada
    existing = db.query(User).filter(
        User.username == user_data.username
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Username sudah digunakan")

    # Buat user baru
    new_user = User(
        username=user_data.username,
        email=user_data.email,
        hashed_password=hash_password(user_data.password)
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    return {"message": "Registrasi berhasil", "user_id": new_user.id}


@router.post("/login", response_model=Token)
async def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    """Login dan mendapatkan JWT token."""
    # Cari user di database
    user = db.query(User).filter(
        User.username == form_data.username
    ).first()

    # Verifikasi user dan password
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Username atau password salah",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # Buat JWT token
    access_token = create_access_token(
        data={"sub": user.username},
        expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )

    return {"access_token": access_token, "token_type": "bearer"}


@router.get("/me")
async def get_my_profile(current_user: User = Depends(get_current_user)):
    """Mendapatkan profil user yang sedang login (protected endpoint)."""
    return {
        "id": current_user.id,
        "username": current_user.username,
        "email": current_user.email,
        "is_active": current_user.is_active
    }
Test Autentikasi: # 1. Register $ curl -X POST http://localhost:8000/auth/register \ -H "Content-Type: application/json" \ -d '{"username": "beebane", "email": "bee@test.com", "password": "rahasia123"}' # 2. Login $ curl -X POST http://localhost:8000/auth/login \ -d "username=beebane&password=rahasia123" {"access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer"} # 3. Akses Protected Endpoint $ curl http://localhost:8000/auth/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." {"id": 1, "username": "beebane", "email": "bee@test.com", "is_active": true}
🚫 Keamanan SECRET_KEY

JANGAN PERNAH hardcode SECRET_KEY di kode sumber untuk production! Gunakan environment variable: os.environ.get("SECRET_KEY") atau file .env yang tidak di-commit ke Git. Kunci ini digunakan untuk menandatangani semua JWT token β€” jika bocor, siapapun bisa membuat token palsu.

8. CORS & Middleware

CORS (Cross-Origin Resource Sharing) adalah mekanisme keamanan browser yang membatasi request dari domain lain. Saat frontend (misalnya React di localhost:3000) memanggil API di localhost:8000, browser akan memblokir request tanpa konfigurasi CORS yang tepat.

Konfigurasi CORS

Python β€” CORS Middleware
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import time

app = FastAPI()

# ===== CORS MIDDLEWARE =====
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",     # React dev server
        "http://localhost:5173",     # Vite dev server
        "https://beebanelabs.pages.dev",  # Production frontend
    ],
    allow_credentials=True,   # Izinkan cookies
    allow_methods=["*"],      # Izinkan semua HTTP methods
    allow_headers=["*"],      # Izinkan semua headers
)

Custom Middleware

Middleware adalah fungsi yang dijalankan pada setiap request sebelum dan sesudah endpoint diproses. Cocok untuk logging, timing, dan error handling global:

Python β€” Custom Middleware
# ===== MIDDLEWARE: REQUEST LOGGING & TIMING =====
@app.middleware("http")
async def log_requests(request: Request, call_next):
    """Middleware untuk logging dan mengukur waktu response."""
    start_time = time.time()

    # Proses request
    response = await call_next(request)

    # Hitung durasi
    duration = round((time.time() - start_time) * 1000, 2)

    # Log informasi request
    print(
        f"[{request.method}] {request.url.path} "
        f"β†’ {response.status_code} ({duration}ms)"
    )

    # Tambahkan header custom
    response.headers["X-Process-Time"] = f"{duration}ms"
    response.headers["X-API-Version"] = "1.0.0"

    return response


# ===== MIDDLEWARE: GLOBAL ERROR HANDLING =====
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    """Menangkap semua error yang tidak tertangani."""
    print(f"[ERROR] {request.method} {request.url.path}: {str(exc)}")
    return JSONResponse(
        status_code=500,
        content={
            "detail": "Terjadi kesalahan internal server",
            "error": str(exc)  # Hapus di production!
        }
    )
Middleware Fungsi Kapan Digunakan
CORSMiddlewareMengatur CORS policy untuk akses lintas originSelalu, jika ada frontend terpisah
TrustedHostMiddlewareMembatasi host yang diizinkanProduction untuk mencegah Host header injection
GZipMiddlewareKompresi response untuk menghemat bandwidthSaat response body besar
Custom TimingMengukur waktu proses setiap requestMonitoring performa API
Rate LimitingMembatasi jumlah request per IPMencegah abuse dan DDoS
πŸ“– Urutan Middleware

Middleware dieksekusi berdasarkan urutan pendaftaran β€” middleware yang didaftarkan pertama akan membungkus request paling luar. Artinya, middleware pertama akan dijalankan pertama saat request masuk dan terakhir saat response keluar. Atur urutan middleware sesuai prioritas keamanan Anda.

9. Deployment ke Production

Saat meng-deploy FastAPI ke production, ada beberapa hal yang harus dikonfigurasi: penggunaan Gunicorn sebagai process manager dengan Uvicorn workers, pengaturan environment variables, dan menggunakan reverse proxy seperti Nginx.

Langkah 1: Persiapan Production

Bash β€” requirements-prod.txt
# requirements-prod.txt
fastapi==0.115.0
uvicorn[standard]==0.30.0
gunicorn==22.0.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.9
aiosqlite==0.20.0

Langkah 2: Jalankan dengan Gunicorn + Uvicorn Workers

Bash
# Jalankan dengan Gunicorn (4 worker processes)
gunicorn main:app \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \
    --access-logfile - \
    --error-logfile -

# Alternatif: langsung dengan Uvicorn (satu proses)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

Langkah 3: Konfigurasi Systemd Service (Linux)

INI β€” /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI Application
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/fastapi-project
Environment="PATH=/opt/fastapi-project/venv/bin"
Environment="SECRET_KEY=kunci-rahasia-produksi-anda"
Environment="DATABASE_URL=sqlite:///./prod.db"
ExecStart=/opt/fastapi-project/venv/bin/gunicorn main:app \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 127.0.0.1:8000
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Langkah 4: Nginx Reverse Proxy

Nginx β€” /etc/nginx/sites-available/fastapi
server {
    listen 80;
    server_name api.beebanelabs.pages.dev;

    # Redirect ke HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.beebanelabs.pages.dev;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # Proxy ke FastAPI (Gunicorn + Uvicorn)
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
    location /api/ {
        limit_req zone=api burst=50 nodelay;
        proxy_pass http://127.0.0.1:8000;
    }
}
πŸš€ Alternatif Deployment Mudah

Jika tidak ingin mengelola server sendiri, FastAPI juga bisa di-deploy ke:

  • Railway / Render β€” PaaS dengan free tier, cukup push ke Git
  • Docker β€” Containerization untuk portabilitas lintas platform
  • Vercel / Cloudflare Workers β€” Serverless deployment
  • AWS Lambda β€” Dengan Mangum adapter untuk ASGI

Dockerfile (Opsional)

Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Copy dan install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy source code
COPY . .

# Expose port
EXPOSE 8000

# Jalankan dengan Uvicorn
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Bash β€” Build & Run Docker
# Build image
docker build -t fastapi-app .

# Jalankan container
docker run -d -p 8000:8000 \
    -e SECRET_KEY="kunci-rahasia-anda" \
    --name fastapi-app \
    fastapi-app

# Cek status
docker ps
docker logs fastapi-app

10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang FastAPI:

Pertanyaan 1: Apa dua library utama yang menjadi fondasi FastAPI?

a) Django dan Flask
b) Starlette dan Pydantic
c) SQLAlchemy dan Alembic
d) Uvicorn dan Gunicorn

Pertanyaan 2: URL apa yang digunakan untuk mengakses dokumentasi Swagger UI secara default di FastAPI?

a) /swagger
b) /api/docs
c) /docs
d) /documentation

Pertanyaan 3: Metode apa yang digunakan FastAPI untuk menyediakan database session ke endpoint?

a) Global variable
b) Singleton pattern
c) Dependency Injection dengan Depends()
d) Decorator @database

Pertanyaan 4: Apa yang terjadi jika Pydantic menerima request body dengan tipe data yang salah?

a) Program crash dan server mati
b) Data tetap diproses dengan nilai default
c) Error 422 dengan pesan validasi yang jelas
d) Error 500 Internal Server Error

Pertanyaan 5: Manfaat utama menggunakan CORS middleware dalam FastAPI adalah?

a) Mempercepat response time API
b) Mengenkripsi semua data yang dikirim
c) Mengizinkan frontend dari domain lain mengakses API
d) Mengompresi ukuran response agar lebih kecil