{"id":49820431,"url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_sabujak_be","last_synced_at":"2026-05-13T10:07:14.519Z","repository":{"id":344001467,"uuid":"1180014194","full_name":"prgrms-fullcycle-devcourse/webfull_9_10_Sabujak_BE","owner":"prgrms-fullcycle-devcourse","description":"프로그래머스 웹 풀스택 9기 10회차 사부작 BE","archived":false,"fork":false,"pushed_at":"2026-04-10T17:15:42.000Z","size":414,"stargazers_count":0,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-10T18:18:36.702Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prgrms-fullcycle-devcourse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-12T16:00:07.000Z","updated_at":"2026-04-10T16:16:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_Sabujak_BE","commit_stats":null,"previous_names":["prgrms-fullcycle-devcourse/webfull_9_10_sabujak_be"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/prgrms-fullcycle-devcourse/webfull_9_10_Sabujak_BE","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Sabujak_BE","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Sabujak_BE/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Sabujak_BE/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Sabujak_BE/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prgrms-fullcycle-devcourse","download_url":"https://codeload.github.com/prgrms-fullcycle-devcourse/webfull_9_10_Sabujak_BE/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Sabujak_BE/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32977396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T06:31:55.726Z","status":"ssl_error","status_checked_at":"2026-05-13T06:31:51.336Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-13T10:06:46.774Z","updated_at":"2026-05-13T10:07:14.512Z","avatar_url":"https://github.com/prgrms-fullcycle-devcourse.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 사부작 백엔드\n\n\u003cdiv align=\"center\"\u003e\n\n![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge\u0026logo=nodedotjs\u0026logoColor=white)\n![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=for-the-badge\u0026logo=typescript\u0026logoColor=white)\n![Express](https://img.shields.io/badge/Express-5-000000?style=for-the-badge\u0026logo=express\u0026logoColor=white)\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15-4169E1?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white)\n![pnpm](https://img.shields.io/badge/pnpm-9.x-F69220?style=for-the-badge\u0026logo=pnpm\u0026logoColor=white)\n![Docker](https://img.shields.io/badge/Docker-Compose-2496ED?style=for-the-badge\u0026logo=docker\u0026logoColor=white)\n![OpenAPI](https://img.shields.io/badge/OpenAPI-Swagger_UI-85EA2D?style=for-the-badge\u0026logo=swagger\u0026logoColor=black)\n\n\u003c/div\u003e\n\n사부작의 타임캡슐 생성, 조회, 수정, 삭제와 익명 메시지 작성을 담당하는 Express + TypeScript 기반 백엔드 서버입니다.  \nZod 기반 입력 검증, OpenAPI 문서 생성, PostgreSQL 연동을 중심으로 MVP API를 구성하고 있습니다.\n\n## Overview\n\n\u003e \"미래의 나와 우리에게 남기는 메시지\"\n\n사부작은 특정 시점에 열리는 타임캡슐을 만들고, 그 안에 익명 메시지를 모아 공개 시점 이후 함께 확인하는 서비스입니다.  \n이 저장소는 프론트엔드와 협업하기 위한 REST API, OpenAPI 문서, 로컬 개발 환경 구성을 포함합니다.\n\n## Live Links\n\n| Service    | URL                                                                                                    |\n| ---------- | ------------------------------------------------------------------------------------------------------ |\n| Frontend   | [webfull-9-10-sabujak-fe.vercel.app](https://webfull-9-10-sabujak-fe.vercel.app/)                      |\n| Swagger UI | [webfull-9-10-sabujak-be.onrender.com/api-docs](https://webfull-9-10-sabujak-be.onrender.com/api-docs) |\n\n## Key Features\n\n- 타임캡슐 생성 및 공개 시점 기반 상태 관리\n- 슬러그 예약 기반 중복 방지 흐름\n- 공개 전/후 단일 조회 API 제공\n- 익명 메시지 작성 및 닉네임 중복 제어\n- Swagger UI와 OpenAPI JSON 제공\n- Docker Compose 기반 로컬 개발 환경 지원\n\n## Tech Stack\n\n| Category   | Stack                                        |\n| ---------- | -------------------------------------------- |\n| Runtime    | Node.js 20+, TypeScript                      |\n| Server     | Express 5                                    |\n| Validation | Zod                                          |\n| API Docs   | `@asteasolutions/zod-to-openapi`, Swagger UI |\n| Database   | PostgreSQL, Redis                            |\n| Tooling    | pnpm, ESLint, Prettier, Husky                |\n| Infra      | Docker Compose                               |\n\n## Architecture\n\n```mermaid\nflowchart LR\n    FE[\"Frontend Client\"] --\u003e|HTTP Request| API[\"Express API Server\"]\n    API --\u003e ROUTER[\"Route Layer\"]\n    ROUTER --\u003e CTRL[\"Controller\"]\n    CTRL --\u003e VALID[\"Zod DTO Validation\"]\n    CTRL --\u003e SERVICE[\"Service\"]\n    SERVICE --\u003e REPO[\"Repository\"]\n    REPO --\u003e DB[(\"PostgreSQL\")]\n    REPO --\u003e REDIS[(\"Redis\")]\n    API --\u003e DOCS[\"Swagger UI / OpenAPI\"]\n    DOCS --\u003e SPEC[\"openapi.json\"]\n\n    subgraph PIPE[\"Schema-to-Client Pipeline\"]\n        DTO[\"src/modules/*/dto\"] --\u003e REG[\"OpenAPI Registry\"]\n        REG --\u003e SPEC\n        SPEC --\u003e ORVAL[\"Orval Client Generation\"]\n        ORVAL --\u003e FECLIENT[\"Frontend API Client\"]\n    end\n\n    subgraph DEPLOY[\"Deployment Architecture\"]\n        VERCEL[\"Vercel Frontend\"] --\u003e RENDER[\"Render Backend\"]\n        RENDER --\u003e NEON[(\"Neon PostgreSQL\")]\n    end\n\n    DTO -.schema source.-\u003e VALID\n    FECLIENT --\u003e FE\n    VERCEL -.client requests.-\u003e FE\n    RENDER -.runtime environment.-\u003e API\n    NEON -.managed database.-\u003e DB\n```\n\n사부작 백엔드는 Express 라우터가 요청을 받고, Controller가 입력 DTO를 Zod로 검증한 뒤 Service와 Repository를 거쳐 PostgreSQL/Redis와 통신합니다.  \n또한 `src/modules/*/dto`에서 정의한 Zod 스키마를 `src/openapi/registry.ts`에 등록해 OpenAPI 문서를 만들고, 그 산출물을 프론트엔드 Orval 클라이언트와 맞추는 흐름을 사용합니다.\n\n## Project Structure\n\n```text\n.\n├── src\n│   ├── app.ts\n│   ├── main.ts\n│   ├── routes.ts\n│   ├── common\n│   ├── db\n│   ├── modules\n│   │   ├── capsules\n│   │   │   └── dto\n│   │   └── system\n│   ├── openapi\n│   └── redis\n├── docs\n│   ├── API_SPEC.md\n│   ├── ERD.md\n│   ├── architecture-guide.md\n│   ├── db-guide.md\n│   ├── reports\n│   └── reviews\n├── scripts\n├── openapi.json\n├── docker-compose.yml\n└── README.md\n```\n\n## Documents\n\n- [API 명세서](./docs/API_SPEC.md)\n- [ERD](./docs/ERD.md)\n- [DB 스키마 가이드](./docs/db-guide.md)\n- [아키텍처 가이드](./docs/architecture-guide.md)\n- [OpenAPI JSON](./openapi.json)\n- [QA 보고서 위치 안내](./docs/reports/README.md)\n\n## Artifact Ownership\n\n| 산출물                              | 정본 위치                       | 설명                                    |\n| ----------------------------------- | ------------------------------- | --------------------------------------- |\n| 백엔드 unit/integration 테스트 코드 | 백엔드 레포                     | 구현과 함께 변경되는 검증 자산          |\n| 백엔드 기술 문서                    | 백엔드 `docs/`                  | 아키텍처, API, 스키마 문서              |\n| 블랙박스 QA 결과 보고서             | QA 레포 `docs/reports/backend/` | smoke/regression, 장애보고서, 대응 계획 |\n\n## Development Workflow\n\n1. 기능 요구사항을 기준으로 `docs/API_SPEC.md`와 필요 시 `docs/ERD.md`를 먼저 갱신합니다.\n2. DB 스키마는 `src/db/schema.ts`와 `drizzle/*`만 정본으로 관리하고, `pnpm run db:generate -\u003e db:migrate -\u003e db:schema:export -\u003e db:schema:check` 순서로 반영합니다.\n3. `docs/schema.sql`은 Drizzle 기준 참조 스냅샷이며 직접 실행하지 않습니다.\n4. 배포 환경에서는 앱 기동 전에 운영 `DATABASE_URL` 기준 `pnpm run db:migrate`를 먼저 실행하고, 서버 시작이 migration을 대체하지 않도록 유지합니다.\n5. `src/modules/*/dto/`에 Zod 스키마를 정의하고, 필요한 경우 `controller`, `service`, `repository`를 순서대로 보강합니다.\n6. `src/openapi/registry.ts`에 경로와 스키마를 등록한 뒤 `pnpm run openapi:generate` 또는 `pnpm run openapi:check`로 산출물을 맞춥니다.\n7. 생성된 `openapi.json`을 기준으로 프론트엔드에서 Orval 클라이언트를 동기화합니다.\n8. Swagger UI, 로컬 실행, 배포 환경(Render/Neon)에서 최종 동작을 확인합니다.\n\n## Getting Started\n\n### 1. Prerequisites\n\n- Node.js `20+`\n- pnpm `9.x`\n- Docker / Docker Compose\n\n### 2. Install Dependencies\n\n```bash\ncorepack enable\ncorepack use pnpm@9.0.0\npnpm install\n```\n\n`package.json`의 `packageManager`와 동일한 pnpm major를 사용해야 lockfile 해석 차이로 인한 의존성 누락을 피할 수 있습니다.\n\n### 3. Configure Environment Variables\n\n```bash\ncp .env.example .env\n```\n\n필요 시 `.env`에서 포트, DB 연결 정보, CORS 허용 출처를 프로젝트 환경에 맞게 수정합니다.\n\n### 4. Run with Docker Compose\n\n```bash\ndocker compose up --build\n```\n\n기본적으로 API 서버, PostgreSQL, Redis가 함께 실행됩니다.\n\n### 5. Run Locally\n\nDB와 Redis가 준비되어 있다면 아래 명령으로 개발 서버를 실행할 수 있습니다.\n\n```bash\npnpm dev\n```\n\n## Available Scripts\n\n| Command                       | Description                   |\n| ----------------------------- | ----------------------------- |\n| `pnpm dev`                    | 개발 서버 실행                |\n| `pnpm build`                  | 프로덕션 번들 생성            |\n| `pnpm start`                  | 빌드 결과 실행                |\n| `pnpm typecheck`              | 타입 검사                     |\n| `pnpm lint`                   | 린트 검사                     |\n| `pnpm lint:fix`               | 린트 자동 수정                |\n| `pnpm test`                   | Jest 테스트 실행              |\n| `pnpm test:unit`              | 동일한 Jest 테스트 실행       |\n| `pnpm db:generate`            | Drizzle migration 생성        |\n| `pnpm db:migrate`             | Drizzle migration 적용        |\n| `pnpm db:schema:export`       | `docs/schema.sql` 스냅샷 갱신 |\n| `pnpm db:schema:check`        | DB 스키마 워크플로우 검증     |\n| `pnpm db:repair:legacy-drift` | legacy drift 수동 복구        |\n| `pnpm openapi:generate`       | OpenAPI 문서 생성             |\n| `pnpm openapi:check`          | OpenAPI 산출물 검증           |\n| `pnpm test:rate-limit`        | rate limit 관련 스크립트 실행 |\n\n## API Entry Points\n\n로컬 실행 기준 주요 접근 경로입니다.\n\n| Path            | Description            |\n| --------------- | ---------------------- |\n| `/`             | 기본 확인용 엔드포인트 |\n| `/healthCheck`  | 서버 상태 확인         |\n| `/openapi.json` | OpenAPI 스펙           |\n| `/api-docs`     | Swagger UI             |\n| `/capsules/*`   | 타임캡슐 관련 API      |\n\n## Environment Variables\n\n자세한 예시는 [`.env.example`](./.env.example)에 정리되어 있습니다.\n\n| Variable                   | Description                     |\n| -------------------------- | ------------------------------- |\n| `NODE_ENV`                 | 실행 환경                       |\n| `API_PORT`                 | API 서버 포트                   |\n| `POSTGRES_USER`            | 로컬 PostgreSQL 계정            |\n| `POSTGRES_PASSWORD`        | 로컬 PostgreSQL 비밀번호        |\n| `POSTGRES_DB`              | 로컬 PostgreSQL DB 이름         |\n| `DATABASE_URL`             | DB 연결 문자열                  |\n| `REDIS_URL`                | 로컬 Redis 연결 문자열          |\n| `CORS_ORIGIN`              | 허용할 CORS origin 목록         |\n| `CHOKIDAR_USEPOLLING`      | Docker 개발 환경 파일 감시 옵션 |\n| `UPSTASH_REDIS_REST_URL`   | Upstash Redis REST URL          |\n| `UPSTASH_REDIS_REST_TOKEN` | Upstash Redis REST 토큰         |\n\n## Team\n\n| 프로필                                                          | 이름   | 역할     | GitHub                                                 |\n| --------------------------------------------------------------- | ------ | -------- | ------------------------------------------------------ |\n| \u003cimg src=\"https://github.com/labyrinth30.png\" width=\"50\" /\u003e     | 이윤하 | Backend  | [@labyrinth30](https://github.com/labyrinth30)         |\n| \u003cimg src=\"https://github.com/insu1170.png\" width=\"50\" /\u003e        | 김인수 | Backend  | [@insu1170](https://github.com/insu1170)               |\n| \u003cimg src=\"https://github.com/s576air.png\" width=\"50\" /\u003e         | 한재민 | Frontend | [@s576air](http://github.com/s576air)                  |\n| \u003cimg src=\"https://github.com/supersoldier132.png\" width=\"50\" /\u003e | 송창욱 | Frontend | [@supersoldier132](https://github.com/supersoldier132) |\n| \u003cimg src=\"https://github.com/yomiyoni.png\" width=\"50\" /\u003e        | 이연희 | Frontend | [@yomiyoni](https://github.com/yomiyoni)               |\n\n## Collaboration Guide\n\n- API 변경 시 `docs/API_SPEC.md`와 `openapi.json`을 함께 갱신합니다.\n- 데이터 모델 변경 시 `docs/ERD.md`와 `docs/db-guide.md` 흐름도 함께 반영합니다.\n- DB 변경은 수동 SQL 대신 Drizzle migration만 사용합니다.\n- 배포 시 DB migration은 앱 시작 전에 한 번만 실행하고, 서버 재기동으로 스키마를 맞추지 않습니다.\n- 외부 입력은 Zod 스키마로 검증하는 규칙을 유지합니다.\n- 민감 정보는 `.env.example`이 아닌 실제 `.env` 또는 배포 환경 변수로 관리합니다.\n\n## License\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_sabujak_be","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_sabujak_be","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_sabujak_be/lists"}