FastAPI, PostgreSQL, SQLAlchemy 2๋ก ์น๊ณผ ๋ชจ๋ฐ์ผ ์ฑ์ด ํจ๊ป ์ฌ์ฉํ REST API๋ฅผ ๊ตฌํํ ํ์ต์ฉ ์๋ฒ๋ค. ๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ, ํ์ ํ Refresh Token, ์ญํ ๊ธฐ๋ฐ ์ํ ๊ด๋ฆฌ, ์ ํํ๋ ์ฑ๊ณต ์๋ต, RFC 9457 Problem Details, OpenAPI ๊ณ์ฝ์ ์ค์ ์ฝ๋์ ํ ์คํธ๋ก ๋ณด์ฌ์ค๋ค.
๋น๊ต ๋์์ธ nestjs-monorepo-test์ ํต์ ๊ณ์ฝ๊ณผ ๊ณ์ธต์ ๋น๊ตํ๋, ์ด ์ ์ฅ์๋
๋ชจ๋
ธ๋ ํฌ๊ฐ ์๋๋ค. ํ๋์ FastAPI ์ ํ๋ฆฌ์ผ์ด์
๊ณผ ํ๋์ PostgreSQL์ ๊ธฐ๋ฅ๋ณ
ํจํค์ง๋ก ๋๋ ๋ชจ๋ํ ๋ชจ๋๋ฆฌ์ค๋ค.
- FastAPI์ ๊ธฐ๋ฐ ํ๋ ์์ํฌ Starlette, ASGI ์๋ฒ Uvicorn์ ์ญํ ๊ตฌ๋ถ
- Pydantic ์์ฒญ๊ณผ ์๋ต ๊ฒ์ฆ, ์๋ OpenAPI ์์ฑ
- SQLAlchemy ๋น๋๊ธฐ ORM, asyncpg, Alembic ๋ง์ด๊ทธ๋ ์ด์
- JWT Access Token, ํ์ ํ ๋ถํฌ๋ช Refresh Token, ๊ณ์ด ์ฌ์ฌ์ฉ ํ์ง
USER,ADMIN์ญํ ๊ณผ FastAPI ์์กด์ฑ ๊ธฐ๋ฐ ์ธ์ฆ, ์ธ๊ฐ- ๊ณต๊ฐ ์ํ ์กฐํ, ๊ด๋ฆฌ์ ์ ์ฉ ์ํ ์์ฑ, ์์ , ์ํํธ ์ญ์
- ์ฑ๊ณต ์๋ต
ApiResponse[T], ์คํจ ์๋ตapplication/problem+json - ์์ฒญ ์ถ์ ID์ structlog ๊ตฌ์กฐํ ๋ก๊ทธ
- ์ค์ PostgreSQL์ ์ฌ์ฉํ๋ ํตํฉ ํ ์คํธ์ HTTP E2E ํ ์คํธ
- OpenAPI ๊ธฐ๋ฐ ํ๋ก ํธ์๋ TypeScript ํ์ ๊ณผ ํด๋ผ์ด์ธํธ ์์ฑ
์์ธํ ๋ด๋ถ ์ค๊ณ๋ ์๋ฒ ์ํคํ ์ฒ, ์คํ ๋ชจ๋ธ์ Node.js์ Python ๋ฐํ์ ๋น๊ต๋ฅผ ์ฐธ๊ณ ํ๋ค.
| ๋ฒ์ | ์ ํ |
|---|---|
| ์ธ์ด์ ์๋ฒ | Python 3.13, FastAPI, Starlette, ASGI, Uvicorn |
| ๋ฐ์ดํฐ๋ฒ ์ด์ค | PostgreSQL 18.4, SQLAlchemy 2, asyncpg, Alembic |
| ๊ณ์ฝ๊ณผ ์ค์ | Pydantic v2, pydantic-settings, OpenAPI |
| ๋ณด์ | PyJWT, pwdlib, Argon2, SHA-256 Refresh Token ํด์ |
| ๊ด์ฐฐ ๊ฐ๋ฅ์ฑ | structlog, x-request-id, RFC 9457 Problem Details |
| ํ์ง | pytest, HTTPX, Testcontainers, Ruff, mypy, markdown-it-py |
| ๋๊ตฌ | uv, Docker, Docker Compose |
์ ํํ ๊ณ ์ ๋ฒ์ ์ pyproject.toml๊ณผ uv.lock์์ ํ์ธํ ์ ์๋ค.
cp .env.example .env
python3 -c 'import secrets; print(secrets.token_urlsafe(48))'๋ ๋ฒ์งธ ๋ช
๋ น์ ์ถ๋ ฅ์ผ๋ก .env์ JWT_SECRET ๊ฐ์ ๊ต์ฒดํ๋ค. ์์ ๊ฐ์ ๊ฐ๋ฐ
๋๋ ์ด์ Secret์ผ๋ก ์ฌ์ฉํ์ง ์๋๋ค. .env๋ Git์ ํฌํจ๋์ง ์๋๋ค.
Compose๋ ํธ์คํธ ํ๊ฒฝ ๋๋ .env์ JWT_SECRET์ Compose Secret์ผ๋ก ์ฝ๊ณ
์ปจํ
์ด๋์ /run/secrets/jwt_secret์ ํ์ผ๋ก ๋ง์ดํธํ๋ค. ์ ํ๋ฆฌ์ผ์ด์
์
pydantic-settings๋ฅผ ํตํด ์ด ํ์ผ์ ์ฝ๋๋ค. Secret ๊ฐ์ ์ผ๋ฐ ์๋น์ค ํ๊ฒฝ๋ณ์์
๋ณต์ฌ๋์ง ์๋๋ค.
docker compose build api
docker compose up api๋ง์ด๊ทธ๋ ์ด์
์ API ๋ช
๋ น์ ํฌํจ๋์ง ์๊ณ ๋ณ๋์ ์ผํ์ฑ migrate ์๋น์ค๊ฐ
๋ด๋นํ๋ค. docker compose up api๋ PostgreSQL์ด ์ค๋น๋ ๋ค migrate๊ฐ ์ฑ๊ณตํ
๊ฒฝ์ฐ์๋ง API๋ฅผ ์์ํ๋ค. ๋ง์ด๊ทธ๋ ์ด์
์ ์คํจํ๋ฉด API๋ ์์ํ์ง ์๋๋ค. ์
์คํค๋ง๋ง ๋ช
์์ ์ผ๋ก ์ ์ฉํ๋ ค๋ฉด docker compose run --rm migrate๋ฅผ ์คํํ๋ค.
๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์คํํ๋ ค๋ฉด ๋ง์ง๋ง ๋ช
๋ น์ -d๋ฅผ ์ถ๊ฐํ๋ค.
docker compose up -d api- Liveness: http://localhost:8000/health/live
- Readiness: http://localhost:8000/health/ready
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI JSON: http://localhost:8000/openapi.json
FastAPI์ ๊ธฐ๋ณธ ๊ฒฝ๋ก์ธ /docs, /redoc, /openapi.json์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค.
๋ก์ปฌ์ 8000 ๋๋ 5432 ํฌํธ๋ฅผ ์ด๋ฏธ ์ฌ์ฉ ์ค์ด๋ฉด .env์ ๋ค์ ๊ฐ์ ์ถ๊ฐํ๋ค.
API_PORT=18000
POSTGRES_PORT=15432์ดํ API๋ http://localhost:18000์์ ์ ๊ทผํ๋ค. ์ปจํ
์ด๋ ์ฌ์ด์์๋ ๊ณ์
postgres:5432๋ฅผ ์ฌ์ฉํ๋ฏ๋ก COMPOSE_DATABASE_URL์ ํธ์คํธ ์ฃผ์๋ก ๋ฐ๊พธ์ง
์๋๋ค. ๊ฐ๋ฐ์ฉ PostgreSQL ํฌํธ๋ ๊ธฐ๋ณธ๊ฐ๊ณผ ๋ณ๊ฒฝ๋ ํฌํธ ๋ชจ๋
127.0.0.1์๋ง ๊ฒ์๋๋ฏ๋ก ๋ค๋ฅธ ํธ์คํธ์์ ์ง์ ์ ์ํ ์ ์๋ค.
docker compose downPostgreSQL ๋ฐ์ดํฐ๋ fastapi-server-test-postgres-data ๋ณผ๋ฅจ์ ๋จ๋๋ค.
๋ณผ๋ฅจ ์ญ์ ๋ ๋ฐ์ดํฐ ์ญ์ ์์
์ด๋ฏ๋ก ์ด ํ๋ก์ ํธ์ ๊ธฐ๋ณธ ์ข
๋ฃ ๋ช
๋ น์ ํฌํจํ์ง
์๋๋ค.
Python 3.13, uv, ์คํ ๊ฐ๋ฅํ PostgreSQL์ด ํ์ํ๋ค. PostgreSQL๋ง Compose๋ก ์คํํ๊ณ API๋ ํธ์คํธ์์ ์คํํ ์ ์๋ค.
cp .env.example .env
docker compose up -d postgres
uv sync --extra dev
uv run alembic upgrade head
uv run uvicorn app.main:create_app --factory --reload.env.example์ DATABASE_URL์ ํธ์คํธ ํ๋ก์ธ์ค๊ฐ
localhost:5432์ PostgreSQL์ ์ ์ํ๋ ๊ฐ์ด๋ค. .env์๋ ์ ์ ์์ ์์ฑํ
์์ ํ JWT_SECRET์ ์ค์ ํ๋ค.
uv run์ ์ ๊ธด ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ช
๋ น์ ์คํํ๋ค. ์๋ฒ๋ ๊ธฐ๋ณธ์ ์ผ๋ก
http://127.0.0.1:8000์์ ์ด๋ฆฐ๋ค. --reload๋ ๊ฐ๋ฐ ์ค ํ์ผ ๋ณ๊ฒฝ ๊ฐ์ง์ฉ์ด๋ฉฐ
์ด์ ๋ฐฐํฌ ์ค์ ์ด ์๋๋ค.
uv๋ฅผ ์ค์นํ์ง ์์ ํ๊ฒฝ์์๋ ์คํํ ์ ์๋ค.
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
alembic upgrade head
uvicorn app.main:create_app --factory --reloadํ๋ก์ ํธ์ ์ง์ ๋ฒ์๋ Python >=3.13,<3.14๋ค. ์์คํ
๊ธฐ๋ณธ Python์ด ๋ค๋ฅธ
๋ฒ์ ์ด๋ฉด Python 3.13 ์ธํฐํ๋ฆฌํฐ๋ฅผ ๋ช
์ํ๋ค.
๊ณต๊ฐ ๊ฐ์
์ ํญ์ USER๋ฅผ ๋ง๋ ๋ค. ADMIN์ CLI์์๋ง ์์ฑํ๋ค. ๋ช
๋ น์ ํฐ๋ฏธ๋์์
๋น๋ฐ๋ฒํธ๋ฅผ ๋ํ์์ผ๋ก ์์ฒญํ๋ฉฐ ์
๋ ฅ๊ฐ์ ํ๋ฉด์ ํ์ํ์ง ์๋๋ค.
uv ํ๊ฒฝ:
uv run python -m app.cli create-admin \
--email admin@example.com \
--display-name AdminMakefile ๋จ์ถ ๋ช ๋ น:
make create-admin EMAIL=admin@example.com DISPLAY_NAME=AdminCompose ํ๊ฒฝ:
docker compose run --rm api uv run python -m app.cli create-admin \
--email admin@example.com \
--display-name Admin๋จผ์ ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ ์ฉ๋์ด ์์ด์ผ ํ๋ค. ๊ธฐ์กด ์ด๋ฉ์ผ๊ณผ ์ค๋ณต๋๊ฑฐ๋ ๋น๋ฐ๋ฒํธ๊ฐ 12์ ๋ฏธ๋ง์ด๋ฉด ์์ฑ๋์ง ์๋๋ค.
๋ชจ๋ ๊ธฐ๋ฅ API๋ /api/v1 ์๋์ ์๋ค. ์ํ ์ฝ๊ธฐ๋ ๊ณต๊ฐ์ด๋ฉฐ, ์ํ ์ฐ๊ธฐ๋
Authorization: Bearer <access-token> ํค๋์ ADMIN ์ญํ ์ด ํ์ํ๋ค.
| ๋ฉ์๋ | ๊ฒฝ๋ก | ์ธ์ฆ | ์ญํ |
|---|---|---|---|
POST |
/api/v1/auth/register |
์์ | USER ๊ฐ์
|
POST |
/api/v1/auth/login |
์์ | Access, Refresh Token ๋ฐ๊ธ |
POST |
/api/v1/auth/refresh |
Refresh Token ๋ณธ๋ฌธ | Token ํ์ |
POST |
/api/v1/auth/logout |
Refresh Token ๋ณธ๋ฌธ | ํด๋น Token ํ๊ธฐ |
POST |
/api/v1/auth/logout-all |
Bearer | ์ฌ์ฉ์ Token ์ ์ฒด ํ๊ธฐ |
GET |
/api/v1/users/me |
Bearer | ๋ด ์ ๋ณด ์กฐํ |
PATCH |
/api/v1/users/me |
Bearer | ํ์ ์ด๋ฆ ๋ณ๊ฒฝ |
POST |
/api/v1/users/me/password |
Bearer | ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ |
GET |
/api/v1/products |
์์ | ํ์ฑ ์ํ ๋ชฉ๋ก |
GET |
/api/v1/products/{product_id} |
์์ | ํ์ฑ ์ํ ์์ธ |
POST |
/api/v1/products |
Bearer | ADMIN ์์ฑ |
PATCH |
/api/v1/products/{product_id} |
Bearer | ADMIN ์์ |
DELETE |
/api/v1/products/{product_id} |
Bearer | ADMIN ์ํํธ ์ญ์ |
์ํ ๋ชฉ๋ก์ page, page_size, query, sort, order ์ฟผ๋ฆฌ๋ฅผ ์ง์ํ๋ค.
page๋ ์ต๋ 10,000, page_size๋ ์ต๋ 100์ด๋ฉฐ ์ด ๋ฒ์๋ฅผ ๋์ผ๋ฉด 422
๊ฒ์ฆ ์ค๋ฅ๋ฅผ ๋ฐํํ๋ค. sort๋ created_at, name,
price_in_minor_units, sku ์ค ํ๋๋ค.
curl -s http://localhost:8000/api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"correct-horse-123","display_name":"Learner"}'
curl -s http://localhost:8000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"correct-horse-123"}'์ฑ๊ณต ์๋ต์ ๋ผ์ฐํธ๊ฐ ์ ์ธํ ApiResponse[T] ํ์์ด๋ค.
{
"success": true,
"data": {
"status": "ok"
},
"meta": {
"timestamp": "2026-07-24T00:00:00Z",
"path": "/health/live",
"trace_id": "6d55b497-e365-4a2f-9981-cb5d8ae81f52"
}
}์คํจ ์๋ต์ RFC 9457 Problem Details์ด๋ฉฐ Content-Type์
application/problem+json์ด๋ค. Pydantic ๊ฒ์ฆ ์ค๋ฅ์๋ ํ๋๋ณ errors๊ฐ
ํฌํจ๋๋ค. x-request-id ํค๋, ์๋ต์ trace_id, ์๋ฒ ๋ก๊ทธ์ ์ถ์ ID๋ฅผ ์ด์ฉํด
๊ฐ์ ์์ฒญ์ ์ฐพ์ ์ ์๋ค.
Access Token์ ๊ธฐ๋ณธ 15๋ถ ์ ํจํ JWT๋ค. Refresh Token์ 256๋นํธ ๋ถํฌ๋ช ๋์์ด๋ฉฐ ๊ธฐ๋ณธ 30์ผ ๋์ ์ ํจํ๋ค. ์๋ฒ๋ Refresh Token ์๋ฌธ์ด ์๋๋ผ SHA-256 ํด์๋ง ์ ์ฅํ๋ค.
์ ์์ ์ธ refresh ์์ฒญ์ ํ์ฌ ํ ํฐ ํ์ ์ ๊ทธ๊ณ ์ฌ์ฉ ์ฒ๋ฆฌํ ๋ค ๊ฐ์ token family์ ์ Refresh Token์ ๋ฐ๊ธํ๋ค. ์ด๋ฏธ ์ฌ์ฉํ Refresh Token์ด ๋ค์ ์ ์ถ๋๋ฉด ํ์ทจ ๊ฐ๋ฅ์ฑ์ด ์๋ค๊ณ ๋ณด๊ณ ๊ฐ์ family ์ ์ฒด๋ฅผ ํ๊ธฐํ๋ค.
logout์ ์ ์ถํ Refresh Token์ ํ๊ธฐํ๊ณ , logout-all์ ํด๋น ์ฌ์ฉ์์ ๋ชจ๋
Refresh Token์ ํ๊ธฐํ๋ค. ์ด ํ๋ก์ ํธ์๋ Access Token denylist๊ฐ ์๋ค.
Access Token์ ๋ก๊ทธ์์ ํ์๋ ์ต๋ 15๋ถ ๋์ ์ ํจํ ์ ์์ต๋๋ค. ์ค์ํ
์๋น์ค์์๋ ๋ ์งง์ ์๋ช
, ์๋ฒ ์ธก ์ธ์
, denylist ๊ฐ์ ์ ํ์ง๋ฅผ ์ํ ๋ชจ๋ธ์
๋ง๊ฒ ๊ฒํ ํ๋ค.
Refresh Token ํ์ ์ ์ฌ๋ฌ ๋ธ๋ผ์ฐ์ ํญ์ด๋ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ํ ํฐ์ผ๋ก ๋์์ refreshํ ๋ ํ ์์ฒญ๋ง ์ฑ๊ณตํ ์ ์๋ค. ํด๋ผ์ด์ธํธ๋ ํ ๊ณ์ ์ refresh ์์ฒญ์ ์ง๋ ฌํํ๊ณ ์ ํ ํฐ์ ์์์ ์ผ๋ก ์ ์ฅํด์ผ ํ๋ค. ์ฌ์ฉ๋ ์ด์ ํ ํฐ์ ์ฌ์ ์กํ๋ฉด ๊ณ์ด ์ฌ์ฌ์ฉ ํ์ง๊ฐ ์๋ํ๋ค.
์คํ ์ค์ธ ์๋ฒ์ /openapi.json์ ์ง์ ๋ณผ ์ ์๊ณ , ์ ์ฅ์์ ๊ฒฐ์ ์ ์ธ ๊ณ์ฝ
ํ์ผ๋ ์์ฑํ ์ ์๋ค.
uv run python scripts/export_openapi.py
git diff -- openapi/openapi.json์ถ๋ ฅ์ openapi/openapi.json์ด๋ค. API ๋ณ๊ฒฝ๊ณผ ํจ๊ป ์ด ํ์ผ์ diff๋ฅผ ๊ฒํ ํ๊ณ ์ปค๋ฐํ๋ค.
๋ค์ ๋ช
๋ น์ ์ด Python ์ ์ฅ์๊ฐ ์๋๋ผ ํ๋ก ํธ์๋ ํ๋ก์ ํธ์์ ์คํํ๋ค.
ํ๋ก ํธ์๋๊ฐ ./openapi/openapi.json์ ๋ด๋ณด๋ธ ๊ณ์ฝ ํ์ผ์ ๋ณต์ฌํ๊ฑฐ๋ CI์์
๊ฐ์ ธ์๋ค๊ณ ๊ฐ์ ํ๋ค. Node.js ํจํค์ง๋ฅผ ์ด Python ์ ์ฅ์์ ์ถ๊ฐํ ํ์๊ฐ ์๋ค.
npx openapi-typescript ./openapi/openapi.json -o src/api/schema.d.ts์์ฑ๋ ํ์ ์ ํ๋ก ํธ์๋ HTTP ํด๋ผ์ด์ธํธ๊ฐ ๊ฒฝ๋ก, ์์ฒญ ๋ณธ๋ฌธ, ์ฑ๊ณต ์๋ต, Problem Details ํ์ ์ ๊ณต์ ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ค.
ํ๋ก ํธ์๋์ orval.config.ts ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
import { defineConfig } from "orval";
export default defineConfig({
api: {
input: "./openapi/openapi.json",
output: {
target: "./src/api/generated.ts",
client: "fetch",
clean: true,
},
},
});ํ๋ก ํธ์๋ ํ๋ก์ ํธ์์ Orval์ ๊ฐ๋ฐ ์์กด์ฑ์ผ๋ก ์ค์นํ ๋ค ์คํํ๋ค.
npx orval --config ./orval.config.ts์์ฑ ์ฝ๋๊ฐ ๋ชจ๋ ๋ฐํ์ ์ ์ฑ ์ ๋์ ํ์ง๋ ์๋๋ค. ์ธ์ฆ ํ ํฐ ์ ์ฅ ๋ฐฉ์, refresh ์ง๋ ฌํ, ์ ํ ์๊ฐ, ์ฌ์๋, ์ค๋ฅ ํ๋ฉด ์ ์ฑ ์ ํ๋ก ํธ์๋์์ ๋ณ๋๋ก ์ค๊ณํ๋ค.
V8์ JavaScript ์คํ ์์ง์ด๋ค. V8 ์์ฒด๋ฅผ nonblocking์ด๋ผ๊ณ ๋ถ๋ฅด๋ ๊ฒ์ ๋ถ์ ํํ๋ค. Node.js๋ V8, libuv, event loop, ์ด์์ฒด์ I/O polling, ์ ํ๋ worker pool์ ๊ฒฐํฉํ๋ค.
Python์ ๋ณธ์ง์ ์ผ๋ก multithread ์น ์๋ฒ๊ฐ ์๋๋ค. ์ด ํ๋ก์ ํธ์ CPython ๊ธฐ๋ณธ
๋น๋๋ GIL์ ์ฌ์ฉํ๋ค. async def๋ asyncio event loop, ๋๊ธฐ ๊ฒฝ๋ก๋ ํ์ํ ๋
thread pool, ์ฌ๋ฌ Uvicorn ์ธ์คํด์ค๋ ๋ณ๋ worker process์์ ์คํ๋๋ค.
| Node.js์ NestJS | Python๊ณผ FastAPI |
|---|---|
| Promise | Coroutine ๋๋ Task |
Promise.all |
asyncio.gather |
setTimeout |
asyncio.sleep |
| Node.js event loop | asyncio event loop |
| Express ๋๋ NestJS | FastAPI |
| Node HTTP server | Uvicorn ASGI server |
| cluster worker | Uvicorn worker process |
| BullMQ | Celery, Dramatiq, ARQ |
| Zod DTO | Pydantic model |
| Prisma | SQLAlchemy์ Alembic |
| Guard | dependency |
| Exception Filter | exception handler |
์์ธํ ์ฐจ์ด๋ ๋ฐํ์ ๋น๊ต ๋ฌธ์์ ์ค๋ช ํ๋ค.
nestjs-monorepo-test๋ apps/์ ์ฌ๋ฌ ์คํ ์ ํ๋ฆฌ์ผ์ด์
๊ณผ libs/์ ๊ณต์
ํจํค์ง๋ฅผ ํฌํจํ๋ค. ์ด ์ ์ฅ์๋ ํ๋์ ๋ฐฐํฌ ๋จ์ ์์์ app/modules/auth,
users, products๋ฅผ ๋๋๋ค.
| NestJS ๊ด์ | ์ด ์ ์ฅ์ |
|---|---|
| Module | ๊ธฐ๋ฅ ํจํค์ง์ API router ์กฐ๋ฆฝ |
| Controller | FastAPI APIRouter ๊ฒฝ๋ก ํจ์ |
| Provider์ Service | ๋ช ์์ ์ผ๋ก ์์ฑํ service์ repository |
| Zod์ DTO | Pydantic ์์ฒญ๊ณผ ์๋ต model |
| Prisma | SQLAlchemy ORM |
| Prisma Migrate | Alembic |
| Guard | FastAPI dependency |
| Exception Filter | FastAPI exception handler |
| Interceptor ์๋ต ๋ณํ | ๋ผ์ฐํธ์ ๋ช
์์ response_model |
| Swagger module | FastAPI ์๋ OpenAPI |
๋ ๊ตฌ์กฐ์ ์ด๋ฆ์ ๊ธฐ๊ณ์ ์ผ๋ก ์ผ๋์ผ ๋ณต์ฌํ๊ธฐ๋ณด๋ค ์ฑ ์ ๊ฒฝ๊ณ๋ฅผ ๋น๊ตํ๋ค.
Django์ Flask๋ ์น๊ณผ ๋ชจ๋ฐ์ผ ์ฑ์ด ํจ๊ป ์ฐ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ API๋ฅผ ๋ง๋ค ์ ์๋ค. Django๋ Flask๋ก ๋ง๋ API๋ฅผ ๋ชจ๋ฐ์ผ ์ฑ ๋๋ฌธ์ ๋ฐ๋ก ๋ค์ ๊ฐ๋ฐํด์ผ ํ๋ค๋ ์ค๋ช ์ ์ ํํ์ง ์๋ค. Django REST Framework ๊ฐ์ ์ ํ์ง๋ ์๋ค.
FastAPI๋ ํ์ ํํธ์ Pydantic ๊ฒ์ฆ, OpenAPI, Swagger UI, ReDoc, ASGI ์ฒ๋ฆฌ๊ฐ API ๊ฐ๋ฐ์ ๊ธฐ๋ณธ ํ๋ฆ์ ๊ธด๋ฐํ๊ฒ ์ฐ๊ฒฐ๋ ์ ์ด ํน์ง์ด๋ค. Django๋ ORM, ๊ด๋ฆฌ์, ํ ํ๋ฆฟ, ์ธ์ฆ์ ํฌํจํ ํฐ ์น ํ๋ ์์ํฌ์ด๊ณ Flask๋ ์์ ์ฝ์ด์ ํ์ฅ์ ์กฐํฉํ๋ค. ์๊ตฌ์ฌํญ๊ณผ ํ ๊ฒฝํ์ ๋ง์ถฐ ์ ํํ๋ค.
FastAPI๊ฐ ํจ์จ์ ์ธ ๋น๋๊ธฐ API๋ฅผ ๋ง๋ค ์ ์๋ค๋ ๊ฒ๊ณผ ๋ชจ๋ ์์ ์์ Node.js ๋๋ Go์ ๊ฐ์ ์ฑ๋ฅ์ ๋ณด์ฅํ๋ค๋ ๊ฒ์ ๋ค๋ฅด๋ค. ์ค์ ์ฒ๋ฆฌ๋์ ์์ ํน์ฑ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ์ ์ ๊ธ, ์ง๋ ฌํ, worker ์, ์ฐ๊ฒฐ ํ, ๋ฐฐํฌ ์ค์ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ค. ์ค์ ์๋น์ค ํํ๋ก ์ธก์ ํ๋ค.
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy app tests scripts
uv run --extra dev python scripts/check_docs.py
uv run python scripts/export_openapi.pyMakefile์ ์ฌ์ฉํ๋ฉด ๊ฐ์ ์์ ์ ์คํํ ์ ์๋ค.
make install
make test
make lint
make format
make typecheck
make docs-check
make openapi
make verifyscripts/check_docs.py๋ ๋ชจ๋ Markdown์ UTF-8๋ก ์ฝ๊ณ ๊ธ์ง๋ ๊ฐ์ด๋์ ๋ฌธ์์
๊นจ์ง ๋ก์ปฌ Markdown ๋งํฌ๋ฅผ ๊ฒ์ฌํ๋ค. CommonMark ๋ฌธ๋ฒ ํด์์๋ ๊ฐ๋ฐ ์์กด์ฑ์ธ
markdown-it-py๋ฅผ ์ฌ์ฉํ๋ค. ๋ฐ๋ผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ uv sync --extra dev ๋๋
pip install -e '.[dev]'๋ก ์ค์นํ ๋ค ์คํํ๋ค. make docs-check๋ ํ์ํ
dev extra๋ฅผ ๋ช
์ํด ๊ฐ์ ๊ฒ์ฌ๋ฅผ ์ํํ๋ค.
make verify๋ ์ ๊ธ ํ์ผ, Ruff ๊ฒ์ฌ์ ํ์, mypy, ๋ฌธ์, ์ ์ฒด pytest,
OpenAPI ์ค๋
์ท์ ํ ๋ฒ์ ๊ฒ์ฆํ๋ค. OpenAPI ๊ฒ์ฆ์ ์์ ํ์ผ๋ก ๋ช
์ธ๋ฅผ ๋ด๋ณด๋ธ
๋ค ์ถ์ ์ค์ธ openapi/openapi.json๊ณผ ๋ฐ์ดํธ ๋จ์๋ก ๋น๊ตํ๋ค. ๋ฐ๋ผ์ ๊ฒ์ฆ
๊ณผ์ ์ด ๊ธฐ์กด ์ค๋
์ท์ ๋ฎ์ด์จ ๋ณ๊ฒฝ์ ์จ๊ธฐ์ง ์๋๋ค.
GitHub Actions๋ Python 3.13๊ณผ ๊ณ ์ ๋ uv 0.11.31 ํ๊ฒฝ์์ ํ์ง ๊ฒ์ฌ, ๋จ์ ํ ์คํธ, PostgreSQL 18.4 ํตํฉ ํ ์คํธ, HTTP E2E ํ ์คํธ, Docker ์ด๋ฏธ์ง ๋น๋, OpenAPI drift ๊ฒ์ฌ๋ฅผ ๊ฐ๊ฐ ์คํํ๋ค. ํตํฉ ํ ์คํธ์ E2E ํ ์คํธ๋ CI ์๋น์ค PostgreSQL์ Alembic ๋ง์ด๊ทธ๋ ์ด์ ์ ๋จผ์ ์ ์ฉํ๋ค. ํ ์คํธ ๋ณธ์ฒด์ Testcontainers PostgreSQL์ ์์์ ํธ์คํธ ํฌํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก CI ์๋น์ค์ 5432 ํฌํธ์ ์ถฉ๋ํ์ง ์๋๋ค.
tests/unit: ์ธ๋ถ ์์คํ ์์ด ๋ณด์ ํจ์์ ์๋น์ค ๊ท์น ๊ฒ์ฆtests/integration: Testcontainers์ ์ค์ PostgreSQL๋ก ๋ง์ด๊ทธ๋ ์ด์ , ์ ์ฝ, ์ ์ฅ์, Refresh Token ํ์ ๊ฒ์ฆtests/e2e: ASGI HTTP ์์ฒญ์ผ๋ก ์ธ์ฆ, ์ฌ์ฉ์, ์ํ, OpenAPI ๊ณ์ฝ ๊ฒ์ฆtests/operations: Docker Compose์ ์ด๋ฏธ์ง ์ด์ ๊ณ์ฝ ๊ฒ์ฆtests/documentation: ํ์ต ๋ฌธ์์ ํ์ ๋ด์ฉ, ๋ช ๋ น, ๋งํฌ ๊ฒ์ฆ
ํตํฉ ํ ์คํธ์๋ ์คํ ๊ฐ๋ฅํ Docker๊ฐ ํ์ํ๋ค.