지금까지 .claude 에는 "무엇을 지키는가"(rules)와 "어떻게 만드는가"(skills)만 있었고 "누가 어디까지 책임지는가"를 적을 자리가 없었다. 역할 파일은 규칙을 복사하지 않고 참조만 한다. 구현 규칙을 옮겨 적으면 스킬과 진실 공급원이 둘로 갈라져, 한쪽만 고쳐졌을 때 어느 쪽이 맞는지 알 수 없게 된다. 역할 고유의 것(담당 범위·경계·완료 기준·인계 형식)만 담는다. 리뷰어와 페르소나 둘은 frontmatter 에서 Edit/Write 를 빼 도구 수준으로 읽기 전용이다. CLAUDE.md 에는 @ import 하지 않는다 — 호출 시점에 로드되는 역할 정의다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 KiB
풀스택 모노레포 기본 템플릿
NestJS(백엔드) · Vue 3(웹) · Flutter(모바일)를 하나의 저장소에서 운영하는 풀스택 템플릿입니다. 세 클라이언트는 동일한 표준 응답 포맷과 공통 에러 코드 맵을 공유하며, Docker Compose로 한 번에 구동됩니다.
구성
| 디렉터리 | 스택 | 역할 | 포트(기본) |
|---|---|---|---|
backend/ |
NestJS + TypeORM | REST API 서버 | 3000 |
frontend/ |
Vue 3 + Vite | 웹 프론트엔드 | 5173 |
flutter/ |
Flutter + Riverpod | 모바일 앱 | - |
db |
PostgreSQL | 데이터베이스 | 5432 |
redis |
Redis | 캐시 / 세션 (기본 비활성) | 6379 |
사전 요구사항
- Docker / Docker Compose — backend·frontend·db·redis 구동
- Flutter SDK
^3.11— 모바일 앱 실행용 (compose 대상 아님)
빠른 시작 (Docker)
# 1) 환경변수 파일 생성 (예제 복사)
# 실제 설정값은 루트 .env.development 한 곳에서 관리한다 (SSOT).
# backend/frontend 는 docker-compose 가 값을 주입하므로 예제만 복사하면 된다.
cp .env.development.example .env.development # ← 여기에만 값 입력
cp backend/.env.development.example backend/.env.development # (존재 필요: JWT 등 고유 시크릿)
# frontend 는 compose 가 빌드 시 값을 주입하므로 자체 env 복사 불필요
# 2) 백엔드 스택 기동 (backend + db)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d
# 3) 의존성 변경 시에만 --build
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build
# 4) 프론트엔드는 호스트에서 직접 띄운다 (컨테이너보다 수십 배 빠르다)
cd frontend && npm install && npm run dev
기동 후:
- 웹: http://localhost:5173
- API: http://localhost:3000/api
- Swagger 문서: http://localhost:3000/api-docs
개발 환경에서 프론트엔드는 컨테이너로 뜨지 않습니다. vite 를 컨테이너에서 돌리면 소스가 Windows 바인드 마운트 너머에 있어 첫 서빙까지 수 분이 걸립니다. 호스트에서
npm run dev로 띄우면 1~2초이고,/api요청은 vite 프록시가 백엔드 컨테이너로 넘깁니다. 컨테이너로 띄워야 한다면--profile frontend를 붙이세요 (아래 "프론트엔드 실행 방식" 참조). 운영(docker-compose.yml단독)은 그대로 nginx 정적 호스팅으로 동작합니다.
Redis 는 기본적으로 뜨지 않습니다. 필요해지면 루트
.env.{env}에COMPOSE_PROFILES=redis를 설정하세요.
프론트엔드 실행 방식
| 방식 | 명령 | 첫 서빙 | 용도 |
|---|---|---|---|
| 호스트 vite (개발 기본) | cd frontend && npm run dev |
1~2초 (최초 의존성 사전번들 10초 내외) | 일상 개발 |
| 컨테이너 vite (선택) | docker compose ... --profile frontend up -d |
수 분 (Windows 바인드 마운트) | 호스트에 Node 를 둘 수 없을 때 |
| nginx 정적 (운영) | docker compose --env-file .env.production up -d --build |
- | 배포 |
컨테이너 방식을 상시 사용하려면 루트
.env.development에COMPOSE_PROFILES=frontend를 넣습니다. (redis 와 함께 켤 때는COMPOSE_PROFILES=frontend,redis) 호스트 vite 와 컨테이너 vite 는 같은 호스트 포트를 쓰므로 동시에 띄우지 마세요.
포트는 루트
.env.{env}를 따릅니다. 호스트에서 띄우는 vite 도FRONTEND_PORT로 열리고,/api프록시 대상은BACKEND_PORT에서 나옵니다 —vite.config.ts가 루트 env 를 직접 읽습니다. 포트가 이미 사용 중이면 조용히 다른 포트로 옮기지 않고 실패합니다 (CORS_ORIGIN과 compose 포트 매핑이 그 값에 묶여 있어, 말없이 옮겨가면 원인 모르게 어긋납니다).
전체 실행/배포 명령어는 .claude/skills/docker/SKILL.md 참조.
모바일 앱 실행 (Flutter)
Flutter 는 Docker Compose 대상이 아니므로 로컬에서 직접 실행합니다.
cd flutter && cp .env.example .env && flutter pub get && flutter run
flutter/.env의API_URL을 백엔드 주소로 설정합니다. (Android 에뮬레이터는http://10.0.2.2:3000/api)
환경변수
.env* 는 커밋하지 않습니다. 각 디렉터리의 *.example 파일을 복사해 사용하세요.
루트 .env.{env} 가 단일 진실 공급원(SSOT) 입니다. backend·frontend 는 docker-compose 가
루트 값을 주입하므로 자체 env 에 값을 중복 작성하지 않습니다. (자세한 위임 구조는 CLAUDE.md 의 "환경변수 구조" 참조)
| 파일 | 용도 | 값 작성 위치 |
|---|---|---|
.env.{env}.example |
루트 — 모든 설정의 SSOT (compose 주입) | ✅ 여기에 작성 |
backend/.env.{env}.example |
백엔드 — 고유 시크릿(JWT 등)만 | 최소 (존재 필수) |
frontend/.env.{env}.example |
프론트 — 빌드 시 루트 BACKEND_API_URL 주입 |
작성 불필요 |
flutter/.env.example |
모바일 — API_URL (compose 비대상) |
✅ 독립 관리 |
{env} = development | production
표준 응답 포맷
// 성공
{ "success": true, "data": { /* ... */ } }
// 실패 — 사용자에게 보이는 모든 실패는 에러 코드를 가진다
{ "success": false, "error": { "code": "VAL_001", "message": "...", "traceId": "3f1c0a2e-..." } }
// 목록 — data 안의 형태가 고정되어 있다
{ "success": true, "data": { "items": [], "pageInfo": { "hasNext": false, "nextCursor": null, "total": 0 } } }
traceId 는 요청마다 발급되는 추적 ID(x-request-id 헤더와 동일)로, 서버 로그에 같은 값이 남습니다.
사용자 문의가 들어오면 이 값으로 로그를 검색하면 됩니다.
/api/health는 예외입니다. 모니터링 도구가 읽는 엔드포인트라 래퍼 없이 원본 형식을 반환합니다.
에러 코드는 한 곳에서 관리됩니다
backend/src/common/errors/error-codes.ts ← 여기만 수정
│ cd backend && npm run sync:error-codes
├─▶ frontend/src/shared/constants/error-codes.generated.ts (자동 생성)
└─▶ flutter/lib/core/api/error_lexicon.g.dart (자동 생성)
생성 파일은 직접 수정하지 마세요. CI 가 동기화 여부를 검사합니다.
자세한 규칙은 .claude/rules/error-codes.md 참조.
로컬 개발 (Docker 없이)
DB 는 컨테이너로 띄우고 앱만 호스트에서 실행하는 방식이 가장 간단합니다.
# 1) DB 만 컨테이너로 기동 (127.0.0.1:5432 로 노출된다)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d db
# 2) backend — DB_HOST 기본값이 localhost 라 그대로 붙는다
cd backend && npm ci && npm run start:dev
# 3) frontend — vite dev 서버가 /api 를 localhost:3000 으로 프록시한다 (개발 기본 방식)
cd frontend && npm ci && npm run dev
# 4) flutter
cd flutter && flutter pub get && flutter run
프론트엔드의 API 요청은
/api상대경로로 나가고 vite dev 프록시가 백엔드로 넘깁니다. 백엔드를 다른 주소에서 띄웠다면VITE_DEV_API_PROXY로 대상을 바꾸세요.
커밋 전 검사
cd backend && npm run lint && npm run test && npm run build
cd frontend && npm run lint && npm run type-check && npm run test:unit
cd flutter && flutter analyze && flutter test
lint 는 --max-warnings 0 이라 경고 하나만 있어도 실패합니다. 자동 수정은 npm run lint:fix.
같은 검사를 CI(.github/workflows/ci.yml)가 PR 마다 수행합니다.
⚠️ 의존성을 추가·제거할 때는 컨테이너 안에서 실행하세요. Windows 에서
npm install하면 Linux 전용 패키지가 lock 에서 잘려 Docker 빌드가 깨집니다.docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development exec backend npm install <패키지>
개발 규칙
| 영역 | 문서 |
|---|---|
| 공통/보안 | CLAUDE.md · .claude/rules/ |
| 응답·에러 | response-format.md · error-codes.md |
| API 설계 | api-design.md |
| 로깅 | logging.md |
| 체크리스트 | checklist.md |
| NestJS | .claude/skills/nestjs/SKILL.md |
| Vue 3 | .claude/skills/vue3/SKILL.md |
| Flutter | .claude/skills/flutter/SKILL.md |
| Docker | .claude/skills/docker/SKILL.md |
| 역할 분담 | .claude/agents/README.md |
디렉터리 구조
.
├── backend/ # NestJS API (Controller / Service / DTO / 공통 필터·인터셉터)
├── frontend/ # Vue 3 (pages / components / composables / stores / router)
├── flutter/ # Flutter (core / features / shared)
├── docker-compose.yml # 베이스 정의
├── docker-compose.dev.yml # 개발 오버라이드 (핫리로드, 포트 노출)
├── CLAUDE.md # 프로젝트 개요 + 공통 규칙 @import
├── .github/
│ ├── workflows/ci.yml # lint·test·build + 에러 코드 동기화 검사
│ └── pull_request_template.md
├── ci/ # CI 러너 (Gitea Actions) — 앱 스택과 분리
└── .claude/
├── settings.json # Claude Code 권한·환경 설정
├── rules/ # 공통 규칙 (코딩·응답 포맷·에러 코드·API 설계·로깅·보안·환경변수·테스트)
├── skills/ # 도메인별 개발 규칙 (nestjs·vue3·flutter·docker)
└── agents/ # 역할별 규칙 (개발 3종·테스터·리뷰어·인프라·사용자/관리자 관점)