From f5a18a52f44aade77abed01dd2a5c70a56219f26 Mon Sep 17 00:00:00 2001 From: ttipo Date: Mon, 15 Jun 2026 11:14:42 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=20=EC=A0=95=EB=B9=84=20=EB=B0=8F=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=98=88=EC=A0=9C=C2=B7README=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: 모노레포 개요·표준 응답 포맷·에러 코드 맵(구현 일치)·환경변수 위임 구조(SSOT) 정리 - nestjs/vue3/flutter 규칙을 실제 구현(클래스명, env키, 디렉터리)과 정합화 - 루트 .env 단일 진실 공급원 구조에 맞춰 docker-compose 변수명 정정(BACKEND_API_URL, DB/REDIS_DATA_DIR) - backend/frontend/flutter 예제 env 추가(루트 위임 반영, 서비스 고유 값만 작성) - README.md 신규 작성, command.md 범용화, .gitignore에 *.example 재포함 규칙 추가 Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/commands/flutter.md | 18 +++-- .claude/commands/nestjs.md | 13 ++-- .claude/commands/vue3.md | 6 +- .env.development.example | 39 ++++++++++ .env.production.example | 39 ++++++++++ .gitignore | 1 + CLAUDE.md | 117 +++++++++++++++++++++++++----- README.md | 109 ++++++++++++++++++++++++++++ backend/.env.development.example | 34 +++++++++ backend/.env.production.example | 17 +++++ command.md | 41 ++++++++--- docker-compose.yml | 6 +- flutter/.env.example | 12 +++ frontend/.env.development.example | 12 +++ frontend/.env.production.example | 10 +++ 15 files changed, 423 insertions(+), 51 deletions(-) create mode 100644 .env.development.example create mode 100644 .env.production.example create mode 100644 README.md create mode 100644 backend/.env.development.example create mode 100644 backend/.env.production.example create mode 100644 flutter/.env.example create mode 100644 frontend/.env.development.example create mode 100644 frontend/.env.production.example diff --git a/.claude/commands/flutter.md b/.claude/commands/flutter.md index 33f8893..3fb0e79 100644 --- a/.claude/commands/flutter.md +++ b/.claude/commands/flutter.md @@ -24,8 +24,8 @@ lib/ ├── core/ │ ├── constants/ ← 상수 (색상, 크기, 텍스트 등) │ ├── theme/ ← 앱 테마 정의 -│ ├── network/ ← Dio 인스턴스, 인터셉터 -│ ├── storage/ ← flutter_secure_storage 래퍼 +│ ├── api/ ← Dio 인스턴스(dio_client), 에러 사전(error_lexicon), 인터셉터 +│ ├── storage/ ← flutter_secure_storage 래퍼 (선택) │ └── utils/ ← 순수 유틸 함수 ├── features/ │ └── user/ @@ -136,14 +136,16 @@ class UserWidget extends ConsumerWidget { ## API 통신 (Dio + 인터셉터) ```dart -// core/network/dio_client.dart +// core/api/dio_client.dart class DioClient { late final Dio _dio; DioClient() { - _dio = Dio(BaseOptions(baseUrl: Env.apiUrl)); + // flutter_dotenv 로 .env 로드, 미설정 시 로컬 기본값 + final baseUrl = dotenv.maybeGet('API_URL') ?? 'http://localhost:3000/api'; + _dio = Dio(BaseOptions(baseUrl: baseUrl)); _dio.interceptors.add(AuthInterceptor()); // 토큰 자동 첨부 - _dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리 + _dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리 (error_lexicon 참조) } } @@ -164,11 +166,11 @@ class ErrorInterceptor extends Interceptor { // flutter_secure_storage 사용 const storage = FlutterSecureStorage(); -// 저장 -await storage.write(key: 'access_token', value: token); +// 저장 (키 이름은 dio_client 인터셉터와 통일: jwt_token) +await storage.write(key: 'jwt_token', value: token); // 읽기 -final token = await storage.read(key: 'access_token'); +final token = await storage.read(key: 'jwt_token'); // 삭제 (로그아웃) await storage.deleteAll(); diff --git a/.claude/commands/nestjs.md b/.claude/commands/nestjs.md index ad36382..0f65ab3 100644 --- a/.claude/commands/nestjs.md +++ b/.claude/commands/nestjs.md @@ -74,21 +74,22 @@ export class CreateUserDto { ## 표준 응답 인터셉터 ```typescript -// common/interceptors/response.interceptor.ts +// common/interceptors/transform.interceptor.ts @Injectable() -export class ResponseInterceptor implements NestInterceptor { +export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( map((data) => ({ success: true, - data, + data: data ?? null, })), ); } } ``` -- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new ResponseInterceptor())` +- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new TransformInterceptor())` +- 실패 응답(`success: false`)은 아래 Exception Filter가 생성한다. --- @@ -97,12 +98,12 @@ export class ResponseInterceptor implements NestInterceptor { ```typescript // common/filters/http-exception.filter.ts @Catch() -export class AllExceptionsFilter implements ExceptionFilter { +export class HttpExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - // 에러 코드 매핑 로직 포함 + // HTTP 상태 → 에러 코드 매핑 후 { success: false, error: { code, message } } 반환 // SYS_001 / AUTH_001 / VAL_001 등 CLAUDE.md 에러 맵 참조 } } diff --git a/.claude/commands/vue3.md b/.claude/commands/vue3.md index a67449d..d9e1e8e 100644 --- a/.claude/commands/vue3.md +++ b/.claude/commands/vue3.md @@ -88,13 +88,13 @@ export const useUserStore = defineStore('user', () => { ```typescript // composables/useApi.ts — 중앙 Axios 인스턴스 const api = axios.create({ - baseURL: import.meta.env.VITE_API_URL, + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // env.d.ts 에 타입 선언 withCredentials: true, // HttpOnly 쿠키 전송 }) -// 응답 인터셉터 — 에러 코드 전역 처리 +// 응답 인터셉터 — 표준 응답 언래핑 + 에러 코드 전역 처리 api.interceptors.response.use( - (response) => response.data.data, // success: true → data 언래핑 + (response) => response.data.data, // { success, data } → data 언래핑 (error) => { const code = error.response?.data?.error?.code // 에러 코드별 Toast 알림 처리 (CLAUDE.md 에러 맵 참조) diff --git a/.env.development.example b/.env.development.example new file mode 100644 index 0000000..0021046 --- /dev/null +++ b/.env.development.example @@ -0,0 +1,39 @@ +# ========================================== +# 프로젝트 전역 설정 +# ========================================== +PROJECT_NAME=prelude +APP_ENV=development +BUILD_MODE=development +FRONTEND_DIR=frontend +BACKEND_DIR=backend + +# ========================================== +# 포트 설정 +# ========================================== +FRONTEND_PORT=5173 +BACKEND_PORT=3000 +WEBSOCKET_PORT=3001 +DB_PORT=5432 +REDIS_PORT=6379 + +# ========================================== +# Database 설정 +# ========================================== +DB_IMAGE=postgres:15-alpine +DB_USER=dev_ttipo +DB_PASSWORD=dev_960426 +DB_NAME=prelude_db +DB_DATA_DIR=./data/postgres + +# ========================================== +# Redis 설정 +# ========================================== +REDIS_IMAGE=redis:7-alpine +REDIS_PASSWORD=dev_960426 +REDIS_DATA_DIR=./data/redis + +# ========================================== +# API 경로 설정 +# ========================================== +BACKEND_API_URL=http://localhost:3000/api +CORS_ORIGIN=http://localhost:5173 \ No newline at end of file diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..173a67c --- /dev/null +++ b/.env.production.example @@ -0,0 +1,39 @@ +# ========================================== +# 프로젝트 전역 설정 +# ========================================== +PROJECT_NAME=prelude +APP_ENV=production +BUILD_MODE=production +FRONTEND_DIR=frontend +BACKEND_DIR=backend + +# ========================================== +# 포트 설정 +# ========================================== +FRONTEND_PORT=5173 +BACKEND_PORT=3000 +WEBSOCKET_PORT=3001 +DB_PORT=5432 +REDIS_PORT=6379 + +# ========================================== +# Database 설정 +# ========================================== +DB_IMAGE=postgres:15-alpine +DB_USER=prd_ttipo +DB_PASSWORD=prd_960426 +DB_NAME=prelude_db +DB_DATA_DIR=./data/postgres + +# ========================================== +# Redis 설정 +# ========================================== +REDIS_IMAGE=redis:7-alpine +REDIS_PASSWORD=prd_960426 +REDIS_DATA_DIR=./data/redis + +# ========================================== +# API 경로 설정 +# ========================================== +BACKEND_API_URL=http://localhost:3000/api +CORS_ORIGIN=http://localhost:5173 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6f3146e..976e0ab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # ========================================== .env .env.* +!.env.example !.env.*.example # ========================================== diff --git a/CLAUDE.md b/CLAUDE.md index 06150a0..3bd5311 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,36 +1,115 @@ +# 프로젝트 개요 + +이 저장소는 **풀스택 모노레포 기본 템플릿**이다. 누구나 클론하여 바로 개발을 시작할 수 있도록 공통 규칙과 언어별 규칙을 정의한다. + +| 디렉터리 | 스택 | 역할 | 개발 규칙 (슬래시 명령) | +| ----------- | -------------- | ------------------------ | ---------------------------------------- | +| `backend/` | NestJS (TS) | REST API 서버 | `/nestjs` → @.claude/commands/nestjs.md | +| `frontend/` | Vue 3 (TS) | 웹 프론트엔드 | `/vue3` → @.claude/commands/vue3.md | +| `flutter/` | Flutter (Dart) | 모바일 앱 | `/flutter` → @.claude/commands/flutter.md | +| 루트 | Docker Compose | 로컬/운영 오케스트레이션 | @command.md | + +세 클라이언트는 동일한 **표준 응답 포맷**과 **에러 코드 맵**을 공유한다 (아래 참조). + +--- + ## 코딩 규칙 ### 공통 - 모든 코드 주석은 **한국어**로 작성 -- 웹/서버는 100% TypeScript, Warning 방치 금지 -- 유틸 함수는 `shared/utils/`에 순수 함수로 분리 +- 웹(`frontend`)·서버(`backend`)는 100% TypeScript, 모바일(`flutter`)은 Dart Sound Null Safety 준수 +- 컴파일러/린터 **Warning 방치 금지** — 커밋 전 각 프로젝트 `lint` / `analyze` 통과 +- 유틸 함수는 순수 함수로 분리 — TS는 `shared/utils/`, Dart는 `core/utils/` +- 매직 넘버·하드코딩 문자열 지양 → 상수 또는 환경변수로 분리 +- 절대경로 alias 사용 (TS `@/`, Dart `package:`), 깊은 상대경로(`../../`) 지양 + +### 표준 응답 포맷 (Global Response Wrapper) + +모든 API 응답은 아래 구조로 통일한다. +(SSOT: backend `TransformInterceptor` + `HttpExceptionFilter`) + +```jsonc +// 성공 +{ "success": true, "data": { /* ... */ } } + +// 실패 +{ "success": false, "error": { "code": "VAL_001", "message": "..." } } +``` + +- 프론트/플러터 인터셉터는 `success: true` 면 `data` 만 언래핑하여 반환한다. +- `success: false` 이거나 HTTP 4xx/5xx 면 `error.code` 로 메시지를 매핑한다. ### Git 커밋 컨벤션 -feat: / fix: / refactor: / docs: / chore: / perf: -예: `feat: 납기오더 페이지네이션 추가` +`: <제목>` 형식 — type: **feat / fix / refactor / docs / chore / perf / test / style** + +- 예: `feat: 사용자 목록 페이지네이션 추가` +- 제목은 한국어, 명령형 현재 시제로 작성 +- 한 커밋은 하나의 논리적 변경만 포함 ### 에러 코드 맵핑 -| 코드 | 상황 | 메시지 | -| -------- | ------------- | -------------------------------------- | -| SYS_001 | 500 Error | "일시적인 시스템 오류가 발생했습니다." | -| AUTH_001 | 401 | "로그인이 필요한 서비스입니다." | -| AUTH_002 | 세션 만료 | "안전을 위해 로그아웃 되었습니다." | -| AUTH_003 | 403 | "해당 기능에 접근 권한이 없습니다." | -| VAL_001 | Validation | "입력하신 정보를 다시 확인해 주세요." | -| RES_001 | 404 | "요청하신 정보를 찾을 수 없습니다." | -| BIZ_001 | 비즈니스 오류 | "요청하신 작업을 완료하지 못했습니다." | +세 플랫폼이 공유하는 **단일 진실 공급원(SSOT)**. 메시지를 수정할 때는 아래 세 파일을 함께 갱신한다. + +- backend: `src/common/filters/http-exception.filter.ts` +- frontend: `src/composables/useApi.ts` (`ErrorCodeLexicon`) +- flutter: `lib/core/api/error_lexicon.dart` (`ErrorLexicon`) + +| 코드 | HTTP / 상황 | 메시지 | +| -------- | -------------------- | ------------------------------------------------------- | +| SYS_001 | 500 / 미정의 예외 | "일시적인 시스템 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." | +| AUTH_001 | 401 | "로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요." | +| AUTH_002 | 세션 만료 (클라이언트) | "안전을 위해 로그아웃 되었습니다. 다시 로그인해 주세요." | +| AUTH_003 | 403 | "해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다." | +| VAL_001 | 400 / 422 (Validation) | "입력하신 정보를 다시 확인해 주세요. (필수값 누락 또는 형식 오류)" | +| RES_001 | 404 | "요청하신 정보나 페이지를 찾을 수 없습니다." | +| BIZ_001 | 200(비즈니스 실패) / 기타 | "요청하신 작업을 완료하지 못했습니다. 다시 시도해 주세요." | + +> 비즈니스 예외는 `HttpException` 응답의 `message` 로 사용자 노출 문구를 재정의할 수 있다. ### 보안 -- `.env` 외 민감정보 Git 커밋 금지 -- JWT는 HttpOnly Secure 쿠키 사용 (localStorage 금지) -- TypeORM ORM 쿼리 사용으로 SQL Injection 차단 -- Docker 컨테이너 내부 root 실행 금지 +- `.env*` 등 민감정보 Git 커밋 금지 (`.gitignore` 확인) — 예시는 `*.example`(예: `.env.development.example`, `.env.example`) 로만 공유 +- JWT는 HttpOnly·Secure 쿠키 사용 (웹 `localStorage` 금지) / 모바일은 `flutter_secure_storage` 사용 +- TypeORM ORM 쿼리 사용으로 SQL Injection 차단 (Raw 쿼리 시 파라미터 바인딩 필수) +- 입력값은 DTO(`class-validator`) 검증을 거친 뒤 서비스 계층에 전달 +- 보안 헤더(`helmet`)·CORS·Rate Limiting(`@nestjs/throttler`) 적용 +- Docker 컨테이너 내부 root 실행 금지 (비-root 사용자로 구동) + +--- + +## 환경변수 구조 (단일 진실 공급원: 루트 `.env`) + +이 템플릿은 **루트 `.env.{APP_ENV}` 가 모든 설정의 단일 진실 공급원(SSOT)** 이다. +값을 한 곳에서만 관리하기 위해, 서비스(backend/frontend)는 자체 env에 값을 중복 작성하지 않고 **루트 → docker-compose 가 주입한 값을 그대로 사용**한다. + +``` +루트 .env.{APP_ENV} + │ (docker compose --env-file 로 로드) + ├─▶ backend : env_file + environment 로 DB/Redis/CORS/포트 주입 + │ (backend/.env.{APP_ENV} 는 "존재"만 하면 됨, 내용 최소) + ├─▶ frontend : BACKEND_API_URL → build arg(VITE_API_BASE_URL) 로 주입 + ├─▶ db : DB_USER/DB_PASSWORD/DB_NAME, DB_DATA_DIR 볼륨 + └─▶ redis : REDIS_PASSWORD, REDIS_DATA_DIR 볼륨 +``` + +규칙: + +- **새 설정값은 루트 `.env.{APP_ENV}` 에 먼저 추가**하고, 필요한 서비스에 `docker-compose.yml` 의 `environment` / `args` 로 전달한다. +- 서비스 자체 env 파일에는 **루트에서 내려주지 않는 고유 값만** 작성한다. (예: 백엔드 `JWT_SECRET`) +- `docker-compose.yml` 의 `env_file` 지시자 때문에 `backend/.env.{APP_ENV}` 파일은 비어 있어도 **존재해야 한다.** +- **단독 실행(non-Docker)** 시에는 주입이 없으므로, 각 서비스 `.env.{APP_ENV}` 의 주석 처리된 예시 값을 활성화해 사용한다. +- **Flutter 는 예외** — docker-compose 대상이 아니므로 `flutter/.env` 에서 `API_URL` 을 독립적으로 관리한다. +- 모든 env 파일은 커밋 금지, `*.example` 만 커밋한다. (`.gitignore` 의 `!*.example` 재포함 규칙) + +> 루트 env의 대표 키: `PROJECT_NAME`, `APP_ENV`, `BUILD_MODE`, 각종 `*_PORT`, `BACKEND_API_URL`, `CORS_ORIGIN`, `DB_*`/`DB_DATA_DIR`, `REDIS_*`/`REDIS_DATA_DIR`. 실제 목록은 `.env.development.example` 참조. + +--- ## 개발 규칙 참조 -@.claude/commands/nestjs.md -@.claude/commands/vue3.md +- 백엔드(NestJS): @.claude/commands/nestjs.md +- 프론트엔드(Vue 3): @.claude/commands/vue3.md +- 모바일(Flutter): @.claude/commands/flutter.md +- 실행/배포 명령어: @command.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3524d4a --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# 풀스택 모노레포 기본 템플릿 + +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 (권장 구동 방식) +- 단독 실행 시: Node.js `^20.19 || >=22.12`, Flutter SDK `^3.11` + +## 빠른 시작 (Docker) + +```bash +# 1) 환경변수 파일 생성 (예제 복사) +# 실제 설정값은 루트 .env.development 한 곳에서 관리한다 (SSOT). +# backend/frontend 는 docker-compose 가 값을 주입하므로 예제만 복사하면 된다. +cp .env.development.example .env.development # ← 여기에만 값 입력 +cp backend/.env.development.example backend/.env.development # (존재만 필요, 내용 최소) +cp frontend/.env.development.example frontend/.env.development # (단독 실행 시에만 사용) + +# 2) 개발 환경 기동 (코드만 수정 시) +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 +``` + +기동 후: + +- 웹: +- API: +- Swagger 문서: + +전체 실행/배포 명령어는 [`command.md`](./command.md) 참조. + +## 단독 실행 (Docker 미사용) + +```bash +# 백엔드 +cd backend && npm install && npm run start:dev + +# 프론트엔드 +cd frontend && npm install && npm run dev + +# 모바일 +cd flutter && cp .env.example .env && flutter pub get && flutter run +``` + +> 단독 실행 시 백엔드 `.env.development` 의 `DB_HOST`/`REDIS_HOST` 를 `localhost` 로 두고, +> PostgreSQL·Redis 를 별도로 띄워야 합니다. + +## 환경변수 + +`.env*` 는 커밋하지 않습니다. 각 디렉터리의 `*.example` 파일을 복사해 사용하세요. + +**루트 `.env.{env}` 가 단일 진실 공급원(SSOT)** 입니다. backend·frontend 는 docker-compose 가 +루트 값을 주입하므로 자체 env 에 값을 중복 작성하지 않습니다. (자세한 위임 구조는 [`CLAUDE.md`](./CLAUDE.md) 의 "환경변수 구조" 참조) + +| 파일 | 용도 | 값 작성 위치 | +| ----------------------------- | ---------------------------------------- | --------------------- | +| `.env.{env}.example` | 루트 — 모든 설정의 SSOT (compose 주입) | ✅ 여기에 작성 | +| `backend/.env.{env}.example` | 백엔드 — 고유 시크릿(JWT 등)만 | 최소 (존재 필수) | +| `frontend/.env.{env}.example` | 프론트 — 단독 실행용 `VITE_API_BASE_URL` | 단독 실행 시에만 | +| `flutter/.env.example` | 모바일 — `API_URL` (compose 비대상) | ✅ 독립 관리 | + +`{env}` = `development` | `production` + +## 표준 응답 포맷 + +```jsonc +// 성공 +{ "success": true, "data": { /* ... */ } } +// 실패 +{ "success": false, "error": { "code": "VAL_001", "message": "..." } } +``` + +에러 코드 맵과 코딩 규칙 전문은 [`CLAUDE.md`](./CLAUDE.md) 및 `.claude/commands/` 의 언어별 규칙 문서를 참고하세요. + +## 개발 규칙 + +| 영역 | 문서 | +| --------- | ------------------------------------------------- | +| 공통/보안 | [`CLAUDE.md`](./CLAUDE.md) | +| NestJS | [`.claude/commands/nestjs.md`](./.claude/commands/nestjs.md) | +| Vue 3 | [`.claude/commands/vue3.md`](./.claude/commands/vue3.md) | +| Flutter | [`.claude/commands/flutter.md`](./.claude/commands/flutter.md) | + +## 디렉터리 구조 + +``` +. +├── backend/ # NestJS API (Controller / Service / DTO / 공통 필터·인터셉터) +├── frontend/ # Vue 3 (pages / composables / stores / router) +├── flutter/ # Flutter (core / features / widgets) +├── docker-compose.yml # 베이스 정의 +├── docker-compose.dev.yml # 개발 오버라이드 (핫리로드, 포트 노출) +├── CLAUDE.md # 공통 코딩 규칙 + 에러 코드 맵 (SSOT) +└── command.md # 실행/배포 명령어 +``` diff --git a/backend/.env.development.example b/backend/.env.development.example new file mode 100644 index 0000000..5fa9c45 --- /dev/null +++ b/backend/.env.development.example @@ -0,0 +1,34 @@ +# ========================================== +# 백엔드 환경변수 (NestJS) — 개발용 +# 사용법: cp .env.development.example .env.development +# +# ⚠️ 도커로 실행하면 이 파일에 값을 적을 필요가 거의 없다. +# 루트 .env.development → docker-compose 가 아래 값을 주입(override)한다: +# NODE_ENV, PORT, WEBSOCKET_PORT, CORS_ORIGIN, +# DB_HOST(=db), DB_PORT(=5432), DB_USER, DB_PASSWORD, DB_NAME, +# REDIS_HOST(=redis), REDIS_PORT(=6379), REDIS_PASSWORD +# (docker-compose 의 env_file 지시자 때문에 이 파일은 "존재"해야 한다.) +# +# 아래 항목은 ⓐ 백엔드 단독(npm run start:dev) 실행 시 또는 +# ⓑ 루트에서 내려주지 않는 서비스 고유 값일 때만 채운다. +# ========================================== + +# ----- 단독 실행(non-Docker) 시에만 필요 — 도커 실행 시 무시됨 ----- +# NODE_ENV=development +# PORT=3000 +# WEBSOCKET_PORT=3001 +# CORS_ORIGIN=http://localhost:5173 +# DB_HOST=localhost +# DB_PORT=5432 +# DB_USER=dev_ttipo +# DB_PASSWORD=dev_960426 +# DB_NAME=prelude_db +# REDIS_HOST=localhost +# REDIS_PORT=6379 +# REDIS_PASSWORD=dev_960426 + +# ----- 백엔드 고유 값 (루트에서 내려주지 않음 / 인증 구현 시 사용) ----- +# JWT_SECRET=changeme_jwt_secret +# JWT_EXPIRES_IN=1h +# JWT_REFRESH_SECRET=changeme_refresh_secret +# JWT_REFRESH_EXPIRES_IN=7d diff --git a/backend/.env.production.example b/backend/.env.production.example new file mode 100644 index 0000000..a190604 --- /dev/null +++ b/backend/.env.production.example @@ -0,0 +1,17 @@ +# ========================================== +# 백엔드 환경변수 (NestJS) — 운영용 +# 사용법: cp .env.production.example .env.production +# +# ⚠️ 운영은 도커로 구동된다. DB/Redis/CORS/포트 등은 루트 .env.production → +# docker-compose 가 모두 주입하므로 여기에 중복 작성하지 않는다. +# (env_file 지시자 때문에 이 파일은 "존재"하기만 하면 된다.) +# +# 아래는 루트에서 내려주지 않는 백엔드 고유 시크릿이다. 인증 구현 시 채운다. +# ⚠️ 반드시 강력한 임의값으로 교체할 것. +# ========================================== + +# ----- 백엔드 고유 값 (인증 구현 시 사용) ----- +# JWT_SECRET=__CHANGE_ME_STRONG_JWT_SECRET__ +# JWT_EXPIRES_IN=15m +# JWT_REFRESH_SECRET=__CHANGE_ME_STRONG_REFRESH_SECRET__ +# JWT_REFRESH_EXPIRES_IN=7d diff --git a/command.md b/command.md index 0abc432..9bbbaf8 100644 --- a/command.md +++ b/command.md @@ -1,18 +1,35 @@ -### 개발 환경 명령어 +# 실행 / 배포 명령어 -- 코드만 수정했을 때 (의존성 변경 없음) - docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d +> Docker Compose 기반 로컬·운영 오케스트레이션 명령어 모음. +> `.env.development` / `.env.production` 는 커밋된 `.env.*.example` 를 복사해 생성한다. +> (`.gitignore` 는 `.env.*` 를 무시하되 `.env.*.example` 만 추적한다.) -- 의존성 변경 시에만 --build - docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build +## 개발 환경 -### 운영 환경 명령어 +```bash +# 코드만 수정했을 때 (의존성 변경 없음) +docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d -- 운영 환경 배포 명령어 - sudo docker compose --env-file .env.production up -d --build +# 의존성 변경 시에만 --build +docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build -- 로그 확인 - sudo docker compose --env-file .env.development logs -f backend +# 로그 확인 (예: backend) +docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development logs -f backend +``` -- ngrok 도메인 포트 열기 - ngrok http --domain=ttipo-backend.ngrok.app 3000 +## 운영 환경 + +```bash +# 운영 환경 배포 +sudo docker compose --env-file .env.production up -d --build + +# 로그 확인 +sudo docker compose --env-file .env.production logs -f backend +``` + +## 외부 노출 (선택) + +```bash +# ngrok 으로 백엔드 포트(3000) 임시 노출 — 을 본인 도메인으로 교체 +ngrok http --domain=.ngrok.app 3000 +``` diff --git a/docker-compose.yml b/docker-compose.yml index 04b7062..73f1bcd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: dockerfile: Dockerfile args: - BUILD_MODE=${BUILD_MODE} - - VITE_API_BASE_URL=${FRONT_API_URL} + - VITE_API_BASE_URL=${BACKEND_API_URL} expose: - "80" environment: @@ -92,7 +92,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} volumes: - - ./db-data:/var/lib/postgresql/data + - ${DB_DATA_DIR}:/var/lib/postgresql/data restart: always networks: - app-network @@ -120,7 +120,7 @@ services: expose: - "6379" volumes: - - ./redis-data:/data + - ${REDIS_DATA_DIR}:/data restart: always networks: - app-network diff --git a/flutter/.env.example b/flutter/.env.example new file mode 100644 index 0000000..74d0c74 --- /dev/null +++ b/flutter/.env.example @@ -0,0 +1,12 @@ +# ========================================== +# Flutter 환경변수 (flutter_dotenv) — pubspec assets 에 .env 등록됨 +# 사용법: cp .env.example .env +# +# ⚠️ 모바일 앱은 docker-compose 대상이 아니므로 루트 .env 의 위임을 받지 않는다. +# 아래 값은 항상 이 파일에서 직접 관리한다. +# 빌드 타임 분리가 필요하면 --dart-define 또는 .env.{flavor} 도입 고려. +# ========================================== + +# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함) +# 안드로이드 에뮬레이터에서 호스트 접근 시: http://10.0.2.2:3000/api +API_URL=http://localhost:3000/api diff --git a/frontend/.env.development.example b/frontend/.env.development.example new file mode 100644 index 0000000..1a5b8f8 --- /dev/null +++ b/frontend/.env.development.example @@ -0,0 +1,12 @@ +# ========================================== +# 프론트엔드 환경변수 (Vite) — 개발용 +# 사용법: cp .env.development.example .env.development +# +# ⚠️ 도커로 빌드/실행하면 VITE_API_BASE_URL 은 루트 .env 의 BACKEND_API_URL 이 +# docker-compose build arg 로 주입되므로 이 파일은 비워도 된다. +# 아래 값은 프론트 단독(npm run dev) 실행 시에만 사용된다. +# (VITE_ 접두사가 붙은 값만 클라이언트 번들에 노출 — 비밀값 금지) +# ========================================== + +# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함) +VITE_API_BASE_URL=http://localhost:3000/api diff --git a/frontend/.env.production.example b/frontend/.env.production.example new file mode 100644 index 0000000..dfb7867 --- /dev/null +++ b/frontend/.env.production.example @@ -0,0 +1,10 @@ +# ========================================== +# 프론트엔드 환경변수 (Vite) — 운영용 +# 사용법: cp .env.production.example .env.production +# +# ⚠️ 도커 빌드 시에는 루트 .env 의 BACKEND_API_URL(build arg)이 우선 적용된다. +# 아래 값은 프론트 단독 빌드(npm run build) 시에만 사용된다. +# ========================================== + +# 운영 도메인 기준 API 베이스 URL +VITE_API_BASE_URL=https://api.example.com/api