Lewati ke konten
Kembali ke Blog

Cara Membuat API dengan FastAPI Python

ยท ยท 8 menit baca

FastAPI adalah framework Python modern untuk membuat API dengan performa tinggi. Mari pelajari dari dasar.

Apa itu FastAPI?

Keunggulan FastAPI

- Sangat cepat (setara Node.js/Go)
- Auto-generate API documentation
- Type hints dan validation
- Async support
- Easy to learn
- Production ready

Setup Project

Install FastAPI

# Buat virtual environment
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate  # Windows

pip install fastapi uvicorn[standard]

Optional dependencies

pip install python-multipart # form data pip install python-jose[cryptography] # JWT pip install passlib[bcrypt] # password hashing pip install sqlalchemy # database ORM

Project Structure

project/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ main.py
โ”‚   โ”œโ”€โ”€ config.py
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ””โ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ schemas/
โ”‚   โ”‚   โ””โ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ routers/
โ”‚   โ”‚   โ””โ”€โ”€ __init__.py
โ”‚   โ””โ”€โ”€ database.py
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ .env

Basic API

Hello World

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/") def read_root(): return {"message": "Hello World"}

@app.get("/items/{item_id}") def read_item(item_id: int, q: str = None): return {"item_id": item_id, "q": q}

Run Server

# Development
uvicorn main:app --reload

Production

uvicorn main:app --host 0.0.0.0 --port 8000

Access:

http://localhost:8000

http://localhost:8000/docs (Swagger UI)

http://localhost:8000/redoc (ReDoc)

Path Parameters

Basic Path Parameters

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

@app.get("/files/{file_path:path}") def get_file(file_path: str): return {"file_path": file_path}

Enum Path Parameters

from enum import Enum

class ModelName(str, Enum): alexnet = "alexnet" resnet = "resnet" lenet = "lenet"

@app.get("/models/{model_name}") def get_model(model_name: ModelName): if model_name is ModelName.alexnet: return {"model_name": model_name, "message": "Deep Learning FTW!"} return {"model_name": model_name}

Query Parameters

Basic Query Parameters

@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

Optional parameters

@app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): if q: return {"item_id": item_id, "q": q} return {"item_id": item_id}

Required Query Parameters

@app.get("/search/")
def search(q: str):  # Required
    return {"query": q}

Request Body

Pydantic Models

from pydantic import BaseModel, EmailStr
from typing import Optional

class UserCreate(BaseModel): name: str email: EmailStr password: str age: Optional[int] = None

class UserResponse(BaseModel): id: int name: str email: EmailStr

class Config:
    from_attributes = True

@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate):

Save to database...

return {"id": 1, "name": user.name, "email": user.email}

Validation

from pydantic import BaseModel, Field, validator

class Item(BaseModel): name: str = Field(..., min_length=1, max_length=100) price: float = Field(..., gt=0) quantity: int = Field(default=1, ge=0, le=1000) description: str | None = Field(default=None, max_length=500)

@validator('name')
def name_must_be_valid(cls, v):
    if not v.strip():
        raise ValueError('Name cannot be empty')
    return v.strip()

@app.post("/items/")
def create_item(item: Item):
return item

Response Models

Multiple Response Models

from typing import List

class ItemBase(BaseModel): name: str price: float

class ItemCreate(ItemBase): pass

class ItemResponse(ItemBase): id: int

class Config:
    from_attributes = True

@app.get("/items/", response_model=List[ItemResponse])
def get_items():
return [
{"id": 1, "name": "Item 1", "price": 100},
{"id": 2, "name": "Item 2", "price": 200}
]

Status Codes

from fastapi import status

@app.post("/items/", status_code=status.HTTP_201_CREATED) def create_item(item: Item): return item

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_item(item_id: int): return None

Routers

Organize Routes

# routers/users.py
from fastapi import APIRouter

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

@router.get("/") def get_users(): return [{"id": 1, "name": "Budi"}]

@router.get("/{user_id}") def get_user(user_id: int): return {"id": user_id, "name": "Budi"}

main.py

from fastapi import FastAPI from routers import users

app = FastAPI() app.include_router(users.router)

Database Integration

SQLAlchemy Setup

# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"

engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

def get_db(): db = SessionLocal() try: yield db finally: db.close()

Models

# models/user.py
from sqlalchemy import Column, Integer, String
from database import Base

class User(Base): tablename = "users"

id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)

CRUD Operations

from fastapi import Depends
from sqlalchemy.orm import Session
from database import get_db
from models.user import User

@app.get("/users/") def get_users(db: Session = Depends(get_db)): users = db.query(User).all() return users

@app.post("/users/") def create_user(user: UserCreate, db: Session = Depends(get_db)): db_user = User( name=user.name, email=user.email, hashed_password=hash_password(user.password) ) db.add(db_user) db.commit() db.refresh(db_user) return db_user

Authentication

JWT Authentication

from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext

SECRET_KEY = "your-secret-key" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def create_access_token(data: dict): to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password): return pwd_context.hash(password)

Protect Routes

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise HTTPException(status_code=401, detail="Invalid token") except JWTError: raise HTTPException(status_code=401, detail="Invalid token")

user = get_user(username)
if user is None:
    raise HTTPException(status_code=401, detail="User not found")
return user

@app.get("/protected/")
async def protected_route(current_user: User = Depends(get_current_user)):
return {"message": f"Hello {current_user.name}"}

Error Handling

HTTPException

from fastapi import HTTPException

@app.get("/items/{item_id}") def get_item(item_id: int): if item_id not in items: raise HTTPException( status_code=404, detail="Item not found", headers={"X-Error": "Item does not exist"} ) return items[item_id]

Custom Exception Handler

from fastapi import Request
from fastapi.responses import JSONResponse

class CustomException(Exception): def init(self, name: str): self.name = name

@app.exception_handler(CustomException) async def custom_exception_handler(request: Request, exc: CustomException): return JSONResponse( status_code=418, content={"message": f"Oops! {exc.name} did something wrong."} )

Middleware

Custom Middleware

from fastapi.middleware.cors import CORSMiddleware
import time

CORS

app.add_middleware( CORSMiddleware, allow_origins=[" "], allow_credentials=True, allow_methods=[""], allow_headers=["*"], )

Custom middleware

@app.middleware("http") async def add_process_time_header(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) return response

Deployment

Production Settings

# config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings): app_name: str = "My API" debug: bool = False database_url: str secret_key: str

class Config:
    env_file = ".env"

settings = Settings()

Docker

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Kesimpulan

FastAPI adalah framework modern yang ideal untuk building APIs. Auto-documentation dan type validation membuat development lebih cepat dan aman.

Ditulis oleh

Hendra Wijaya

Tinggalkan Komentar

Email tidak akan ditampilkan.