DevChat μ νμ΄μ€λΆ νμμ SNS μ νμ΄μ€λΆ λ©μ μ νμμ μ€μκ° μ±ν
μ ν μλΉμ€λ‘ ν΅ν©ν νμ΅μ© νμ€ν μΉ μ ν리μΌμ΄μ
μ
λλ€. κ²μκΈΒ·λκΈΒ·μ’μμΒ·μΉκ΅¬ κ΄κ³ κ°μ SNS ν΅μ¬ κΈ°λ₯κ³Ό, Socket.io κΈ°λ° μ€μκ° 1:1 / κ·Έλ£Ή μ±ν
, κ·Έλ¦¬κ³ μ¬μ©μ νλμ μΆμ νλ μλ¦Ό μμ€ν
μ νλμ μΌκ΄λ μν€ν
μ² μμ ꡬννμ΅λλ€. JWT + HttpOnly μΏ ν€ κΈ°λ° μΈμ¦, μ΄λ©μΌ μΈμ¦ κ°μ
νλ¦, κ³μΈ΅ λΆλ¦¬(Controller / Service / Repository) λ°±μλ ꡬ쑰, PostgreSQL μ CASCADE μ μ½κ³Ό ENUM νμ
μ μ κ·Ή νμ©ν λ°μ΄ν° λͺ¨λΈλ§μ ν΅ν΄ μ€μ μλΉμ€ μμ€μ μ€κ³ μμΉμ μ μ©νλ κ²μ λͺ©νλ‘ ν©λλ€.
μ΄ 5κ° μμ / 31κ° κΈ°λ₯ μΌλ‘ ꡬμ±λ©λλ€.
Auth β μΈμ¦ / κ³μ κ΄λ¦¬
μ΄λ©μΌ μΈμ¦ μ½λ λ°μ‘ (νμκ°μ
1λ¨κ³, 60μ΄ μΏ¨λ€μ΄)
μ΄λ©μΌ μΈμ¦ μ½λ κ²μ¦ + κ³μ μμ± + μλ λ‘κ·ΈμΈ (νμκ°μ
2λ¨κ³)
λ‘κ·ΈμΈ / λ‘κ·Έμμ
JWT Access(1h) + Refresh(14d) ν ν° μλ μ¬λ°κΈ
λ΄ νλ‘ν μ‘°ν / νΈμ§ (νλ‘ν μ΄λ―Έμ§ μ
λ‘λ ν¬ν¨)
κ³΅κ° νλ‘ν μ‘°ν
νμ νν΄ (CASCADE λ‘ κ΄λ ¨ λ°μ΄ν° μ 리)
μ¬μ©μ κ²μ (handle κΈ°λ°)
μΉκ΅¬ μ μ² / μλ½ / κ±°μ / μ·¨μ / λκΈ°
μΉκ΅¬ λͺ©λ‘ μ‘°ν
λ³΄λΈ / λ°μ μΉκ΅¬ μ μ² λͺ©λ‘
Feed β κ²μκΈ / λκΈ / μ’μμ
κ²μκΈ μμ± / μμ / μμ (μ΅λ 5μ₯ λ―Έλμ΄ μ²¨λΆ)
λ΄μ€νΌλ μ‘°ν (λ³ΈμΈ + accepted μΉκ΅¬μ κ²μκΈ)
νμ νΌλ (λ³ΈμΈ μ μΈ λ¬΄μμ κ²μκΈ)
νλ‘ν νΌλ (νΉμ μ μ μ κ²μκΈ)
λκΈ μμ± / μμ (parent_id λ‘ λλκΈ κ³μΈ΅ μ§μ)
μ’μμ / μ’μμ μ·¨μ (UNIQUE(post_id, user_id) λ‘ μ€λ³΅ λ°©μ§)
Messenger β μ€μκ° μ±ν
1:1 μ±ν
λ°© μμ± (direct_key λ‘ μ€λ³΅ λ°©μ§)
κ·Έλ£Ή μ±ν
λ°© μμ± / μ΄λ¦ λ³κ²½ / λ©€λ² μ΄λ
μ±ν
λ°© λͺ©λ‘ / λ©€λ² λͺ©λ‘ μ‘°ν
μ€μκ° λ©μμ§ μ‘μμ (Socket.io)
λ©μμ§ λ΄μ μ‘°ν (νμ΄μ§λ€μ΄μ
)
μ±ν
μ΄λ―Έμ§ μ
λ‘λ
λ©μμ§ μμ (soft delete)
λ©μμ§ μ½μ νμ μ€μκ° λκΈ°ν
μ±ν
λ°© λκ°κΈ°
μλ¦Ό λͺ©λ‘ μ‘°ν (μΉκ΅¬ μ μ² / μλ½, μ’μμ, λκΈ, λλκΈ, μ±ν
μ΄λ λ± 6μ’
)
κ°λ³ / μ 체 μ½μ μ²λ¦¬
μλ¦Ό μμ
λΆλΆ μΈλ±μ€(WHERE is_read = FALSE) λ‘ λ―Έμ½μ μΉ΄μ΄νΈ μ΅μ ν
νλͺ©
μ¬μ© κΈ°μ
Framework
React 19 (functional + hooks)
Routing
react-router-dom v7
HTTP Client
axios (withCredentials: true)
Real-time
socket.io-client
Styling
CSS Module (*.module.css)
State
useState / useReducer / Context (Redux λ―Έμ¬μ©)
Build
Create React App (react-scripts 5)
νλͺ©
μ¬μ© κΈ°μ
Runtime
Node.js + Express 5
Real-time
Socket.io 4
Auth
JWT (jsonwebtoken) + HttpOnly Cookie
Password
bcrypt
File Upload
multer
Mailer
nodemailer (Gmail SMTP)
Env
dotenv
νλͺ©
μ¬μ© κΈ°μ
RDBMS
PostgreSQL 15+
Driver
pg (node-postgres)
νΉμ§
UUID + BIGSERIAL νΌν© PK, ENUM νμ
3μ’
, ON DELETE CASCADE, λΆλΆ μΈλ±μ€, JSONB
νλͺ©
μ¬μ© κΈ°μ
Dev Server (FE)
react-scripts dev server (port 3000)
Dev Server (BE)
Node.js (port 5000)
CORS
cors + credentials
Static Files
/uploads λλ ν 리 μ μ μλΉ
DevChat λ°±μλλ Single Responsibility λ₯Ό λ°λ₯Έ 4κ³μΈ΅ ꡬ쑰μ΄λ©°, κ° κ³μΈ΅μ λ³ΈμΈ μ±
μλ§ μνν©λλ€.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client (React) β
β pages β components β hooks/contexts β services (axios) β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β HTTPS + Cookie (HttpOnly)
β WebSocket (Socket.io)
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Express App / Socket.io β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Routes (URL β Controller) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Middlewares (authenticate, upload, errorHandler) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Controllers (HTTP I/O, νμ κ²μ¦, res.json) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Services (λΉμ¦λμ€ λ‘μ§ + λΉμ¦λμ€ κ²μ¦) β β
β β μ€ν¨λ boolean μ΄ μλ AppError throw β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Repositories (DB μΏΌλ¦¬λ§ β parameterized) β β
β ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β pg pool
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β PostgreSQL (10 tables, 3 ENUMs) β
β users Β· friendships Β· posts Β· posts_media Β· comments β
β likes Β· chat_rooms Β· messages Β· room_members Β· notificationsβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
κ³μΈ΅
μ±
μ
κΈμ§
Controller
HTTP μ²λ¦¬, νμ κ²μ¦, Service νΈμΆ, μλ΅ μμ±
DB 쿼리
Service
λΉμ¦λμ€ λ‘μ§, λΉμ¦λμ€ κ²μ¦, AppError throw
res / res.json μ¬μ©
Repository
DB 쿼리(parameterized)λ§
λΉμ¦λμ€ λ‘μ§
Middleware
ν‘λ¨ κ΄μ¬μ¬ (μΈμ¦, μ
λ‘λ, μλ¬ λ³ν)
λλ©μΈ λ‘μ§
μλ¬ μ²λ¦¬: throw + globalHandler ν¨ν΄
// Service β μ€ν¨λ throw
throw new ConflictError ( 'HANDLE_TAKEN' , 'νμ¬ μ¬μ©μ€μΈ IDμ
λλ€.' ) ;
// Controller β try/catch β next(err)
try {
const result = await authService . signup ( req . body ) ;
res . status ( 201 ) . json ( { data : result } ) ;
} catch ( err ) { next ( err ) ; }
// errorHandler β AppError.statusCode κΈ°λ° HTTP λ³ν
// β { error: { code, message } }
// μ±κ³΅
{ "data" : { ... } }
// μ€ν¨
{ "error" : { "code" : " HANDLE_TAKEN" , "message" : " νμ¬ μ¬μ©μ€μΈ IDμ
λλ€." } }
projectDevChat/
βββ client/ # React νλ‘ νΈμλ
β βββ public/
β βββ src/
β β βββ api/ # axios κΈ°λ° API νΈμΆ ν¨μ
β β β βββ axios.js # μΈμ€ν΄μ€ + interceptor (μλ μ¬λ°κΈ)
β β β βββ auth.js
β β β βββ friend.js
β β β βββ feed.js
β β β βββ messenger.js
β β β βββ user.js
β β βββ components/
β β β βββ feature/ # λλ©μΈ μ»΄ν¬λνΈ (PostCard, CommentSection λ±)
β β βββ contexts/ # AuthContext, SocketContext, UnreadContext
β β βββ pages/ # λΌμ°νΈ λ¨μ νμ΄μ§
β β β βββ auth/ # Login, Signup, Profile
β β β βββ messenger/ # Messenger, ChatRoom, NewChatModal
β β β βββ Home.jsx
β β β βββ Friends.jsx
β β β βββ UserProfile.jsx
β β βββ App.js
β β βββ index.js
β βββ package.json
β
βββ server/ # Express λ°±μλ
β βββ src/
β β βββ app.js / index.js # μ± λΆνΈμ€νΈλ© (Express + Socket.io)
β β βββ routes/ # URL β Controller λ§€ν
β β β βββ auth.js
β β β βββ friend.js
β β β βββ feed.js Β· posts.js Β· comments.js
β β β βββ messenger.js
β β β βββ users.js
β β β βββ notification.js
β β βββ controllers/ # HTTP μ²λ¦¬ + νμ κ²μ¦
β β βββ services/ # λΉμ¦λμ€ λ‘μ§
β β β βββ auth.js Β· mailer.js Β· storage.js
β β β βββ feed.js Β· friend.js
β β β βββ messenger.js Β· notification.js
β β βββ repositories/ # DB 쿼리
β β β βββ user.js Β· emailVerification.js
β β β βββ friendship.js Β· feed.js
β β β βββ messenger.js Β· notification.js
β β βββ middlewares/ # authenticate, upload, errorHandler
β β βββ errors/ # AppError ν΄λμ€
β β βββ sockets/ # Socket.io νΈλ€λ¬
β β β βββ index.js # μΈμ¦ λ―Έλ€μ¨μ΄ + λΆνΈμ€νΈλ©
β β β βββ messenger.js # message:send / message:read μ΄λ²€νΈ
β β βββ db/
β β βββ db.js # pg pool
β βββ uploads/ # μ
λ‘λλ νμΌ (μ μ μλΉ)
β βββ .env
β βββ package.json
β
βββ .claude/
β βββ agents/ # Sub-agent μ μ (6κ°)
β βββ document/ # λͺ
μΈ λ¬Έμ (κΈ°λ₯/API/DB)
βββ CLAUDE.md # νλ‘μ νΈ μ»¨λ²€μ
κ°μ΄λ
βββ README.md
Node.js 18 μ΄μ
PostgreSQL 15 μ΄μ
npm (λλ yarn)
Gmail κ³μ β μ΄λ©μΌ μΈμ¦ λ°μ‘μ© (μ± λΉλ°λ²νΈ νμ)
git clone https://github.com/Parkseojin08/DevChat.git
cd DevChat
2. PostgreSQL λ°μ΄ν°λ² μ΄μ€ μ€λΉ
-- psql λλ pgAdmin μμ μ€ν
CREATE DATABASE devchat ;
μ΄ν .claude/document/DBν
μ΄λΈμ 리.md μ μ μλ 10κ° ν
μ΄λΈκ³Ό 3κ° ENUM νμ
μ μμλλ‘ μμ±ν©λλ€.
ENUM: friend_status, room_type_status, notification_type
ν
μ΄λΈ: users β friendships β posts β posts_media β comments β likes β chat_rooms β messages β room_members β notifications
μ΄λ©μΌ μΈμ¦μ©: email_verifications (migration νμΌ: server/src/db/migrations/002_email_verifications.sql)
server/.env νμΌμ μμ±νκ³ μλ λ³μλ₯Ό μ±μλλ€.
# Node.js / Front URL
NODE_ENV = development
NODE_PORT = 5000
FRONT_URL = http://localhost:3000
# PostgreSQL
PG_USER = postgres
PG_HOST = localhost
PG_DATABASE = devchat
PG_PASSWORD = your_password
PG_PORT = 5432
# JWT
JWT_SECRET = replace_with_strong_random_string
JWT_REFRESH_SECRET = replace_with_another_strong_random_string
# SMTP (μ΄λ©μΌ μΈμ¦) β Gmail μ± λΉλ°λ²νΈ μ¬μ©
# Google κ³μ β 보μ β 2λ¨κ³ μΈμ¦ β μ± λΉλ°λ²νΈ μμ±
SMTP_USER = your_gmail@gmail.com
SMTP_PASS = your_app_password
.env λ μ λ 컀λ°νμ§ λ§μΈμ. .gitignore μ ν¬ν¨λμ΄μΌ ν©λλ€.
4. μμ‘΄μ± μ€μΉ & μ€ν
# Backend
cd server
npm install
npm start # http://localhost:5000
# Frontend (λ³λ ν°λ―Έλ)
cd client
npm install
npm start # http://localhost:3000
λΈλΌμ°μ μμ http://localhost:3000 μΌλ‘ μ μν©λλ€.
cd client
npm run build # client/build/ μμ±
/signup νμ΄μ§μμ μμ΄λ(handle), μ΄λ©μΌ, μ΄λ¦, λΉλ°λ²νΈ, μλ
μμΌμ μ
λ ₯ν©λλ€.
μ νμ μΌλ‘ νλ‘ν μ΄λ―Έμ§λ₯Ό 첨λΆν μ μμ΅λλ€.
μΈμ¦ μ½λ λ°μ‘ λ²νΌμ λλ₯΄λ©΄ μ
λ ₯ν μ΄λ©μΌλ‘ 6μ리 μ½λκ° λ°μ‘λ©λλ€.
μ½λλ₯Ό μ
λ ₯ν΄ κ²μ¦νλ©΄ κ³μ μ΄ μμ±λκ³ μλμΌλ‘ λ‘κ·ΈμΈλ©λλ€.
μ½λλ 10λΆκ° μ ν¨ νλ©°, 60μ΄ μΏ¨λ€μ΄ ν μ¬λ°μ‘ κ°λ₯ν©λλ€.
ν νλ©΄ μλ¨ μ
λ ₯λμμ κ²μκΈμ μμ±ν©λλ€. μ΄λ―Έμ§λ μ΅λ 5μ₯ μ²¨λΆ κ°λ₯ν©λλ€.
λ΄μ€νΌλ ν: λ³ΈμΈκ³Ό μΉκ΅¬μ κ²μκΈμ΄ μ΅μ μμΌλ‘ νμλ©λλ€.
νμ ν: μΉκ΅¬κ° μλ λ€λ₯Έ μ¬μ©μμ κ²μκΈμ 무μμλ‘ νμν©λλ€.
κ²μκΈμ λκΈμ λ¬κ±°λ, λκΈμ λλκΈμ λ¬ μ μμ΅λλ€.
ννΈ(μ’μμ) λ²νΌμΌλ‘ λ°μμ λ¨κΈΈ μ μμ΅λλ€.
μλ¨ κ²μ λλ μΉκ΅¬ κ΄λ¦¬ νμ΄μ§μμ handleλ‘ μ¬μ©μλ₯Ό κ²μν©λλ€.
μΉκ΅¬ μ μ²μ 보λ΄λ©΄ μλλ°©μ΄ μλ½/κ±°μ ν μ μμ΅λλ€.
μλ½λ μΉκ΅¬λ λ΄μ€νΌλμ κ²μκΈμ΄ νμλκ³ DMμ λ³΄λΌ μ μμ΅λλ€.
νλ¨ λ€λΉκ²μ΄μ
μ λ©μ μ μμ΄μ½μΌλ‘ μ΄λν©λλ€.
μ±ν
λͺ©λ‘μμ κΈ°μ‘΄ μ±ν
λ°©μ μ ννκ±°λ, + λ²νΌμΌλ‘ μ μ±ν
λ°©μ λ§λλλ€.
1:1 μ±ν
: μΉκ΅¬ λͺ©λ‘μμ μλλ°©μ μ νν©λλ€.
κ·Έλ£Ή μ±ν
: μ¬λ¬ λͺ
μ μ νν΄ λ°©μ λ§λ€κ³ μ΄λ¦μ μ§μ ν©λλ€.
μ΄λ―Έμ§ μ²¨λΆ λ²νΌμΌλ‘ μ¬μ§μ μ μ‘ν μ μμ΅λλ€.
λ΄ λ©μμ§λ₯Ό κΈΈκ² λλ₯΄λ©΄ μμ ν μ μμ΅λλ€.
μ°μΈ‘ νλ¨ νλ‘ν μμ΄μ½ β νλ‘ν νΈμ§ μμ μ΄λ¦, μκ°κΈ, νλ‘ν μ΄λ―Έμ§λ₯Ό λ³κ²½ν©λλ€.
λ€λ₯Έ μ¬μ©μμ μ΄λ¦μ΄λ νλ‘νμ ν΄λ¦νλ©΄ ν΄λΉ μ¬μ©μμ κ³΅κ° νλ‘ν νμ΄μ§λ‘ μ΄λν©λλ€.
μ 체 λͺ
μΈλ .claude/document/API λͺ
μΈμ *.md λ° .claude/document/add_function.md λ₯Ό μ°Έμ‘°νμΈμ. μλλ μ£Όμ μλν¬μΈνΈ μμ½μ
λλ€.
Method
Path
Auth
μ€λͺ
POST
/auth/email/send-code
β
νμκ°μ
1λ¨κ³: μ΄λ©μΌ μΈμ¦ μ½λ λ°μ‘
POST
/auth/email/verify
β
νμκ°μ
2λ¨κ³: μ½λ κ²μ¦ + κ³μ μμ± + μλ λ‘κ·ΈμΈ
POST
/auth/login
β
λ‘κ·ΈμΈ (μΏ ν€ λ°κΈ)
POST
/auth/logout
required
λ‘κ·Έμμ (μΏ ν€ μ κ±° + refresh token 무ν¨ν)
POST
/auth/refresh
refresh cookie
Access token μ¬λ°κΈ (νμ )
GET
/auth/me
required
λ΄ νλ‘ν μ‘°ν
PATCH
/auth/me
required
νλ‘ν νΈμ§ (μ΄λ―Έμ§ ν¬ν¨)
DELETE
/auth/me
required
νμ νν΄
Method
Path
μ€λͺ
GET
/friendships/search?q=handle
μ¬μ©μ κ²μ
GET
/friendships
μΉκ΅¬ λͺ©λ‘ / 보λΈΒ·λ°μ μ μ² λͺ©λ‘
POST
/friendships
μΉκ΅¬ μ μ² (pending μμ±)
PATCH
/friendships/:id
μΉκ΅¬ μλ½ (pending β accepted)
DELETE
/friendships/:id
κ±°μ / μ μ² μ·¨μ / μΉκ΅¬ λκΈ°
Feed β /posts, /comments, /feed
Method
Path
μ€λͺ
GET
/feed
λ΄μ€νΌλ (λ³ΈμΈ + μΉκ΅¬)
GET
/feed/explore
νμ νΌλ (무μμ)
POST
/posts
κ²μκΈ μμ± (μ΅λ 5μ₯ λ―Έλμ΄)
PATCH
/posts/:id
κ²μκΈ μμ
DELETE
/posts/:id
κ²μκΈ μμ
GET
/posts/user/:userId
νΉμ μ μ μ κ²μκΈ
GET
/posts/:postId/comments
λκΈ λͺ©λ‘
POST
/posts/:postId/comments
λκΈ μμ±
DELETE
/comments/:id
λκΈ μμ
POST
/posts/:postId/likes
μ’μμ
DELETE
/posts/:postId/likes
μ’μμ μ·¨μ
Messenger β /chat-rooms, /messages
Method
Path
μ€λͺ
POST
/chat-rooms
μ±ν
λ°© μμ± (direct / group)
GET
/chat-rooms
λ΄ μ±ν
λ°© λͺ©λ‘
GET
/chat-rooms/:id/messages
λ©μμ§ λ΄μ
POST
/chat-rooms/:id/messages/upload
μ±ν
μ΄λ―Έμ§ μ
λ‘λ
PATCH
/chat-rooms/:id
μ±ν
λ°© μ΄λ¦ λ³κ²½ (group λ§)
GET
/chat-rooms/:id/members
λ©€λ² λͺ©λ‘
POST
/chat-rooms/:id/members
λ©€λ² μ΄λ
DELETE
/chat-rooms/:id/members/me
μ±ν
λ°© λκ°κΈ°
DELETE
/messages/:id
λ©μμ§ μμ (soft)
Method
Path
μ€λͺ
GET
/users/:id
κ³΅κ° νλ‘ν μ‘°ν
GET
/notifications
μλ¦Ό λͺ©λ‘
PATCH
/notifications/read-all
μ 체 μ½μ μ²λ¦¬
PATCH
/notifications/:id
κ°λ³ μ½μ μ²λ¦¬
DELETE
/notifications/:id
μλ¦Ό μμ
Socket.io λ μΏ ν€μ accessToken μΌλ‘ μΈμ¦λλ©°, μ°κ²° μ μ¬μ©μκ° μν λͺ¨λ μ±ν
λ°©μ room:{roomId} μ μλ join λ©λλ€.
Event
Payload
μ€λͺ
message:send
{ room_id, content, media_url }
λ©μμ§ μ μ‘ β κ°μ λ°© μ μμκ² message:receive emit
message:read
{ room_id, last_message_id }
μ½μ νμ β κ°μ λ°©μ λ€λ₯Έ λ©€λ²μκ² message:read:update emit
Event
Payload
μ€λͺ
message:receive
{ id, room_id, sender_id, content, media_url, created_at }
μ λ©μμ§ μμ
message:read:update
{ room_id, user_id, last_read_message_id }
λ€λ₯Έ λ©€λ²μ μ½μ μν κ°±μ
message:error
{ code, message }
μ μ‘Β·μ½μ μ²λ¦¬ μ€ν¨
νλͺ©
μ μ©
λΉλ°λ²νΈ
bcrypt ν΄μ μ μ₯. νλ¬ΈΒ·μλ΅ λ
ΈμΆ μ λ κΈμ§
ν ν° μ μ₯μ
localStorage λ―Έμ¬μ© β HttpOnly Cookie (XSS λ°©μ΄)
ν ν° κ΅¬μ‘°
Access(1h) + Refresh(14d), refresh token νμ
CSRF λ°©μ΄
sameSite: lax μΏ ν€ μ΅μ
CORS
FRONT_URL νμ΄νΈλ¦¬μ€νΈ + credentials: true
SQL Injection
μ 쿼리 parameterized ($1, $2 β¦)
User Enumeration
λ―Έκ°μ
μ΄λ©μΌκ³Ό λΉλ°λ²νΈ λΆμΌμΉλ₯Ό λμΌ μλ¬λ‘ μλ΅
μ΄λ©μΌ μΈμ¦
6μ리 μ½λ + 10λΆ λ§λ£ + 5ν μλ μ ν + 60μ΄ μ¬λ°μ‘ μΏ¨λ€μ΄
κΆν κ²μ¦
λͺ¨λ λΉ-κ³΅κ° λΌμ°νΈ authenticate λ―Έλ€μ¨μ΄ ν΅κ³Ό + Service λ¨μμ μμ κΆ/λ©€λ²μ μ¬κ²μ¦
CASCADE
νμ νν΄ μ μμ±ν λͺ¨λ λ°μ΄ν° μλ μ 리
μν¬λ¦Ώ
νλμ½λ© κΈμ§ β .env λ‘ λΆλ¦¬
νμΌ μ
λ‘λ
multer λ‘ νμ
Β·ν¬κΈ° μ ν, uploads/ λλ ν λ¦¬λ§ μ μ μλΉ