와인 홍보 및 이벤트 안내를 위한 블로그 서비스입니다.
- React 19
- TypeScript
- Vite
- TanStack Query
- Zustand
- TailwindCSS v4
- Shadcn/ui
- Tiptap
- NestJS
- TypeScript
- Prisma
- MySQL 8.4
- Passport.js
- Docker Compose
- Nginx - 리버스 프록시 및 정적 파일 서빙
- Let's Encrypt - SSL/TLS 인증서
- EC2 (t3.micro) - 서울 리전
- EBS (30GB) - 블록 스토리지
- Elastic IP - 고정 IP 주소
- Security Group - 방화벽 설정
┌─────────────────┐
│ Internet │
└────────┬────────┘
│
┌────────┴────────┐
│ Elastic IP │
│ biswine.co.kr │
└────────┬────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────────┐
│ AWS Cloud (ap-northeast-2 / Seoul) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ VPC │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Public Subnet │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────┐ ┌───────────────────────────────────────┐ │ │ │
│ │ │ │ Security Group │ │ EC2 (t3.micro) │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ :80 ← 0.0.0.0 │ │ ┌─────────────────────────────────┐ │ │ │ │
│ │ │ │ :443 ← 0.0.0.0 │───▶│ │ Docker Compose (3 containers) │ │ │ │ │
│ │ │ │ :22 ← My IP │ │ │ ┌───────────────────────────┐ │ │ │ │ │
│ │ │ │ │ │ │ │ Nginx :80/443 │ │ │ │ │ │
│ │ │ └────────────────┘ │ │ │ (React Static Files) │ │ │ │ │ │
│ │ │ │ │ └───────────────────────────┘ │ │ │ │ │
│ │ │ │ │ ┌───────────┐ ┌───────────┐ │ │ │ │ │
│ │ │ │ │ │ NestJS │ │ MySQL 8.4 │ │ │ │ │ │
│ │ │ │ │ │ :3000 │ │ :3306 │ │ │ │ │ │
│ │ │ │ │ └───────────┘ └───────────┘ │ │ │ │ │
│ │ │ │ └─────────────────────────────────┘ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ EBS Volume (30GB gp3) │ │ │ │
│ │ │ │ ┌─────────────────────────────────┐ │ │ │ │
│ │ │ │ │ ~/mysql (DB Data) │ │ │ │ │
│ │ │ │ │ ~/static (Image Files) │ │ │ │ │
│ │ │ │ │ ~/logs (Log Files) │ │ │ │ │
│ │ │ │ └─────────────────────────────────┘ │ │ │ │
│ │ │ └───────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
| 항목 | 선택 | 이유 |
|---|---|---|
| EC2 vs ECS/EKS | EC2 | 단일 인스턴스로 충분, 프리티어 활용 |
| RDS vs EC2 내 DB | EC2 내 Docker | 비용 절감, 추후 서버 이전 시 데이터 마이그레이션 용이 |
| S3 vs EBS | EBS | 이미지를 로컬 저장, 서버 이전 시 함께 이동 가능 |
| ALB vs Nginx | Nginx (컨테이너) | 단일 인스턴스에서 ALB 불필요, 비용 절감 |
┌─────────────────────────────────────────────────────────────────┐
│ Client (Browser) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Nginx (HTTPS) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Static Files │ │ SPA Routing │ │ API Proxy │ │
│ │ (React App) │ │ (index.html) │ │ (/api/*) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │
│ Frontend Network │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ React SPA │ │ NestJS API │
│ (Static) │ │ (Port 3000) │
└──────────────┘ └──────────────────┘
│
│ Backend Network
▼
┌──────────────────┐
│ MySQL 8.4 │
│ (Port 3306) │
└──────────────────┘
- JWT 기반 인증: Access Token + Refresh Token 전략
- 보안 강화:
- Refresh Token은 bcrypt로 해시화하여 DB 저장
- HttpOnly 쿠키를 통한 Refresh Token 전송
- Access Token 만료 시 자동 갱신 (Axios Interceptor)
- Rate Limiting: Brute-force 공격 방지를 위한 요청 제한
- Tiptap 에디터 기반의 WYSIWYG 편집기
- 지원 기능:
- 텍스트 스타일링 (Bold, Italic, Strikethrough)
- 제목 레벨 (H1, H2, H3)
- 목록 (순서 있음/없음)
- 인용문
- 이미지 삽입 (드래그 앤 드롭, 붙여넣기, 버튼 클릭)
- 이미지 최적화: Sharp를 사용한 WebP 변환 (최대 1920px, 품질 80%)
- 자동 이미지 정리: 매일 새벽 미사용 이미지 자동 삭제
- 이미지-게시글 연결: 게시글에 포함된 이미지 URL 파싱 후 자동 연결
- IntersectionObserver API 활용
- TanStack Query의 useInfiniteQuery로 페이지네이션
- Monorepo 구조에서 타입 정의 패키지 공유
- 프론트엔드와 백엔드 간 API 계약 보장
- 컴파일 타임 타입 검증
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/signup |
회원가입 |
| POST | /api/auth/login |
로그인 |
| POST | /api/auth/refresh |
토큰 갱신 |
| POST | /api/auth/logout |
로그아웃 |
| GET | /api/auth/me |
현재 사용자 정보 |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/posts |
게시글 목록 (페이지네이션) |
| GET | /api/posts/:id |
게시글 상세 |
| POST | /api/posts |
게시글 작성 (인증 필요) |
| PATCH | /api/posts/:id |
게시글 수정 (작성자만) |
| DELETE | /api/posts/:id |
게시글 삭제 (작성자만) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/images/upload |
이미지 업로드 (인증 필요) |
- Helmet.js: HTTP 보안 헤더 설정
- Rate Limiting: API 요청 횟수 제한
- Input Validation: class-validator를 통한 요청 데이터 검증
- Prisma Exception Filter: DB 에러 안전하게 처리
- Password Hashing: bcrypt를 사용한 비밀번호 해시화
- Network Isolation: Docker 네트워크 분리 (프론트엔드/백엔드)
- Health Check Protection: 내부 전용 헬스체크 엔드포인트
biswine/
├── apps/
│ ├── api/ # NestJS 백엔드
│ └── web/ # React 프론트엔드
├── packages/
│ └── shared-types/ # API 타입 정의 공유
├── compose.prod.yml # 프로덕션 Docker Compose
├── compose.local.yml # 로컬 개발 환경
└── biome.json # 코드 포매터/린터 설정
- Node.js 22+
- pnpm 10+
- Docker & Docker Compose
# 의존성 설치
pnpm install
# 로컬 MySQL 실행
docker compose -f compose.local.yml up -d
# API 개발 서버
cd apps/api
pnpm db:generate
pnpm db:migrate-dev
pnpm dev
# Web 개발 서버
cd apps/web
pnpm dev# 환경 변수 설정
cp .env.example .env.production
# 빌드 및 배포
pnpm build
pnpm dup