Cara Membuat API dengan FastAPI Python
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 Optionalclass 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, validatorclass 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 itemResponse Models
Multiple Response Models
from typing import Listclass 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 APIRouterrouter = 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 sessionmakerSQLALCHEMY_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 Baseclass 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 CryptContextSECRET_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 OAuth2PasswordBeareroauth2_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 JSONResponseclass 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 timeCORS
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 BaseSettingsclass 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-slimWORKDIR /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