![]() |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|---|
| 노진경 | 안준형 | 이성진 | 이윤교 | 정서영 |
부글(Boogle) 은 사용자가 배변 상태와 생활 패턴을 간편하게 기록하고, 누적된 데이터를 바탕으로 나만의 장 컨디션 패턴을 확인하며 생활 습관을 조정할 수 있도록 돕는 장 건강 관리 웹앱입니다.
브리스톨 변 척도, 질병관리청·NIDDK 등 의학적 근거에 기반한 룰 테이블로 개인의 배변·생활 패턴을 자동 감지하고, 진단이 아닌 생활 습관 개선 가이드를 제공하도록 설계되었습니다. 기본 기록 30초 완료를 목표로 하는 간편한 UX로 꾸준한 기록 습관 형성을 유도하며, 병원 방문 시 참고할 수 있는 PDF 리포트도 제공합니다.
main ← develop ← feature
- main branch : 배포 파이프라인과 연결되어 있지 않음 (배포 트리거는 develop)
- develop branch : 개발 브랜치,
push시 GitHub Actions가 자동으로 빌드·배포함 (feature 브랜치가 merge됨) - feature branch : 페이지 / 기능 별 브랜치
npm i -g gitmoji-cli
# or
brew install gitmoji-
커밋 유형
- 🎉 Init: 프로젝트 세팅
- ✨ Feat: 새로운 기능 추가
- 🐛 Fix : 버그 수정
- 💄 Design : UI(CSS) 수정
- ✏️ Typing Error : 오타 수정
- 🚚 Mod : 폴더 구조 이동 및 파일 이름 수정
- 💡 Add : 파일 추가 (ex- 이미지 추가)
- 🔥 Del : 파일 삭제
- ♻️ Refactor : 코드 리펙토링
-
형식:
커밋유형: 상세설명 (#이슈번호) -
예시:
✨ Feat: 메인페이지 개발 (#1)
커밋 메시지 작성 (with Issue)
- git commit이 아닌 아래 명령어 사용
gitmoji -c- Choose a gitmoji : 위 commit style의 깃모지 사용
- Enter the commit title : 커밋 메세지 - ex)
Feat: 메인 페이지 개발 (#이슈번호)→ 여기서 커밋 컨벤션 맞게 작성하면 됨 - Enter the commit message : 커밋 메세지에 대한 설명, 없다면 그냥 enter
pre-commit 훅(Husky + lint-staged)이 staged된 *.ts 파일에 ESLint / Prettier를 자동으로 돌리므로, 커밋 전에 별도로 pnpm run lint를 실행하지 않아도 됩니다.
- 이슈 생성 후 브랜치 생성
- 브랜치 종류
init: 프로젝트 세팅feat: 새로운 기능 추가fix: 버그 수정refactor: 코드 리팩토링
- 형식:
브랜치종류/#이슈번호/상세기능 - 예시:
init/#1/settingsfeat/#3/mainPage
Issue Title 규칙
- 형식: [태그] 작업 요약
- 태그 목록:
Init: 프로젝트 세팅Feat: 새로운 기능 추가Fix: 버그 수정Refactor: 코드 리펙토링
- 예시:
- [Init] 프로젝트 초기 세팅
- [Feat] 로그인 API 구현
- Node.js 버전
- 24.14.0 (
package.json의engines및.nvmrc로 고정)
- 24.14.0 (
- pnpm 버전
- 10.12.1 (
package.json의packageManager필드로 고정)
- 10.12.1 (
- pnpm 버전 변경 방법
corepack use pnpm@버전 # 프로젝트 최상위 폴더 위치에서 명령어 입력
- pnpm 명령어 예시
pnpm install # 전체 설치
pnpm add 라이브러리 # 라이브러리 설치
pnpm run start:dev # 개발 서버 실행 (watch mode)
pnpm run build # 프로덕션 빌드
pnpm run lint # ESLint 검사
pnpm run format # Prettier 전체 포맷
pnpm test # Jest 단위 테스트 실행
pnpm run test:e2e # Jest E2E 테스트 실행
-
DB는 MySQL을 사용합니다.
-
Prisma 7부터는
PrismaClient가 직접 DB에 붙지 않고 driver adapter(@prisma/adapter-mariadb)를 통해 연결합니다. 그래서prisma/schema.prisma의datasource블록에는url을 두지 않고, 연결 문자열은prisma.config.ts(마이그레이션용)와PrismaService(런타임용) 양쪽에서process.env.DATABASE_URL로 읽습니다. -
로컬 MySQL 준비 (최초 1회)
brew install mysql
brew services start mysql
mysql -u root -e "CREATE USER IF NOT EXISTS 'boogle'@'localhost' IDENTIFIED BY 'boogle';"
mysql -u root -e "CREATE DATABASE IF NOT EXISTS boogle CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -e "GRANT ALL PRIVILEGES ON \`boogle\`.* TO 'boogle'@'localhost';"
# prisma migrate dev가 shadow DB를 만들 수 있도록 권한 부여
mysql -u root -e "GRANT ALL PRIVILEGES ON \`prisma_migrate_shadow_db%\`.* TO 'boogle'@'localhost';"
mysql -u root -e "FLUSH PRIVILEGES;"-
.env의DATABASE_URL이 위에서 만든 DB를 가리키는지 확인하세요 (.env.example참고). -
명령어
npx prisma generate # 스키마 변경 후 Prisma Client 재생성
npx prisma migrate dev # 로컬 DB에 마이그레이션 적용 및 생성
npx prisma studio # DB GUI 실행
- 스키마는
prisma/schema.prisma에서 관리합니다. - Prisma Client는
src/generated/prisma에 생성되며, git에는 포함되지 않습니다 (pnpm install또는 스키마 변경 후npx prisma generate로 생성).
- 같은 폴더(형제 파일)끼리는 상대경로(
./)를 그대로 씁니다. ex-main.ts에서./app.module - 상위 폴더로 거슬러 올라가야 하는 경우(
../)는@/alias(tsconfig.json의paths, →src/*)를 씁니다. ex-../generated/prisma/client대신@/generated/prisma/client nest build/nest start는 별도 도구(tsc-alias 등) 없이@/alias를 relative import로 그대로 컴파일해줍니다. 다만 Jest는 tsconfig의paths를 안 읽으므로package.json의jest.moduleNameMapper,test/jest-e2e.json의moduleNameMapper에 각각^@/(.*)$매핑이 되어 있어야 합니다. 새 alias를 추가하면 이 두 곳도 같이 맞춰주세요.
- Prisma 7의 생성된 클라이언트는 내부적으로
./enums.js처럼 확장자를 붙인 ESM 스타일 상대경로 import를 씁니다. ts-jest가 이를 못 찾는 문제가 있어 두 Jest 설정 모두moduleNameMapper에"^(\\.{1,2}/.*)\\.js$": "$1"매핑을 추가해 확장자를 벗겨줍니다. - Prisma 7의 WASM 쿼리 컴파일러는 내부적으로 동적
import()를 사용하는데, Jest 기본 실행 환경에서는 지원되지 않아 실제 DB에 연결하는 테스트(e2e 등)를 돌리려면NODE_OPTIONS=--experimental-vm-modules플래그가 필요합니다.test:e2e스크립트에cross-env로 이미 적용되어 있습니다.PrismaService를 실제로 초기화($connect)하는 테스트를 새로 추가한다면 같은 플래그가 필요할 수 있습니다.
.env.example을 복사해 .env를 만들고 실제 값을 채워주세요. .env.example만 git에 커밋됩니다.
소셜 로그인은 서버 주도 Authorization Code 방식입니다. 배포 환경에서는 다음 값을 반드시 실제 도메인 기준으로 설정해야 합니다.
FRONTEND_OAUTH_CALLBACK_URL: OAuth 처리 결과를 받을 프론트 화면 URLGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URIKAKAO_CLIENT_ID,KAKAO_REDIRECT_URIKAKAO_CLIENT_SECRET: Kakao 보안 설정에서 Client Secret을 활성화한 경우 필수JWT_ACCESS_SECRET,JWT_REFRESH_SECRET: 서로 다른 충분히 긴 임의 문자열AUTH_TEMPORARY_TOKEN_RETENTION: 사용 완료·만료된 OAuth 임시 토큰의 보존 기간(기본7d)AUTH_TEMPORARY_TOKEN_CLEANUP_INTERVAL: 임시 토큰 정리 주기(기본1h)ACCOUNT_LINK_TOKEN_EXPIRES_IN: 동일 이메일 소셜 계정 연동 토큰의 만료 시간(기본5m)
Google/Kakao 개발자 콘솔에 등록하는 Redirect URI는 각각 GOOGLE_REDIRECT_URI, KAKAO_REDIRECT_URI와 문자 단위로 같아야 합니다. 운영 DB에는 배포 전에 npx prisma migrate deploy를 실행해야 합니다.
- 로컬 프론트 기본 주소:
http://localhost:5173 - 로컬 Google Redirect URI:
http://localhost:8080/api/v1/auth/oauth/google/callback - 로컬 Kakao Redirect URI:
http://localhost:8080/api/v1/auth/oauth/kakao/callback - 운영 백엔드 주소:
https://api.glgc.cloud - 운영 Google Redirect URI:
https://api.glgc.cloud/api/v1/auth/oauth/google/callback - 운영 Kakao Redirect URI:
https://api.glgc.cloud/api/v1/auth/oauth/kakao/callback
운영 FRONTEND_ORIGIN과 FRONTEND_OAUTH_CALLBACK_URL에는 API 도메인이 아니라 실제 배포된 프론트엔드 도메인을 입력합니다. FRONTEND_ORIGIN은 로컬과 배포 주소를 쉼표로 함께 등록할 수 있습니다(예: http://localhost:5173,https://app.example.com). 프론트는 소셜 로그인 시작 URL에 자신의 Origin을 frontendOrigin 쿼리로 전달합니다(예: /api/v1/auth/oauth/google?frontendOrigin=http%3A%2F%2Flocalhost%3A5173). 서버는 허용 목록에 있는 Origin만 OAuth state에 저장하며, 로그인 완료 후 해당 Origin의 /oauth/callback으로 돌려보냅니다. frontendOrigin을 생략하면 기존 FRONTEND_OAUTH_CALLBACK_URL로 이동합니다.
사용자가 업로드한 프로필 이미지는 EC2 로컬 디스크가 아닌 비공개 S3 버킷에 저장하며, DB에는 profile-images/users/{userId}/{uuid}.{확장자} 형식의 Object Key만 저장합니다. EC2에는 AWS Access Key를 넣지 않고 Instance IAM Role을 연결해 AWS SDK 기본 자격 증명 체인을 사용합니다.
AWS_REGION: S3 버킷 리전AWS_S3_BUCKET: 비공개 프로필 이미지 버킷 이름AWS_CLOUDFRONT_BASE_URL: CloudFront를 사용하는 경우 배포 도메인, 사용하지 않으면 빈 값AWS_S3_SIGNED_URL_EXPIRES_IN: CloudFront 미사용 시 GET 서명 URL 만료 시간(초)
EC2 Instance IAM Role에는 실제 버킷 이름으로 치환한 다음 정책처럼 프로필 이미지 경로만 허용합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/profile-images/*"
}
]
}운영 배포 시 스키마 변경은 pnpm exec prisma migrate deploy로 적용하며 prisma db push를 사용하지 않습니다.
- camelCase
- 변수명, 함수명에 적용
- 첫글자는 소문자로 시작, 띄어쓰기는 붙이고 뒷 단어의 시작을 대문자로
- ex- handleDelete
- 언더바 사용 X (클래스명은 허용)
-
API 공통 prefix는
/api/v1입니다 (main.ts의app.setGlobalPrefix('api/v1')). 컨트롤러에는@Controller('home')처럼 리소스 경로만 적으면 실제로는/api/v1/home으로 노출됩니다. Swagger 문서(/api-docs)는 이 prefix의 영향을 받지 않습니다. -
모든 응답은 전역
ResponseInterceptor/HttpExceptionFilter(src/common)를 거쳐 API 명세서의 공통 Response Format대로 내려갑니다.- 성공:
{ success: true, data, message }(message기본값:"요청이 성공적으로 처리되었습니다.") - 실패:
{ success: false, code, message }
- 성공:
-
공통 에러코드(
src/common/constants/common-error-code.enum.ts): HTTP status에 따라 자동으로 매핑되는 기본 코드입니다. 별도 처리를 하지 않으면 아래 값이 그대로 내려갑니다.Status code 기본 message 400 BAD_REQUEST요청 값이 올바르지 않습니다. 401 UNAUTHORIZED로그인이 필요합니다. 403 FORBIDDEN해당 요청을 처리할 권한이 없습니다. 404 NOT_FOUND요청한 데이터를 찾을 수 없습니다. 409 CONFLICT이미 존재하는 데이터입니다. 500 INTERNAL_SERVER_ERROR서버 내부 오류가 발생했습니다. -
도메인 에러코드: 위 공통 코드로 표현이 안 되는 도메인 고유 에러(ex- 이미 가입된 소셜 계정)는 각 모듈의
*-error-code.enum.ts에도메인_번호(ex-AUTH_001) 형식으로 추가하고,BusinessException(errorCode, message, status)(src/common/exceptions)을 던져서 사용합니다.BusinessException은HttpExceptionFilter에서 공통 매핑보다 우선 적용됩니다. -
Swagger 문서는 서버 실행 후
/api-docs에서 확인할 수 있으며, Bearer 인증 스키마가 등록되어 있습니다.
📦boogle-server
┣ 📂prisma
┃ ┣ 📂migrations
┃ ┗ 📜schema.prisma
┣ 📂src
┃ ┣ 📂auth (회원가입/로그인(Google·Kakao OAuth)/온보딩, 경로: /auth)
┃ ┣ 📂user (회원 정보/알림 설정/프로필 이미지/회원탈퇴, 경로: /users)
┃ ┣ 📂home (홈 화면, 경로: /home)
┃ ┣ 📂record (부글 기록(배변), 경로: /records)
┃ ┣ 📂life-record (생활 기록, 경로: /life-records)
┃ ┣ 📂calendar (캘린더, 경로: /calendar)
┃ ┣ 📂report (주간·월간 리포트, PDF 리포트, 경로: /reports)
┃ ┣ 📂guide (가이드 카드/피드백, 경로: /guides)
┃ ┣ 📂food (음식 목록 - 생활 기록 등록 시 참조, 경로: /foods)
┃ ┣ 📂medicine (약/영양제 목록 - 생활 기록 등록 시 참조, 경로: /medicines)
┃ ┣ 📂notification (알림 목록/읽음 처리, 경로: /notifications)
┃ ┣ 📂push (FCM 푸시 토큰 등록/삭제, 경로: /push)
┃ ┃ ┣ 📂dto
┃ ┃ ┣ 📜guide-error-code.enum.ts
┃ ┃ ┣ 📜guide.controller.ts
┃ ┃ ┣ 📜guide.controller.spec.ts
┃ ┃ ┣ 📜guide.module.ts
┃ ┃ ┣ 📜guide.service.ts
┃ ┃ ┗ 📜guide.service.spec.ts
┃ ┃ (다른 도메인 모듈들도 대체로 위 guide와 동일한 구성: dto/ + *-error-code.enum.ts + *.controller.ts + *.module.ts + *.service.ts + 각 *.spec.ts)
┃ ┣ 📂common
┃ ┃ ┣ 📂dto
┃ ┃ ┃ ┗ 📜api-response.dto.ts (성공/실패 응답 타입)
┃ ┃ ┣ 📂exceptions
┃ ┃ ┃ ┗ 📜business.exception.ts (에러코드를 담는 커스텀 예외)
┃ ┃ ┣ 📂filters
┃ ┃ ┃ ┗ 📜http-exception.filter.ts (전역 예외 필터)
┃ ┃ ┣ 📂interceptors
┃ ┃ ┃ ┗ 📜response.interceptor.ts (전역 응답 래퍼)
┃ ┃ ┗ 📂swagger
┃ ┃ ┗ 📜error-example.util.ts (Swagger 에러 응답 예시 생성 유틸)
┃ ┣ 📂generated (Prisma Client 자동 생성 - git 미포함)
┃ ┣ 📂prisma
┃ ┃ ┣ 📜prisma.module.ts
┃ ┃ ┗ 📜prisma.service.ts
┃ ┣ 📜app.controller.ts
┃ ┣ 📜app.controller.spec.ts
┃ ┣ 📜app.module.ts
┃ ┣ 📜app.service.ts
┃ ┗ 📜main.ts
┣ 📂test
┃ ┣ 📜app.e2e-spec.ts
┃ ┗ 📜jest-e2e.json
┣ 📜.env.example
┣ 📜.gitignore
┣ 📜.husky (pre-commit 훅)
┣ 📜.lintstagedrc.json
┣ 📜.prettierrc
┣ 📜eslint.config.mjs
┣ 📜nest-cli.json
┣ 📜package.json
┣ 📜pnpm-lock.yaml
┣ 📜prisma.config.ts
┣ 📜README.md
┣ 📜tsconfig.build.json
┗ 📜tsconfig.json
- prisma -
schema.prisma에 DB 모델 정의, 마이그레이션 파일 관리 - src
- auth / user / home / record / life-record / calendar / report / guide / food / medicine / notification / push - 도메인별 모듈 (각
dto/폴더와*-error-code.enum.ts포함). 폴더/클래스명은 단수(ex-RecordController)이고 실제 API 경로는 복수형(ex-/api/v1/records)으로 노출됩니다. - common - 전역 응답 래퍼(
interceptors) / 예외 필터(filters) / 커스텀 예외(exceptions) / 응답 타입(dto) / Swagger 유틸(swagger) - generated/prisma -
prisma generate로 자동 생성되는 Prisma Client (직접 수정 X) - prisma - 전역으로 주입되는
PrismaService/PrismaModule
- auth / user / home / record / life-record / calendar / report / guide / food / medicine / notification / push - 도메인별 모듈 (각
-
배포 파이프라인:
develop브랜치에 push되면.github/workflows/deploy.yml의build잡이 GitHub Actions 러너에서 Docker 이미지를 빌드해 GHCR(ghcr.io/boogle-team/boogle-server)에 push합니다. 이어서deploy잡이 EC2에 SSH로 접속하지 않고,aws ssm send-command(AWS-RunShellScript)로scripts/deploy-remote.sh를 EC2에서 원격 실행합니다(EC2 보안그룹에서 22번 포트를 아예 막아뒀기 때문). 이 스크립트가git pull(compose 파일 동기화) → AWS SSM Parameter Store(/boogle/prod/*)에서 값을 직접 조회해.env재생성 → GHCR 로그인 →docker compose pull→ 일회성 컨테이너에서prisma migrate deploy및prisma db seed실행 →docker compose up -d --force-recreate→docker image prune로 낡은 이미지 정리, 순서로 재배포합니다. 애플리케이션 컨테이너 시작과 DB 마이그레이션을 분리해 여러 컨테이너가 동시에 마이그레이션을 실행하지 않도록 했습니다. 이미지 빌드는 EC2가 아니라 GitHub Actions에서 수행합니다 — t3.micro(RAM 1GB)에서 직접 빌드하면 메모리 부족으로 인스턴스 전체가 응답 불능 상태가 되는 문제가 반복돼서, 빌드를 러너로 옮기고 EC2는 완성된 이미지를 pull만 하도록 구조를 바꿨습니다. (SSH 기반 배포 → SSM Parameter Store 기반.env관리 → SSH 대신 SSM Run Command로 배포, 순서로 단계적으로 전환되었습니다.) -
EC2 접속: pem 키 SSH는 더 이상 안 됩니다 (보안그룹에서 22번 포트 제거함). CI/CD 배포도, 사람이 직접 접속할 때도 AWS Systems Manager만 사용합니다 (배포는 Run Command, 개인 접속은 Session Manager).
aws ssm start-session --target i-09994256d8b10d1f3
(로컬에
awscli+session-manager-plugin설치,aws configure로ssm:StartSession권한 있는 IAM 사용자 자격증명 설정 필요.) -
운영 환경변수 변경: EC2에 직접 들어가
.env를 수정하지 않습니다. 값을 SSM Parameter Store에서 바꾸면, 다음 배포 때 EC2가 알아서 최신 값으로.env를 다시 만듭니다.-
값 하나만 바꿀 때:
aws ssm put-parameter \ --name "/boogle/prod/<KEY>" \ --type SecureString \ --value "<새값>" \ --overwrite \ --region ap-northeast-2
-
로컬
.env를 통째로(여러 키) 반영할 때:./scripts/migrate-ssm-params.sh
-
값을 바꾼 뒤엔 코드 변경이 없어도 재배포를 트리거해야 반영됩니다:
gh workflow run deploy.yml --ref develop
-
현재 등록된 값 확인:
aws ssm get-parameters-by-path --path "/boogle/prod" --with-decryption --region ap-northeast-2
-
-
develop에서 문제가 된 커밋(들)을 되돌립니다.git checkout develop git pull origin develop git revert <문제_커밋_SHA> # 여러 개면 가장 최근 것부터 순서대로, 또는 -m 1로 머지 커밋 revert git push origin develop
-
push되면 Deploy 워크플로우가 자동으로 돌면서 되돌려진 상태로 재배포됩니다. 진행 상황은
gh run list --workflow deploy.yml,gh run watch <run-id>로 확인합니다. -
배포 완료 후 헬스체크로 정상화를 확인합니다.
curl -s https://api.glgc.cloud/api/v1/health
-
만약 CD 파이프라인 자체가 죽어 있거나(예: EC2 무응답) 위 방법으로 재배포가 안 되면,
aws ssm start-session --target i-09994256d8b10d1f3로 EC2에 접속해(pem 키 SSH는 비활성화됨) 같은 순서를 수동으로 실행합니다. 이미지는 더 이상 EC2에서 빌드하지 않으므로 GHCR에서 pull해야 하고, 이때는 GitHub Actions의 임시 토큰을 쓸 수 없어 개인 PAT(classic,read:packages권한, https://github.com/settings/tokens 에서 발급)로 직접 로그인해야 합니다.cd ~/boogle-server git fetch origin git checkout <되돌아갈_커밋_또는_브랜치> docker login ghcr.io -u <본인_github_아이디> # 비밀번호 자리에 PAT 입력 docker compose pull docker compose up -d
- 되돌리기 전에 먼저
docker compose logs -f app,/api/v1/health상태를 확인해 정말 배포가 원인인지(vs DB, 인프라 문제) 먼저 판단하는 것을 권장합니다.




