docs: 템플릿 규칙 정비 및 환경변수 예제·README 추가
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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<T> implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
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<Response>();
|
||||
|
||||
// 에러 코드 매핑 로직 포함
|
||||
// HTTP 상태 → 에러 코드 매핑 후 { success: false, error: { code, message } } 반환
|
||||
// SYS_001 / AUTH_001 / VAL_001 등 CLAUDE.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 에러 맵 참조)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -3,6 +3,7 @@
|
||||
# ==========================================
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.*.example
|
||||
|
||||
# ==========================================
|
||||
|
||||
@@ -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>: <제목>` 형식 — 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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
기동 후:
|
||||
|
||||
- 웹: <http://localhost:5173>
|
||||
- API: <http://localhost:3000/api>
|
||||
- Swagger 문서: <http://localhost:3000/api-docs>
|
||||
|
||||
전체 실행/배포 명령어는 [`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 # 실행/배포 명령어
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
+29
-12
@@ -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) 임시 노출 — <YOUR_DOMAIN> 을 본인 도메인으로 교체
|
||||
ngrok http --domain=<YOUR_DOMAIN>.ngrok.app 3000
|
||||
```
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user