Kembali ke Blog
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
# Install FastAPI dan Uvicorn
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
Artikel Sebelumnya
Cara Membuat About Page yang SEO Friendly
Artikel Selanjutnya
Cara Membuat Aplikasi PWA (Progressive Web App)