안녕하세요. makeviibe입니다.
이번 글에서는 저희가 한 프로젝트에서 구현한 방 기반 실시간 채팅 기능에 대해 소개드리고자 합니다.
단순한 메시지 전송을 넘어서, 사용자가 특정 채팅방에 참여하고, 이전 대화 히스토리를 받아보고, 나가면 방 상태가 정리되는 흐름을 구현하는 것이 주요 목표였습니다.
기술적으로는 NestJS + Socket.IO + React를 조합하여 구축하였고, 그 과정에서 겪었던 다양한 이슈와 해결 방안을 공유드립니다.
🧱 채팅 시스템의 기본 구조
- 사용자는 특정 유저의 ID 또는 방 ID를 기반으로 joinRoom 요청을 서버에 전송합니다.
- 서버는 해당 요청을 받아 사용자를 방에 참여시키고, 기존 메시지 히스토리를 클라이언트에 전달합니다.
- 이후에는 message 이벤트를 통해 실시간 메시지를 주고받게 됩니다.
- 사용자가 채팅방을 나가거나 React 컴포넌트가 언마운트될 경우, 클라이언트는 leaveRoom을 통해 방에서 나가고, 서버는 해당 방의 사용자 목록에서 클라이언트를 제거합니다.
React에서는 useEffect를 활용해 소켓을 연결하고 해제하며, 서버에서는 NestJS의 @WebSocketGateway를 기반으로 각 방을 관리했습니다.
😵 겪었던 문제들과 해결 방법
1. 메시지 중복 수신 문제
초기 구현 당시, useEffect 내에서 socket.on('message') 리스너를 매번 새로 등록하다 보니, 일부 상황에서는 같은 메시지를 여러 번 수신하는 문제가 발생했습니다.
✅ 해결 방법
- 리스너를 등록하기 전 socket.off('message')를 통해 기존 리스너 제거
- useEffect의 의존성 배열을 설정하여, roomUser.id나 user.id 변경 시에만 재등록되도록 제한

2. React에서 소켓 연결 해제 타이밍
컴포넌트가 언마운트되거나 사용자가 방을 떠나더라도, 서버는 단순한 disconnect()만으로는 어떤 방에서 사용자가 나갔는지 알 수 없었습니다.
이로 인해 서버는 해당 사용자가 여전히 방에 남아 있는 것으로 인식하는 문제가 발생했습니다.
✅ 해결 방법
- leaveRoom이라는 커스텀 이벤트를 만들어, 방 ID와 함께 명시적으로 방을 나가는 동작을 수행하도록 했습니다.
- 클라이언트가 나가기 전 socket.emit('leaveRoom', { roomId })를 호출하고, 이후에 연결을 종료합니다.

- 서버는 이를 수신하여, 해당 방에서 클라이언트를 제거하고, 방에 사용자가 더 이상 없다면 방 정보도 삭제합니다.

3. 채팅방 참여 시 히스토리 불러오기
유저가 채팅방에 입장했을 때, 기존 대화 내용(히스토리)을 불러오는 기능도 중요한 요구사항 중 하나였습니다.
처음에는 서버 메모리에 메시지를 임시 저장하는 방법을 고려했으나, 안정성과 데이터 영속성을 위해 DB에 저장하고 불러오는 방식으로 전환했습니다.
✅ 구현 방식
- joinRoom 요청 시, userId와 roomId를 함께 서버에 전송
- 서버는 해당 유저의 방 참여 기록을 확인하고, 필요한 경우 추가
- 그 이후의 메시지를 불러와 chatHistory 이벤트로 클라이언트에 전송
이렇게 하면 새로운 유저도 자연스럽게 대화 흐름을 이해하고 참여할 수 있습니다.
💡 마무리하며
이번 실시간 채팅 시스템을 구현하면서, 단순한 WebSocket 연결이 아닌
- 클라이언트 생명주기와의 연동,
- 서버 내 방 상태 관리,
- 메시지 리스너 중복 방지,
- 채팅 히스토리 관리 등 다양한 요소를 고려하게 되었습니다.
Socket.IO는 강력하고 유연한 툴이지만,
모든 것을 자동으로 처리해주는 도구는 아닙니다.
클라이언트와 서버 양쪽에서 상태 관리와 책임을 명확히 정의해야,
유지보수성과 확장성을 모두 갖춘 시스템을 만들 수 있다는 것을 다시금 실감했습니다.
이 글이 실시간 채팅을 구현하고자 하는 분들께 실질적인 도움이 되기를 바랍니다.
궁금하신 점이나 의견은 언제든 환영합니다!
'개발일지' 카테고리의 다른 글
| “서버가 다 해줄게요”는 위험할 수 있다 – 리소스 전달 방식에 대한 고민 (1) | 2025.07.15 |
|---|---|
| 실시간 채팅 시스템, 왜 서버가 필요할까? – 구조와 아키텍처 설계까지 (2) | 2025.07.15 |
| SNS 로그인 구현, 어디까지 해봤니? (2) | 2025.07.15 |
| “CodePipeline으로 시작하는 AWS 배포 자동화: 무중단 배포를 위한 첫걸음” (0) | 2025.07.15 |
| “GitHub Actions로 만드는 깔끔한 배포 자동화: 코드 푸시만 하면 끝!” (8) | 2025.07.15 |