야구 직관 메이트 매칭 플랫폼
- pnpm workspace: 패키지 관리
- Turbo: 빌드 시스템 및 태스크 오케스트레이션
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS v4
- UI Library: shadcn/ui, Radix UI
- State Management: TanStack Query
- Testing: Vitest, Jest
- Documentation: Storybook
- Framework: NestJS
- Language: TypeScript
- ORM: Drizzle ORM
- Database: PostgreSQL
- Authentication: Passport.js (JWT, Kakao OAuth)
- API Documentation: Swagger
- Testing: Jest
- 공통 엔티티 및 타입 정의
- 프론트엔드와 백엔드에서 공유하는 타입
homerunnie/
├── apps/
│ ├── frontend/ # Next.js 프론트엔드 애플리케이션
│ └── backend/ # NestJS 백엔드 애플리케이션
├── packages/
│ ├── shared/ # 공유 타입 및 엔티티
│ └── eslint-config/ # 공유 ESLint 설정
├── pnpm-workspace.yaml # pnpm 워크스페이스 설정
├── turbo.json # Turbo 설정
└── package.json # 루트 패키지 설정
- Node.js 18+
- pnpm 10.16.0+
- PostgreSQL 14+
# 의존성 설치
pnpm install# Docker Compose로 PostgreSQL 실행
docker compose -f apps/backend/docker-compose.postgresql.yaml up -dapps/backend/secret은 별도의 private Git submodule입니다. 처음 clone한 경우 먼저 submodule을 초기화해야 합니다.
git submodule update --init --recursive환경 변수 파일의 실제 값은 저장소나 문서에 기록하지 않습니다. 팀원이 필요한 경우 secret submodule의 접근 권한을 받아 로컬 파일을 사용합니다.
Backend (apps/backend/secret/.env):
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=your_password
DB_NAME=your_database_name
DATABASE_URL=postgresql://postgres:your_password@localhost:5432/your_database_name
# Server
PORT=3030
# JWT
JWT_SECRET=your_jwt_secret
# Kakao OAuth
KAKAO_CLIENT_ID=your_kakao_client_id
KAKAO_CLIENT_SECRET=your_kakao_client_secret
KAKAO_CALLBACK_URL=http://localhost:3030/auth/kakao/callbackFrontend (apps/frontend/.env.local):
NEXT_PUBLIC_API_URL=http://localhost:3030# 백엔드 디렉토리로 이동
cd apps/backend
# 로컬 데이터베이스에 현재 Entity 스키마 반영
# Homerunnie 로컬 개발의 기본 방식은 db:push입니다.
pnpm db:push
# 시드 데이터 입력
pnpm db:seed# 루트 디렉토리에서 모든 앱 실행
pnpm dev
# 또는 개별 실행
pnpm --filter @homerunnie/frontend dev
pnpm --filter @homerunnie/backend dev- Frontend: http://localhost:3000
- Backend: http://localhost:3030
- Swagger API Docs: http://localhost:3030/api
src/
├── app/ # Next.js App Router
│ ├── layout.tsx # 루트 레이아웃
│ ├── page.tsx # 랜딩 페이지
│ ├── providers.tsx # 클라이언트 프로바이더 (React Query 등)
│ │
│ ├── home/ # 홈 페이지
│ │ ├── page.tsx
│ │ └── components/ # 홈 페이지 전용 컴포넌트
│ │ ├── MainBanner.tsx
│ │ ├── MateListBanner.tsx
│ │ └── ...
│ │
│ ├── signup/ # 회원가입 페이지
│ │ ├── page.tsx
│ │ └── components/
│ │ ├── SignUpForm.tsx
│ │ └── ...
│ │
│ ├── chat/ # 채팅 페이지
│ │ ├── page.tsx
│ │ ├── [id]/
│ │ │ └── page.tsx
│ │ └── components/ # 채팅 관련 공통 컴포넌트
│ │ ├── ChatBox.tsx
│ │ ├── ChatList.tsx
│ │ └── ...
│ │
│ ├── my/ # 마이페이지
│ ├── edit-profile/ # 프로필 수정
│ ├── write/ # 글 작성
│ └── login/ # 로그인
│
├── shared/ # 공유 코드
│ ├── ui/ # 공통 UI 컴포넌트
│ │ ├── primitives/ # shadcn/ui 기본 컴포넌트
│ │ │ ├── button.tsx
│ │ │ ├── input.tsx
│ │ │ └── ...
│ │ ├── button/ # 커스텀 버튼 컴포넌트
│ │ ├── input/ # 커스텀 입력 컴포넌트
│ │ ├── dropdown/ # 드롭다운 컴포넌트
│ │ └── ...
│ ├── styles/ # 스타일 시스템
│ │ ├── tokens.css # 디자인 토큰
│ │ └── index.css # 글로벌 스타일
│ ├── stores/ # 상태 관리 (Zustand 등)
│ └── utils/ # 공통 유틸리티
│
├── lib/ # 라이브러리 설정
│ └── utils.ts # 유틸리티 함수 (cn 등)
│
├── hooks/ # 커스텀 React Hooks
└── apis/ # API 클라이언트 코드
아키텍처 원칙:
- Next.js App Router 기반 구조
- 페이지별 컴포넌트는
app/[route]/components/에 배치 - 여러 페이지에서 공통 사용되는 컴포넌트는
shared/ui/에 배치 - shadcn/ui 기본 컴포넌트는
shared/ui/primitives/에, 커스텀 래핑은shared/ui/[component-name]/에 배치
src/
├── main.ts # 애플리케이션 진입점
├── app.module.ts # 루트 모듈
│
├── auth/ # 인증 모듈
│ ├── auth.module.ts
│ ├── auth.controller.ts
│ ├── auth.service.ts
│ ├── guards/ # 인증 가드
│ │ └── jwt-auth.guard.ts
│ └── strategies/ # Passport 전략
│ ├── jwt.strategy.ts
│ └── kakao.strategy.ts
│
├── member/ # 회원 모듈
│ ├── member.module.ts
│ ├── controller/ # 컨트롤러
│ ├── service/ # 비즈니스 로직
│ ├── repository/ # 데이터 접근
│ ├── domain/ # 엔티티
│ ├── dto/ # 데이터 전송 객체
│ └── swagger/ # Swagger 문서화
│
├── post/ # 게시글 모듈
├── comment/ # 댓글 모듈
├── participation/ # 참여 모듈
├── scrap/ # 스크랩 모듈
├── report/ # 신고 모듈
├── warn/ # 경고 모듈
├── admin/ # 관리자 모듈
│
└── common/ # 공통 모듈
├── config/ # 설정 파일
│ ├── database.config.ts
│ ├── drizzle.config.ts
│ └── swagger.config.ts
├── db/ # 데이터베이스
│ ├── schema/ # 스키마 정의
│ ├── seed/ # 시드 데이터
│ └── db.module.ts
├── decorators/ # 커스텀 데코레이터
├── dto/ # 공통 DTO
├── enums/ # 열거형
├── exceptions/ # 예외 처리
├── filters/ # 예외 필터
├── guards/ # 가드
├── interceptors/ # 인터셉터
└── response/ # 응답 형식
아키텍처 원칙:
- NestJS 모듈 기반 구조
- 각 도메인별로 독립적인 모듈 구성
- Controller → Service → Repository 패턴
- Domain Entity는 Drizzle ORM 사용
src/
├── entities/ # 공통 엔티티
│ └── team/
│ ├── team.ts
│ ├── stadium.ts
│ └── teamAssets.ts
└── index.ts # Export
프론트엔드와 백엔드에서 공통으로 사용하는 타입 정의
# 1. app 디렉토리에 새 라우트 폴더 생성
apps/frontend/src/app/new-page/page.tsx
# 2. 페이지별 컴포넌트는 components 폴더에 배치
apps/frontend/src/app/new-page/components/NewPageComponent.tsx# shared/ui에 배치
apps/frontend/src/shared/ui/component-name/ComponentName.tsx# components.json에서 ui 경로가 @/shared/ui/primitives로 설정되어 있음
cd apps/frontend
npx shadcn@latest add button
# → 자동으로 src/shared/ui/primitives/에 설치됨cd apps/backend
nest g module module-name
nest g controller module-name
nest g service module-nameHomerunnie는 Drizzle ORM의 db:push와 migration 기능을 함께 사용합니다.
- 로컬 기능 개발: 기존 데이터와 빠른 반복을 위해
db:push사용 - 운영 반영: migration 파일을 생성하고 CI/CD에서 적용
- 운영 DB에는
db:push를 직접 실행하지 않음
# 스키마 변경 후 운영 반영용 migration 파일 생성
pnpm db:generate
# migration 기반 DB에서 적용하거나 CI/CD에서 사용
pnpm db:migrate
# 로컬 개발 DB에 Entity 스키마를 직접 반영
pnpm db:push현재 로컬 jikgwan DB는 기존 db:push 방식으로 구성되어 있으므로, migration history가 없는 로컬 DB에서 db:migrate를 실행하지 않습니다. 로컬에서는 db:push를 사용하고, 운영 배포가 필요한 스키마 변경은 반드시 db:generate로 migration 파일을 생성해 함께 커밋합니다.
새 Entity를 추가하거나 Entity를 변경할 때는 다음 순서를 따릅니다.
src/[module]/domain/[entity].entity.ts수정 또는 추가- 해당 도메인의
domain/index.ts에 export 추가 src/common/db/schema/index.ts에서 schema가 export되는지 확인- 로컬 DB에는
pnpm db:push실행 - 운영 반영 전
pnpm db:generate실행 apps/backend/drizzle/migrations/의 SQL,metasnapshot,_journal.json변경을 검토하고 커밋
현재 migration 폴더에는 _journal.json에 등록된 migration과 과거에 생성된 미등록 SQL 파일이 함께 있습니다. 기존 migration 파일을 임의로 삭제하거나 이름을 변경하지 말고, 새 migration은 반드시 db:generate로 생성합니다.
운영 배포 workflow는 다음 순서로 동작합니다.
Entity 변경 시 migration 존재 여부 검사
→ 백엔드 빌드 및 테스트
→ Docker image 생성
→ 운영 서버에서 node dist/scripts/migrate.js 실행
→ 백엔드 컨테이너 실행
관련 파일:
.github/workflows/deploy-aws-ec2.ymlapps/backend/src/scripts/migrate.tsscripts/check-drizzle-migration.sh
src/[module]/domain/[entity].entity.ts에 엔티티 정의src/common/db/schema/index.ts에 export 추가pnpm db:generate실행
# 개발 서버 실행 (모든 앱)
pnpm dev
# 빌드 (모든 앱)
pnpm build
# 린트 체크
pnpm lint
# 타입 체크
pnpm type-check
# 테스트
pnpm test# 개발 서버
pnpm dev
# 프로덕션 빌드
pnpm build
# 프로덕션 실행
pnpm start
# Storybook
pnpm storybook
# 타입 체크
pnpm type-check
# 린트
pnpm lint# 개발 서버
pnpm dev
# 또는
pnpm start:dev
# 프로덕션 빌드
pnpm build
# 프로덕션 실행
pnpm start:prod
# 데이터베이스 관련
pnpm db:push # 스키마 직접 push
pnpm db:generate # 마이그레이션 파일 생성
pnpm db:migrate # 마이그레이션 실행
pnpm db:seed # 시드 데이터 입력
pnpm db:fresh # DB 초기화 후 시드 입력
pnpm db:clear # 모든 데이터 삭제
pnpm db:drop # 데이터베이스 삭제
pnpm db:reset # DB 삭제 후 재생성채팅 이미지는 WebSocket으로 바이너리 파일을 직접 전송하지 않고, 기존 S3 업로드 구조를 재사용합니다.
이미지 선택
→ 채팅방 권한 확인 후 S3 업로드
→ chat/{roomId}/{memberId}/... 경로의 object key 반환
→ Socket.IO로 이미지 메시지 전송
→ chat_message와 첨부파일 정보 저장
이미지 기능을 추가할 때는 다음 구조를 권장합니다.
chat_message:TEXT/IMAGE메시지 타입과 본문 저장chat_message_image: S3 object key, URL, MIME type, 파일 크기, 정렬 순서 저장- 이미지 전용 메시지는 본문이 없을 수 있으므로
contentnullable 검토 objectKey를 저장해 추후 S3 이미지 삭제가 가능하도록 처리- 메시지와 첨부파일 INSERT는 하나의 DB transaction으로 처리
이미지 업로드 API는 현재 피드용 /upload/images와 목적을 분리하는 것을 권장합니다. S3 bucket과 AWS 환경 변수는 기존 설정을 재사용하고, 채팅용 prefix와 파일 제한은 백엔드 코드에서 관리합니다.
필요한 환경 변수는 apps/backend/secret/.env에 설정합니다.
DATABASE_URL: PostgreSQL 연결 URLDB_HOST,DB_PORT,DB_USERNAME,DB_PASSWORD,DB_NAME: 데이터베이스 연결 정보PORT: 서버 포트 (기본: 3030)JWT_SECRET: JWT 토큰 시크릿KAKAO_CLIENT_ID,KAKAO_CLIENT_SECRET: Kakao OAuth 인증 정보AWS_REGION,AWS_S3_BUCKET,AWS_S3_PUBLIC_BASE_URL: 이미지 저장 및 공개 URL 설정
운영 환경은 apps/backend/secret/prod.env를 사용합니다. 운영 배포 시에는 GitHub Actions와 서버의 secret 관리 방식을 사용하며, 실제 AWS access key나 JWT secret을 README, issue, 로그에 기록하지 않습니다.
필요한 환경 변수는 apps/frontend/.env.local에 설정합니다.
NEXT_PUBLIC_API_URL: 백엔드 API URL
@homerunnie/frontend: 프론트엔드 애플리케이션@homerunnie/backend: 백엔드 애플리케이션@homerunnie/shared: 공유 타입 및 엔티티@homerunnie/eslint-config: 공유 ESLint 설정
# 특정 패키지에 의존성 추가
pnpm --filter @homerunnie/frontend add package-name
# 워크스페이스 패키지 추가
pnpm --filter @homerunnie/frontend add @homerunnie/shared# 모든 테스트 실행
pnpm test
# 테스트 커버리지
pnpm test:cov
# Watch 모드
pnpm test:watch- 새 브랜치 생성:
git checkout -b feature/[your-feature-name]-[issue-number] - 변경사항 커밋:
git commit -m "feat: your feature description" - 브랜치 푸시:
git push origin feature/your-feature-name - Pull Request 생성
이 프로젝트는 팀 내부 프로젝트입니다.