Express.js 기반 실시간 소셜 매칭 플랫폼 백엔드
MVP 아키텍처와 Raw SQL 최적화를 통해 생산성과 성능을 동시에 달성한 프로젝트입니다.
Matcha는 실시간 채팅과 사용자 매칭 기능을 제공하는 소셜 플랫폼의 백엔드 서버입니다.
- MVP 아키텍처 도입: Express.js에서 계층별 관심사 분리를 통한 유지보수성 향상
- Raw SQL 최적화: ORM 오버헤드 없이 PostgreSQL의 성능을 최대한 활용
- 실시간 통신: Socket.io를 활용한 즉각적인 알림 및 채팅 기능 (목표: 10초 이내 응답)
- Express.js (^4.19.2) - 웹 프레임워크
- Socket.io (^4.7.5) - 실시간 WebSocket 통신
- Node.js - JavaScript 런타임
- PostgreSQL - 관계형 데이터베이스
- pg (^8.12.0) - PostgreSQL 클라이언트 (Raw SQL)
- jsonwebtoken (^9.0.2) - JWT 토큰 인증
- bcrypt (^5.1.1) - 비밀번호 암호화
- otplib (^12.0.1) - 2단계 인증 (TOTP)
- helmet (^7.1.0) - 보안 헤더 설정
- cors (^2.8.5) - CORS 정책 관리
- nodemailer (^6.9.13) - 이메일 발송
- badwords-ko (^1.0.4) - 한국어 비속어 필터링
- moment-timezone (^0.5.45) - 타임존 처리
- winston (^3.13.0) - 애플리케이션 로깅
- morgan (^1.10.0) - HTTP 요청 로깅
Express.js 환경에서 계층별 관심사를 명확히 분리하여 코드 유지보수성과 테스트 용이성을 확보했습니다.
flowchart TB
Client((Client))
subgraph View ["View Layer - Controllers (9개)"]
AuthController[Auth Controller]
UserController[User Controller]
ProfileController[Profile Controller]
AlarmController[Alarm Controller]
RateController[Rate Controller]
OtherControllers[...]
end
subgraph Presenter ["Presenter Layer - Services (11개)"]
AuthService[Auth Service]
UserService[User Service]
ProfileService[Profile Service]
ChatService[Chat Service]
LikeService[Like Service]
OtherServices[...]
end
subgraph Model ["Model Layer - Repositories (12개)"]
AuthRepo[Auth Repository]
UserRepo[User Repository]
ChatRepo[Chat Repository]
LikeRepo[Like Repository]
BlockRepo[Block Repository]
OtherRepos[...]
end
subgraph Database
DB[("PostgreSQL
Raw SQL")]
end
Client -- "HTTP Request" --> View
View -- "Route Handling
Validation" --> Presenter
Presenter -- "Business Logic
Orchestration" --> Model
Model -- "Parameterized
SQL Queries" --> DB
DB -- "Query Result" --> Model
Model -- "Data Entity" --> Presenter
Presenter -- "Processed Data" --> View
View -- "HTTP Response" --> Client
-
View Layer (Controllers)
- HTTP 요청/응답 처리
- 라우트 정의 및 파라미터 파싱
- JWT 인증 미들웨어 적용
- 입력 유효성 검증
- 에러 핸들링 및 응답 포맷팅
-
Presenter Layer (Services)
- 비즈니스 로직 구현
- 여러 Repository 호출을 조율
- 데이터 변환 및 가공
- 트랜잭션 관리
-
Model Layer (Repositories)
- 데이터베이스 직접 접근
- Raw SQL 쿼리 실행
- 파라미터화된 쿼리로 SQL Injection 방어
- 데이터 CRUD 작업
/matchaBE
├── controllers/ # View Layer - API 엔드포인트 (9개 파일)
├── services/ # Presenter Layer - 비즈니스 로직 (11개 파일)
├── repositories/ # Model Layer - 데이터 접근 (12개 파일)
├── configs/ # 설정, 미들웨어, 검증, 로깅
├── enums/ # 상수 및 열거형 데이터
├── mocks/ # 데이터베이스 시드 데이터
└── main.js # Express 서버 진입점
ORM(Sequelize, TypeORM)을 사용하지 않고 **PostgreSQL Native Client (pg)**를 직접 활용하여 성능과 제어력을 극대화했습니다.
graph TD
subgraph Application ["Application Layer"]
Service[Service Layer]
end
subgraph DataAccess ["Data Access Layer - Raw SQL"]
Repo["Repository
(pg Client)"]
end
subgraph Database
DB[("PostgreSQL")]
end
Service -- "Call with Params" --> Repo
Repo -- "Parameterized Query
($1, $2, ...)" --> DB
DB -- "Result Set" --> Repo
Repo -- "Mapped Entity" --> Service
| 비교 항목 | ORM (Sequelize, TypeORM) | Raw SQL (pg) |
|---|---|---|
| 쿼리 성능 | N+1 문제, 불필요한 JOIN 발생 가능 | 필요한 쿼리만 정확히 실행 |
| 복잡한 쿼리 | 쿼리 빌더로 표현 제약 | 서브쿼리, CTE, Window Function 자유롭게 사용 |
| 타입 안전성 | 런타임 오류 발생 가능 | 쿼리 결과 명시적 검증 |
| 학습 곡선 | ORM API 학습 필요 | SQL 지식 직접 활용 |
| 디버깅 | 생성된 SQL 확인 어려움 | 실행 쿼리 즉시 확인 가능 |
| 오버헤드 | 추상화 레이어 존재 | 없음 (직접 드라이버 사용) |
1. 파라미터화된 쿼리 (SQL Injection 방어)
// repositories/auth.repository.js
const result = await client.query(`SELECT * FROM auth WHERE user_id = $1`, [
id,
]);2. 복잡한 필터링과 조인 최적화
// repositories/user.repository.js
const subQuery = `
SELECT u.id, COUNT(uh.hashtags) AS common_hashtags
FROM users u
JOIN user_hashtags uh ON u.id = uh.user_id
WHERE $1 && uh.hashtags
GROUP BY u.id
`;
let mainQuery = `
SELECT u.*, ur.si, ur.gu, s.common_hashtags
FROM users u
JOIN user_regions ur ON u.id = ur.user_id
JOIN (${subQuery}) s ON u.id = s.id
WHERE ${genderCondition} AND u.deleted_at IS NULL
AND ur.si = $2 AND ur.gu = $3
`;3. RETURNING 절을 활용한 INSERT 최적화
// 한 번의 쿼리로 INSERT + SELECT 수행
const result = await client.query(
`INSERT INTO users (email, username, password, ...)
VALUES ($1, $2, $3, ...)
RETURNING *`,
[email, username, hashedPassword, ...]
);4. Soft Delete 패턴 구현
// 물리적 삭제 대신 deleted_at 타임스탬프 활용
await client.query(
`UPDATE users
SET deleted_at = now(), updated_at = now()
WHERE id = $1`,
[userId]
);
// 복구 가능
await client.query(
`UPDATE user_chat_rooms
SET deleted_at = NULL
WHERE id = $1`,
[roomId]
);- Array Type: 해시태그 저장 및 검색에 PostgreSQL 배열 타입 활용
- Cascade Delete: 외래 키 제약 조건으로 참조 무결성 자동 관리
- Timestamp Functions:
now(),updated_at,created_at자동 관리 - Conditional Expressions: CASE WHEN을 활용한 동적 필터링
// main.js - 서버 시작 시 자동 스키마 생성
const schemaPath = path.join(__dirname, "./configs/schema.sql");
const schema = fs.readFileSync(schemaPath, "utf8");
await client.query(schema);관리 테이블 (12개)
| 테이블명 | 설명 |
|---|---|
users |
사용자 핵심 정보 |
auth |
인증 메타데이터 |
user_block_histories |
차단 기록 |
user_hashtags |
사용자 관심사 태그 |
user_profile_images |
프로필 사진 |
user_ratings |
평점 시스템 |
user_regions |
지역 정보 (시/구) |
user_reports |
신고 기록 |
user_view_histories |
프로필 조회 추적 |
user_like_histories |
좋아요/매칭 기록 |
user_chat_rooms |
채팅방 메타데이터 |
user_chat_histories |
채팅 메시지 |
- JWT 기반 토큰 인증
- OAuth 통합 (Google)
- 2단계 인증 (TOTP)
- 이메일 인증
- 비밀번호 재설정
- 프로필 생성 및 수정
- 프로필 이미지 업로드 (Base64)
- 해시태그 기반 관심사 관리
- 지역 설정 (시/구 단위)
- 성별 및 선호도 설정
- 사용자 좋아요/싫어요
- 차단 시스템
- 실시간 채팅 (Socket.io)
- 프로필 조회 추적
- 사용자 평점 시스템
- 알림 시스템 (방문, 좋아요, 메시지, 매칭)
- 상호 매칭 감지
- 한국어 비속어 필터 (badwords-ko)
- 나이 및 선호도 기반 추천
- 지역 기반 필터링
- Bcrypt 비밀번호 해싱
- SQL Injection 방어 (파라미터화된 쿼리)
- CORS 정책 관리
- Helmet 보안 헤더
- 세션 관리 (30분 만료)