π³ βν¨κ» μ¬λ 곡κ°, ν¨κ» λ§λλ κ΄λ¦¬.β
μ리λΈ(Welive)λ μ
μ£Όλ―Όκ³Ό κ΄λ¦¬ λ¨μ²΄κ° νμ리μμ μν΅νκ³ λ¬Έμ λ₯Ό ν΄κ²°νλ©°, λ λμ 곡λ체λ₯Ό λ§λ€μ΄κ°λ μννΈ μνΈ κ΄λ¦¬ νλ«νΌμ
λλ€.
λΉμ μ μΌμμ΄ λ νΈν΄μ§λ κ·Έ μκ°κΉμ§, μ리λΈκ° ν¨κ»ν©λλ€.
- Frontend: https://welive.mimu.live
- Backend: https://api.mimu.live
- Swagger λ¬Έμ: https://api.mimu.live/docs
- SUPER_ADMIN κ³μ λ‘κ·ΈμΈ νμ
- GitHub Repository: https://github.com/codeit-welive/welive
- μ΅μ’ λ°ν μλ£: https://www.canva.com/design/DAG5lY6qiKI/GFW3js2SBZA3F8Q9vJrkLg/edit
- μμ° μμ: https://drive.google.com/file/d/1C8UfyI5FkuQbRDflhq3Kx3OKuWppcZIU/view?usp=sharing
- ν λ Έμ λ¬Έμ: https://notion.so/234e98c187a680acbd98e3590dce1382?source=copy_link
Node.js + Express 5
- λκ·λͺ¨ μλΉμ€μμλ κ²μ¦λ κ²½λ μΉ νλ μμν¬λ‘, λΌμ°ν Β·λ―Έλ€μ¨μ΄ κΈ°λ° κ΅¬μ‘°λ₯Ό μΈλ°νκ² μ€κ³νκΈ° μ©μ΄ν¨
- λΉλκΈ° I/O κΈ°λ°μΌλ‘ SSEΒ·Socket.io λ± μ€μκ° κΈ°λ₯ ꡬνμ μ ν©
- Layered Architecture κ΅¬μ± μ Router β Controller β Service β Repository νλ¦μ κ°μ₯ μ μ°νκ² μ€κ³ν μ μμ
TypeScript
- λλ©μΈ λ¨μλ‘ νμ μ μ격νκ² μ μν΄ λκ·λͺ¨ λͺ¨λ κ° μμ‘΄μ±μ μμ μ μΌλ‘ μ μ§
- DTOΒ·ValidatorΒ·Repository λ± κ³μΈ΅ κ° λ°μ΄ν° κ³μ½μ λͺ νν 보μ₯
- Prismaμ κ²°ν©νμ¬ μ»΄νμΌ λ¨κ³μμ DB μ€ν€λ§ μ€λ₯λ₯Ό μ‘°κΈ° κ°μ§ν μ μμ
Prisma + PostgreSQL
- Prismaμ μ€ν€λ§ κΈ°λ° ORMμ λͺ¨λΈ λ³κ²½ λ° λ§μ΄κ·Έλ μ΄μ κ΄λ¦¬κ° μ§κ΄μ μ΄λ©°, λκ·λͺ¨ ν μ΄λΈ μ‘°μΈ κ΅¬μ‘°μμλ νμ μμ μ±μ μ 곡
- PostgreSQLμ νΈλμμ κ°λκ° λμ Poll SchedulerΒ·μλ¦ΌΒ·λλ CSV μ²λ¦¬ λ± λμμ± μꡬμ μμ μ μΌλ‘ λμ
Docker + Nginx
- κ°λ°Β·ν μ€νΈΒ·μ΄μ νκ²½μ μλ²½νκ² λμΌνκ² μ μ§ν μ μμ΄ λ°°ν¬ μμ μ±μ΄ λμ
- Nginx Reverse Proxyλ₯Ό ν΅ν΄ SSL, μ μ μμ μλΉμ€, CORS, ν€λ 보μ λ±μ λ¨μΌ μ§μ μ μμ μ μ΄ κ°λ₯
- BE(컨ν μ΄λ) + FE(PM2) μ‘°ν©μΌλ‘ μ΄κΈ° λ‘λ© μκ°μ μ΅μ ννλ©΄μλ CI/CD μλνμ μ 리
AWS(EC2, S3)
- EC2λ Docker νκ²½ λ°°ν¬μ μ§κ΄μ μ΄λ©° νμ₯ λ° μ μ§λ³΄μκ° μ©μ΄
- S3λ μ΄λ―Έμ§Β·μ μ νμΌ μ μ₯μ μμ μ μ΄λ©°, Presigned URL κΈ°λ° μ λ‘λλ‘ λ³΄μ μμ€μ ν¬κ² λμΌ μ μμ
GitHub Actions + GHCR
- νΈμ μ΄λ²€νΈ κΈ°λ°μΌλ‘ ν μ€νΈ β λΉλ β μ΄λ―Έμ§ μμ± β μλ² μλ λ°°ν¬κΉμ§ μμ μλν
- GHCR μ¬μ©μΌλ‘ μ΄λ―Έμ§ λ²μ κ΄λ¦¬ λ° λ‘€λ°±μ΄ μ©μ΄νλ©°, μ¬μ€ λ μ§μ€νΈλ¦¬λ³΄λ€ μ€μ μ΄ λ¨μ
Jest + Supertest
- DB μ°λ ν μ€νΈ, SSE μ λ¬ ν μ€νΈ, μ€μΌμ€λ¬ λμκΉμ§ κ²μ¦ κ°λ₯ν ꡬ쑰
- ν μ€νΈ DBλ₯Ό λ³λλ‘ κ΅¬μ±ν΄ μ€μ Prisma νΈλμμ νλ¦μ κ·Έλλ‘ μ¬ν κ°λ₯
- Zod: DTO/Query/Paramsλ₯Ό μ€ν€λ§ κΈ°λ°μΌλ‘ ν΅ν© κ΄λ¦¬νμ¬ κ²μ¦ μΌκ΄μ±μ 보μ₯
- DOMPurify + jsdom: μλ²λ¨ SanitizationμΌλ‘ XSS 곡격 μ°¨λ¨ (FEμμ μ‘°μνλλΌλ 무ν¨ν)
- sharp: μ΄λ―Έμ§ 리μ¬μ΄μ¦ λ° Magic Number κΈ°λ° MIME κ²μ¦μ ν΅ν νμΌ μμ₯ 곡격 λ°©μ§
- Limiter(μ§μ ꡬν): Poll μ€μΌμ€λ¬ λμμ± μ μ΄(λ¨μΌ μ€ν 보μ₯)
- Socket.io / SSE: μ±ν + μ€μκ° μλ¦Ό κ°κ°μ νΉμ±μ νμ©ν μ΄μ€ μ€μκ° μ²λ¦¬ ꡬ쑰
-
μν κΈ°λ° μΈμ¦/μΉμΈ νλ‘μΈμ€
- μ μ£Όλ―Ό Β· κ΄λ¦¬μ Β· μνΌκ΄λ¦¬μ λ± μν λ³ νμκ°μ , μΉμΈ μμ², μΉμΈ/κ±°μ νλ¦
-
μ μ£Όλ―Ό λͺ λΆ κ΄λ¦¬
- CSV μ λ‘λ, κ²μ/νν°λ§, νμ΄μ§, μΌκ΄ μ 리 κΈ°λ₯ μ 곡
-
곡μ§μ¬ν / λ―Όμ / λκΈ μμ€ν
- CRUD, μΉ΄ν κ³ λ¦¬, μ‘°νμ, λΉκ³΅κ° μ²λ¦¬, μμ μ ν λ± μ΄μ κΈ°λ₯ ν¬ν¨
-
μ€μκ° μλ¦Ό μμ€ν (SSE)
- λ―Όμ/곡μ§/ν¬ν μν λ³κ²½ μ μ¦μ μλ¦Ό μ΄λ²€νΈ μ λ¬
-
μ£Όλ―Ό ν¬ν μμ€ν (Polls)
- ν¬νκΆμ λ²μ μ€μ , μλ μ€ν/λ§κ°, μ€μκ° μ°Έμ¬, κ²°κ³Ό μλ μ§κ³
-
μ΄λ²€νΈ μΊλ¦°λ (κ΄λ¦¬μμ©)
- μννΈ λ΄ μΌμ κ΄λ¦¬ λ° κ³΅μ§μ μ°λλλ μ΄λ²€νΈ CRUD μ 곡
-
μ€μκ° μ±ν (Socket.io)
- μ μ£Όλ―Όβκ΄λ¦¬μ κ° λΉ λ₯Έ μλλ₯Ό μν μ€μκ° λ©μμ§ κΈ°λ₯
-
νλ‘ν μ΄λ―Έμ§ μ λ‘λ (S3 Presigned URL)
- Magic Number κΈ°λ° κ²μ¦μΌλ‘ μμ₯ νμΌ μ°¨λ¨, μμ ν νμΌ μ²λ¦¬
-
λμμ± μ μ΄ κΈ°λ° μ€μΌμ€λ¬ (Limiter)
- Poll μλ νμ±ν/λ§λ£ μ λ¨μΌ μ€ν 보μ₯ λ° κ²½μ 쑰건 λ°©μ§
μ리λΈλ νμ΅μ© μμ κ° μλλΌ, μ€μ μννΈ κ΄λ¦¬ λλ©μΈμ κ³ λ €ν
βκΈ°λ₯·보μΒ·νμ₯μ± μ€μ¬μ μ€μ νλ‘μ νΈβλ‘ μ€κ³λμμ΅λλ€.
flowchart LR
subgraph CLIENT["Client"]
A[Browser / Next.js]
end
subgraph PROXY["Nginx Reverse Proxy"]
B[Nginx]
end
subgraph BACKEND["Backend (Node.js Β· Express 5)"]
C[Router]
D[Controller]
E[Service]
F[Repository]
G[Prisma Client]
end
subgraph DATABASE["PostgreSQL (RDS)"]
H[(PostgreSQL)]
end
subgraph STORAGE["AWS S3"]
S3[S3 Bucket - Profile Images]
end
subgraph CICD["CI/CD Pipeline"]
GA[GitHub Actions]
GHCR[GHCR - Docker Image]
EC2[EC2 - Docker Runtime]
end
%% Client -> Backend
A --> B --> C
C --> D --> E --> F --> G --> H
%% File Upload
E -->|Upload/Delete| S3
%% CI/CD Flow
GA --> GHCR --> EC2 --> B
- Apartments β Residents : ν μννΈμλ μ¬λ¬ μ μ£Όλ―Όμ΄ μμ
- Residents β Complaints / Notices / Comments : μ μ£Όλ―Όμ΄ μμ±ν λ―Όμ·곡μ§Β·λκΈκ³Ό μ°κ²°
- Polls β PollVotes : ν¬ν 1κ°μ μ¬λ¬ ν¬ν κΈ°λ‘μ΄ μ’ μ
- Complaints / Notices / Polls β Events : λͺ¨λ μ£Όμ νλμ μ΄λ²€νΈ λ‘κ·Έλ‘ κΈ°λ‘
- Events β Notifications : μν λ³κ²½ μ μλ¦Ό(SSE)λ‘ μ°λ
- Users β Residents : μ£Όλ―Ό κ³μ κ³Ό μ¬μ©μ κ³μ μ 1:1 λ§€ν ꡬ쑰
ERDλ Prisma Schema κΈ°λ°μΌλ‘ μλ μμ±λλ©°,
κ΄κ³ν λͺ¨λΈμ ν΅ν΄ κΆν/μν κΈ°λ° κ΄λ¦¬, μ€μκ° μλ¦Ό, ν¬ν μμ€ν λ±
μ£Όμ κΈ°λ₯μ μΌκ΄λ κ΅¬μ‘°λ‘ νμ₯ν μ μλλ‘ μ€κ³νμ΅λλ€.
μλ¦¬λΈ λ°±μλλ Express 5 κΈ°λ°μ Layered Architectureλ‘ κ΅¬μ±λμ΄ μμΌλ©°,
κ° λ μ΄μ΄κ° λͺ
νν μν μ κ°μ§λλ‘ μ€κ³ν΄ μ μ§λ³΄μμ±κ³Ό νμ₯μ±μ λμμ΅λλ€.
-
Router Layer
- μμ² μλν¬μΈνΈ μ μ λ° λͺ¨λ λ¨μ λΌμ°ν ꡬμ±
- μΈμ¦/μΈκ°, CSV κ²μ¦, Sanitization λ± κ³΅ν΅ λ―Έλ€μ¨μ΄ μ μ© μ§μ
-
Controller Layer
- HTTP μμ²/μλ΅ μ²λ¦¬
- μλΉμ€ νΈμΆ μ μ λ ₯κ° κ²μ¦(Zod) λ° μμΈ νλ¦ ν΅μ
- Swagger μλ λ¬Έμν μ£Όμ μμΉ
-
Service Layer
- λΉμ¦λμ€ λ‘μ§ μ€μ¬
- κΆν κ²μ¦, νΈλμμ μ²λ¦¬, λλ©μΈ κ·μΉ μ μ©
- SSE μλ¦Ό νΈλ¦¬κ±°, Poll μ€μΌμ€λ¬ νΈμΆ λ± ν΅μ¬ λ‘μ§ λ΄λΉ
-
Repository Layer
- Prisma κΈ°λ° λ°μ΄ν°λ² μ΄μ€ μ κ·Ό
- 볡μ‘ν 쿼리/μ‘°μΈ/νΈλμμ μ μΊ‘μννμ¬ Serviceμ λΆλ¦¬
-
Prisma Client Layer
- PostgreSQLκ³Ό μ§μ ν΅μ
- νμ μΈμ΄νν° μ 곡 λ° μ€ν€λ§ κΈ°λ° λͺ¨λΈ κ΄λ¦¬
-
Input Sanitization (DOMPurify + jsdom)
λκΈ/곡μ§/λ―Όμ λ± μ¬μ©μ μ λ ₯ νλμ λν΄ μλ² λ¨μμ HTML μ ν μν -
Validation Layer (Zod)
Params/Query/Bodyλ₯Ό μ€ν€λ§ κΈ°λ°μΌλ‘ ν΅ν© κ²μ¦νμ¬ Controllerμ λλ¬νκΈ° μ λ°μ΄ν° λ¬΄κ²°μ± λ³΄μ₯ -
SSE Notification Layer
μλ¦Ό emitterλ₯Ό λ³λ λͺ¨λλ‘ λΆλ¦¬ν΄ Poll/λ―Όμ/κ³΅μ§ λ±μ μ΄λ²€νΈλ₯Ό μ€μκ°μΌλ‘ μ μ‘ -
Poll Scheduler (Limiter κΈ°λ° λ¨μΌ μ€ν 보μ₯)
Poll μλ μ€ν/λ§λ£λ₯Ό Cron + Limiter μ‘°ν©μΌλ‘ ꡬνν΄ κ²½μ 쑰건μ λ°©μ§ -
Socket Layer (μ€μκ° μ±ν )
μ μ£Όλ―Όβκ΄λ¦¬μ κ° μ€μκ° λ©μμ§μ Socket.ioλ‘ μ²λ¦¬ (JWT μΈμ¦ λ―Έλ€μ¨μ΄ ν¬ν¨)
μ΄ λ μ΄μ΄ ꡬ쑰λ λλ©μΈ νμ₯Β·ν μ€νΈΒ·λ³΄μΒ·λ°°ν¬μ λͺ¨λ κ³Όμ μμ μΌκ΄λ νλ¦μ μ μ§νλ©°,
λκ·λͺ¨ μλΉμ€ κ΅¬μ‘°λ‘ νμ₯ν΄λ μμ μ μΌλ‘ μ μ§λ μ μλλ‘ μ€κ³λμμ΅λλ€.
μ리λΈλ Express 5 κΈ°λ° λ€μΈ΅ 보μ ꡬ쑰λ₯Ό μ μ©ν΄
μ
λ ₯ κ²μ¦λΆν° νμΌ μ
λ‘λ, μΈμ¦, μλ² ν€λκΉμ§ μ λ°μ μΈ λ³΄μ 리μ€ν¬λ₯Ό μ΅μννμ΅λλ€.
- helmet(CSP μ μ©): μ€ν¬λ¦½νΈ/μ΄λ―Έμ§/νλ μ μ μ± κ°νλ‘ XSSΒ·Clickjacking λ°©μ§
- X-Powered-By μ κ±°, ETag λΉνμ±νλ‘ νλ μμν¬ λ ΈμΆ λ° μΊμ± κΈ°λ° κ³΅κ²© μν
- HPP(HTTP Parameter Pollution) μ°¨λ¨ μ μ©
- JWT Access / Refresh Token ꡬ쑰
- Refresh Tokenμ DB μν κ΄λ¦¬ λ°©μμΌλ‘ ꡬνν΄ μ¬λ°κΈ μ κΈ°μ‘΄ ν ν° λ¬΄ν¨ν
- λΌμ°ν° λ¨μλ‘ μν κΈ°λ° μ κ·Ό μ μ΄(Role-based Access Control) μ μ©
- Strict Origin κΈ°λ° CORS νμ΄νΈλ¦¬μ€νΈ
trust proxyμ€μ μΌλ‘ Nginx λ€μμ IP/νλ‘ν μ½ μ 보 μ νν νμ±
- Zod μ€ν€λ§ κΈ°λ° Params/Query/Body μ νμ κ²μ¦
- μ¬μ©μ μ λ ₯κ°μ DOMPurify + jsdom κΈ°λ° μλ²λ¨ SanitizationμΌλ‘ XSS μ°¨λ¨
- CSV μ λ‘λ μ μ νν ν€λΒ·νμ κ²μ¦ λ° μ 체 νΈλμμ μ²λ¦¬
- μ΄λ―Έμ§ μ λ‘λλ νμ₯μκ° μλ Magic Number(MIME) κΈ°λ° κ²μ¦
- sharpλ₯Ό μ΄μ©ν κ°μ μ¬μΈμ½λ©(μ¬μμΆ)μΌλ‘ βν΄λ¦¬κΈλ‘―(polyglot) νμΌβ μ°¨λ¨
- μ: JPEG/PNGλ‘ μμ₯ν μ€ν¬λ¦½νΈΒ·μ€ννμΌ νΌν© νμ΄λ‘λ κ°μ μ κ±°
- μ¬μΈμ½λ© νμλ§ Presigned URL κΈ°λ° S3 μ λ‘λ νμ©
- μμ₯ νμΌ(μ: PNG ν€λ + JS νμ΄λ‘λ) μ¦μ κ±°λΆ
- express-rate-limit: IP λ¨μ μμ² μ ν μ μ©
- JSON Body μ¬μ΄μ¦ μ ν(2MB), URL-encoded μ ν
- SSE μλ¦Ό: JWT κΈ°λ° μΈμ¦
- Socket.io μ±ν : Connection middlewareλ‘ μΈμ¦ ν μμΌ ν립
- Pino κΈ°λ° κ΅¬μ‘°μ λ‘κΉ
- κΈλ‘λ² μλ¬ νΈλ€λ¬λ‘ λ΄λΆ μ€ν μ¨κΉ & μΌκ΄λ μλ¬ μλ΅ ν¬λ§· μ μ§
μ λ ₯ κ²μ¦, Sanitization, μ¬μΈμ½λ© κΈ°λ° νμΌ λ³΄μ, JWT μΈμ¦, CSP λ±
μ¬λ¬ 보μ κ³μΈ΅μ μ μ©νμ¬ μμ μ±κ³Ό μ λ’°μ±μ κ°ννμ΅λλ€.
μ리λΈλ Docker κΈ°λ° νλ‘λμ
νκ²½ + GitHub Actions μλ λ°°ν¬ νμ΄νλΌμΈμ ꡬμΆνμ¬
κ°λ° β λΉλ β ν
μ€νΈ β λ°°ν¬μ μ κ³Όμ μ μλννμ΅λλ€.
-
GitHub Actions(CI)
- PR/commit μ μλμΌλ‘ ν μ€νΈ μ€ν(Jest)
- νμ 체ν¬/ESLint κ²μ¬ ν΅ν΄ νμ§ μ μ§
- λͺ¨λ ν μ€νΈ ν΅κ³Ό μ Docker μ΄λ―Έμ§ λΉλ
-
GHCR(GitHub Container Registry)
- λΉλλ Docker μ΄λ―Έμ§λ₯Ό νκ·Έ λ²μ μΌλ‘ μ λ‘λ
- μ΄λ―Έμ§ λ²μ κ΄λ¦¬ λ° λ‘€λ°±μ΄ μ©μ΄
-
GitHub Actions(CD) β EC2 μλ λ°°ν¬
- SSHλ₯Ό ν΅ν΄ EC2μ μ μ
- μ΅μ μ΄λ―Έμ§ Pull
docker-compose.prod.yamlκΈ°λ°μΌλ‘ 컨ν μ΄λ μ¬κΈ°λ- νκ²½ λ³μ νμΌ(.env.prod) μλ μμ±/λκΈ°ν
-
EC2(Docker Runtime)
- Backend 컨ν μ΄λ λ¨μΌ μ€ν
- λ‘κ·Έ κ΄λ¦¬(Pino νμΌ μ μ₯), ν¬μ€μ²΄ν¬ ν¬ν¨
-
Nginx Reverse Proxy
- μμ² λΌμ°ν
:
FE β / (Next.js)/BE β /api - Cloudflare Origin 볡μ, gzip μμΆ, CORS/보μ ν€λ μΌκ΄ κ΄λ¦¬
- SSL(TLS) μ’ λ¨ μ²λ¦¬
- μμ² λΌμ°ν
:
flowchart LR
Dev[Developer Push] --> GA[GitHub Actions]
GA --> Test[Tests & Lint]
GA --> Build[Docker Build]
Build --> GHCR[Push to GHCR]
GHCR --> Deploy[GA Deploy Script]
Deploy --> EC2[(EC2)]
EC2 --> DC[Docker Compose Up]
DC --> Nginx[Nginx Reverse Proxy]
Nginx --> Backend[Backend Container]
- μμ μλ λ°°ν¬: Git pushλ§μΌλ‘ ν μ€νΈ β λΉλ β λ°°ν¬κΉμ§ μλ μ²λ¦¬
- νκ²½ λΆλ¦¬:
.env,.env.test,.env.productionλΆλ¦¬ λ‘λ - μ νμ μ¬λ°°ν¬: API λ³κ²½ μμ΄ νΉμ λͺ¨λλ§ μ¬λ°°ν¬ κ°λ₯
- μλ¬ λ°©μ§ μ₯μΉ: Swagger λ―Έμμ± μ μλ² κΈ°λ μ°¨λ¨, νλ‘μ/ν¬νΈ μλ κ²μ¦
- μ΄μ μμ μ± κ°ν: Docker μ΄λ―Έμ§ κΈ°λ°μΌλ‘ λμΌν μ€ν νκ²½ 보μ₯
νλ‘μ νΈλ Layered Architecture + λλ©μΈ λͺ¨λ κ΅¬μ‘°λ‘ κ΅¬μ±λμ΄ μμΌλ©°,
ν΅μ¬ μ±
μ λ¨μκ° λͺ
ννκ² λΆλ¦¬λμ΄ μ μ§λ³΄μμ±κ³Ό νμ₯μ±μ λμμ΅λλ€.
welive/
βββ infra/ # Docker, nginx, λ°°ν¬ μ€μ
β βββ docker/
β βββ nginx/
β
βββ prisma/ # Prisma μ€ν€λ§ λ° λ§μ΄κ·Έλ μ΄μ
β βββ schema.prisma
β βββ migrations/
β
βββ src/
β βββ core/ # μ± μ½μ΄ (envΒ·loggerΒ·middlewaresΒ·sseΒ·socket λ±)
β βββ modules/ # λλ©μΈ λͺ¨λ (auth, residents, polls, notices λ±)
β βββ jobs/ # Poll Scheduler
β βββ @types/ # κΈλ‘λ² νμ
νμ₯
β βββ index.ts # μλ² μνΈλ¦¬ν¬μΈνΈ
β
βββ tests/ # Core / Modules / Scheduler ν΅ν© ν
μ€νΈ
β βββ core/
β βββ modules/
β βββ jobs/
β
βββ swagger/ # μλ μμ±λ Swagger JSON
βββ scripts/ # Swagger μμ± λ± μ€ν¬λ¦½νΈ
βββ package.json
- core: μΈμ¦/CORS/Sanitize/Error νΈλ€λ¬, SSE, Socket, Limiter λ± κ³΅ν΅ μμ€ν λ μ΄μ΄
- modules: Router β Controller β Service β Repository β Prisma ꡬ쑰λ₯Ό λ°λ₯΄λ λλ©μΈ λ¨μ ꡬν
- jobs: Poll μλ μ€ν/λ§λ£ μ€μΌμ€λ¬
- tests: μ€μ DB κΈ°λ° ν΅ν© ν μ€νΈ (ν μ€νΈμ© DB μλ μ΄κΈ°ν)
.env.example νμΌμ μ°Έκ³ ν΄ κ°λ°/ν
μ€νΈ/μ΄μ νκ²½μ λ§κ² .env, .env.test, .env.productionμ ꡬμ±ν©λλ€.
# DATABASE
DATABASE_URL=postgresql://<user>:<password>@localhost:5432/welive
# SERVER
PORT=3001
FE_PORT=3000
CORS_ORIGIN=http://localhost:3000
# JWT / AUTH
ACCESS_TOKEN_SECRET=your_access_token_secret
REFRESH_TOKEN_SECRET=your_refresh_token_secret
PASSWORD_PEPPER=your_pepper
# AWS S3
AWS_REGION=ap-northeast-2
AWS_S3_BUCKET_NAME=your_bucket
AWS_S3_BASE_URL=https://your_bucket.s3.ap-northeast-2.amazonaws.comnpm installκ°λ°/ν μ€νΈ νκ²½ λͺ¨λ Prisma μ€ν€λ§ κΈ°λ°μΌλ‘ μ΄κΈ°νν©λλ€.
npm run prisma:migrate # λ§μ΄κ·Έλ μ΄μ
+ μλ μ 체 μν
npm run prisma:generate # Prisma Client μμ±
npm run prisma:reset # κ°λ° DB μ΄κΈ°ν
npm run prisma:studio # DB UI (μΉ)ν μ€νΈ νκ²½μμ DB μ΄κΈ°ν:
npm run test:reset
npm run test:migrateλ°±μλ/νλ‘ νΈλ₯Ό λμμ κ°λ°νκΈ° μν μ€ν¬λ¦½νΈ ν¬ν¨.
npm run dev # BE + FE λμ μ€ν
npm run dev:be # λ°±μλ λ¨λ
μ€ν
npm run dev:fe # νλ‘ νΈ λ¨λ
μ€νν
μ€νΈ νκ²½(.env.test)μ΄ μλ λ‘λλλ©°,
ν
μ€νΈμ© DBλ λ§€ μ€νλ§λ€ μ΄κΈ°νλ©λλ€.
npm test # μ 체 ν
μ€νΈ
npm run test:watch # λ³κ²½ κ°μ§
npm run test:cov # 컀λ²λ¦¬μ§npm run buildλΉλ ν κ²°κ³Όλ dist/μ μμ±λλ©° tsc-aliasλ‘ κ²½λ‘ aliasκ° μλ λ³νλ©λλ€.
- λ°±μλ: Docker κΈ°λ° (EC2)
- νλ‘ νΈμλ: PM2 κΈ°λ° Next.js μ€ν
npm start # BE + FE
npm run start:be # λ°±μλλ§
npm run start:fe # νλ‘ νΈλ§μ€μ§:
npm run stopν
μ€νΈ/μ΄μ DB νΌμ©μ λ°©μ§νκΈ° μν΄
.env.testμλ μ΄μ DB μ 보λ₯Ό μ λ μ μ₯νμ§ μλλ‘ μ€κ³λμ΄ μμ΅λλ€.
- JWT Access/Refresh Token + DB κΈ°λ° Refresh μν κ΄λ¦¬
- SUPER_ADMIN / ADMIN / USER 3λ¨ μν μ λΌμ°ν° λ¨μλ‘ μ μ΄
- μΉμΈ λκΈ°/κ±°μ /μΆκ° μ 보 νμ λ± μ μ£Όλ―Ό μν νλ¦μ μμΈνκ² λͺ¨λΈλ§
- λ―Όμ·곡μ§Β·ν¬ν λ± μ£Όμ μ΄λ²€νΈ λ°μ μ μ¦μ μλ¦Ό μ μ‘
- SSE μ μ Emitter λͺ¨λν β μλΉμ€ λ μ΄μ΄μμ λ¨μΌ APIλ‘ μ¬μ©
- νλ‘ νΈλ μλ¦Ό Storeμ μ°κ²°λμ΄ μ€μκ° UI λ°μ
- Cron κΈ°λ° μλ μ€μΌμ€λ¬ ꡬν
- Limiter(μ§μ ꡬν) λ‘ λ¨μΌ μ€ν 보μ₯ β μ€λ³΅ μ²λ¦¬/κ²½μ 쑰건 μ κ±°
- λ§λ£ μμ μ μλ μ§κ³ + μλ¦ΌκΉμ§ μΌκ΄ μ²λ¦¬
- νμ₯μ κΈ°λ°μ΄ μλ Magic Number(MIME) κ²μ¦
sharpμ¬μΈμ½λ©μΌλ‘ ν΄λ¦¬κΈλ‘―(polyglot) νμΌ κ³΅κ²© μ°¨λ¨- Presigned URL λ°©μμΌλ‘ S3 μ§μ μ λ‘λ
- μλ²λ¨ DOMPurify + jsdomμΌλ‘ XSS λ°©μ§
- Zod μ€ν€λ§ κΈ°λ°μΌλ‘ Params/Query/Body ν΅ν© κ²μ¦
- κ²μ¦(Zod) β Sanitize β Controller νλ¦μΌλ‘ μ κ³μΈ΅ 보νΈ
- JWT μΈμ¦ λ―Έλ€μ¨μ΄λ‘ μ¬μ©μ κ²μ¦
- κ΄λ¦¬μ β μ μ£Όλ―Ό κ° λΉ λ₯Έ μλ ꡬ쑰 ν립
- λ©μμ§ μ μ₯, μ½μ μ²λ¦¬, μ±ν λ°© λ¨μ κ΄λ¦¬
- CSV ν€λ/νμ μ ν κ²μ¦
- λλ μ λ‘λ μ μ 체λ₯Ό λ¨μΌ νΈλμμ μΌλ‘ μ²λ¦¬
- μλͺ»λ λ°μ΄ν°λ line λ¨μλ‘ μ¦μ μ€λ₯ μ 곡
- μ 체 λλ©μΈ λͺ¨λ ν΅ν© ν μ€νΈ κ΅¬μ± (Jest + Supertest)
- ν
μ€νΈ νκ²½ μ μ© DB(
setupTestDB) μλ μ΄κΈ°ν - SchedulerΒ·SSE μ΄λ²€νΈκΉμ§ λ 립μ μΌλ‘ κ²μ¦ κ°λ₯νλλ‘ κ΅¬μ‘°ν
- Swagger λ―Έμμ± μ μλ² λΆν μ°¨λ¨
- healthcheck κΈ°λ° Docker 컨ν μ΄λ μν νμΈ
- Nginx + Cloudflare Origin Restore + gzip μμΆ μ‘°ν©
λ³Έ νλ‘μ νΈλ Layered Architecture, λλ©μΈ κΈ°λ° μ€κ³, ν μ€νΈΒ·λ³΄μΒ·λ°°ν¬ μλνλ₯Ό λͺ¨λ κ°μΆ
μ€μ μ΄μ μμ€μ λ°±μλ μν€ν μ²λ₯Ό λͺ©νλ‘ κ΅¬νλμμ΅λλ€.λν λͺ¨λ μ± μ λΆλ¦¬, μ€νλ¦°νΈ λ¨μ μΌμ κ΄λ¦¬, GitHub PR κΈ°λ° νμ μΌλ‘
μ€μ ν κ°λ° νλ¦μ λ§μΆ μ΄μ νλ‘μΈμ€λ₯Ό μ μ§νμ΅λλ€.
μλ λ¬Έμλ νλ‘μ νΈμ μ κ³Όμ μ μ 리ν κ΅¬κΈ λ μ€ κΈ°λ° κ³΅μ μ°μΆλ¬Όμ λλ€.
-
1. μꡬμ¬ν λΆμ
https://docs.google.com/document/d/11Kl92rqan2iA_5K8Q_3UCQXxkDiuzfVb10B0d-X-JTQ/edit?usp=sharing -
2. μμ€ν /DB μ€κ³
https://docs.google.com/document/d/1cymp9U-yK94KXXUE7jiBzh5EP23w9luxXiKN50FrFGA/edit?usp=sharing -
3. ꡬν λ¬Έμ
https://docs.google.com/document/d/1hgKMj29_KXsSXFceEgQGOPSlGxUAqYg32ujF5OWZd2U/edit?usp=sharing -
4. ν μ€νΈ λ¬Έμ
https://docs.google.com/document/d/1Dnkksd0Oa1b5dHiuudu5tm-BDBxiOFkTnExfLzwxJVw/edit?usp=sharing -
5. λ°°ν¬ λ° μ μ§λ³΄μ λ¬Έμ
https://docs.google.com/document/d/1Pq4H3DNUBoLCKIp0Jdt3oD8kg3pb66YLEaR4uXr8KJg/edit?usp=sharing
Note
μλ¨μ βπ λ°λͺ¨ & μ£Όμ λ§ν¬βλ μ€ν κ°λ₯ν μλΉμ€/μ½λ μ κ·Όμ©,
λ³Έ μΉμ
μ νλ‘μ νΈ μ§ν μ 체λ₯Ό λ€λ£¬ μ μ λ¬Έμ μ°μΆλ¬Ό λͺ¨μμ
λλ€.

