본문으로 건너뛰기
Advertisement

라우터와 경로 매개변수

APIRouter로 엔드포인트를 모듈별로 분리하고, 경로·쿼리·바디·헤더 매개변수를 세밀하게 제어할 수 있습니다.


APIRouter — 라우터 분리

# routers/users.py
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel

router = APIRouter(
prefix="/users", # 모든 경로에 /users 접두사
tags=["users"], # Swagger UI 그룹
)


class User(BaseModel):
id: int
name: str
email: str


fake_users: dict[int, User] = {
1: User(id=1, name="Alice", email="alice@example.com"),
2: User(id=2, name="Bob", email="bob@example.com"),
}


@router.get("/", response_model=list[User])
def list_users():
return list(fake_users.values())


@router.get("/{user_id}", response_model=User)
def get_user(user_id: int):
if user_id not in fake_users:
raise HTTPException(status_code=404, detail="User not found")
return fake_users[user_id]


@router.post("/", response_model=User, status_code=status.HTTP_201_CREATED)
def create_user(user: User):
fake_users[user.id] = user
return user


@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
if user_id not in fake_users:
raise HTTPException(status_code=404, detail="User not found")
del fake_users[user_id]
# routers/items.py
from fastapi import APIRouter

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


@router.get("/")
def list_items():
return [{"id": 1, "name": "Item 1"}]
# main.py
from fastapi import FastAPI
from routers import users, items

app = FastAPI(title="My API")

# 라우터 등록
app.include_router(users.router)
app.include_router(items.router)

# 버전 접두사 추가
# app.include_router(users.router, prefix="/api/v1")

경로 매개변수 (Path Parameters)

from fastapi import FastAPI, Path
from typing import Annotated
from enum import Enum

app = FastAPI()


# 타입 검증 자동 수행
@app.get("/items/{item_id}")
def get_item(item_id: int): # str → int 변환 + 검증
return {"item_id": item_id}


# Path()로 추가 검증
@app.get("/products/{product_id}")
def get_product(
product_id: Annotated[int, Path(ge=1, le=9999, description="상품 ID")]
):
return {"product_id": product_id}


# 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 == ModelName.alexnet:
return {"model": model_name, "message": "Deep Learning"}
return {"model": model_name}


# 파일 경로 (슬래시 포함)
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}
# GET /files/home/user/doc.txt → file_path = "home/user/doc.txt"

쿼리 매개변수 (Query Parameters)

from fastapi import FastAPI, Query
from typing import Annotated

app = FastAPI()


# 기본값 있는 쿼리
@app.get("/items/")
def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
# GET /items/?skip=20&limit=5


# 필수 쿼리 (기본값 없음)
@app.get("/search/")
def search(q: str):
return {"q": q}


# Query()로 검증
@app.get("/advanced/")
def advanced_search(
q: Annotated[str | None, Query(
min_length=2,
max_length=50,
pattern=r"^[a-zA-Z\s]+$",
description="검색어 (영문·공백만 허용)",
)] = None,
page: Annotated[int, Query(ge=1)] = 1,
size: Annotated[int, Query(ge=1, le=100)] = 20,
):
return {"q": q, "page": page, "size": size}


# 리스트 쿼리
@app.get("/items/filter/")
def filter_items(
tags: Annotated[list[str], Query()] = [],
):
return {"tags": tags}
# GET /items/filter/?tags=python&tags=fastapi


# deprecated 표시
@app.get("/old-endpoint/")
def old_endpoint(
q: Annotated[str | None, Query(deprecated=True)] = None
):
return {"q": q}

요청 바디 (Request Body)

from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Annotated

app = FastAPI()


class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: str | None = Field(None, max_length=300)
price: float = Field(..., gt=0)
tax: float | None = None


class User(BaseModel):
username: str
full_name: str | None = None


# 단일 바디
@app.post("/items/")
def create_item(item: Item):
item_dict = item.model_dump()
if item.tax:
item_dict["price_with_tax"] = item.price + item.tax
return item_dict


# 여러 바디 (중첩 JSON)
@app.put("/items/{item_id}")
def update_item(
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body(ge=1, le=5)] = 1,
):
return {
"item_id": item_id,
"item": item,
"user": user,
"importance": importance,
}
# 요청 JSON:
# {
# "item": {"name": "Foo", "price": 10.5},
# "user": {"username": "alice"},
# "importance": 3
# }


# embed: 단일 바디도 키로 래핑
@app.post("/items/embed/")
def create_embedded(item: Annotated[Item, Body(embed=True)]):
return item
# 요청: {"item": {"name": "Foo", "price": 10.5}}

헤더·쿠키·Form 매개변수

from fastapi import FastAPI, Header, Cookie, Form
from typing import Annotated

app = FastAPI()


# 헤더
@app.get("/headers/")
def read_headers(
user_agent: Annotated[str | None, Header()] = None,
x_token: Annotated[str | None, Header(alias="X-Token")] = None,
):
return {"User-Agent": user_agent, "X-Token": x_token}


# 쿠키
@app.get("/cookies/")
def read_cookies(
session_id: Annotated[str | None, Cookie()] = None,
):
return {"session_id": session_id}


# Form 데이터 (application/x-www-form-urlencoded)
@app.post("/login/")
def login(
username: Annotated[str, Form()],
password: Annotated[str, Form()],
):
return {"username": username}
# 주의: Form과 JSON Body를 동시에 사용 불가


# 파일 업로드
from fastapi import UploadFile, File


@app.post("/upload/")
async def upload_file(
file: Annotated[UploadFile, File(description="업로드 파일")],
):
contents = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents),
}

정리

매개변수클래스위치
경로Path()URL 경로 /{id}
쿼리Query()URL 뒤 ?key=value
바디Body() / Pydantic요청 본문 (JSON)
헤더Header()HTTP 헤더
쿠키Cookie()Cookie 헤더
FormForm()form-data
파일UploadFile + File()multipart

APIRouter기능별 파일을 분리하면 대규모 API도 유지보수가 쉬워집니다.

Advertisement