실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
1. 프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
실시간 채팅 앱 만들기 with Socket.io는 웹소켓 기반의 양방향 통신을 학습하고 실제 채팅 애플리케이션을 구현하는 프로젝트입니다. Socket.io는 실시간 이벤트 기반 통신을 간편하게 구현할 수 있는 라이브러리로, 채팅, 알림, 실시간 대시보드 등 다양한 분야에 활용됩니다.
이 프로젝트를 통해 클라이언트-서버 간 실시간 통신의 원리를 이해하고, 메시지 송수신, 사용자 관리, 방(Room) 개념 등 실무에서 필요한 핵심 기능들을 직접 구현해볼 수 있습니다. 완성된 채팅 앱은 포트폴리오로도 활용 가능하며, 실시간 웹 애플리케이션 개발의 기초를 탄탄히 다질 수 있습니다.
2. 필요한 기술 스택
이 프로젝트를 진행하기 위해 다음과 같은 기술 스택이 필요합니다:
- 백엔드: Node.js, Express.js, Socket.io
- 프론트엔드: HTML5, CSS3, JavaScript (Vanilla JS 또는 React)
- 개발 도구: npm 또는 yarn, 코드 에디터(VS Code 권장)
- 선택 사항: TypeScript, MongoDB(채팅 기록 저장), Redis(확장성)
Node.js와 npm이 설치되어 있어야 하며, 기본적인 JavaScript 문법과 비동기 프로그래밍에 대한 이해가 있으면 더욱 수월하게 진행할 수 있습니다.
3. 프로젝트 셋업
먼저 프로젝트 디렉토리를 생성하고 필요한 패키지를 설치합니다:
# 프로젝트 디렉토리 생성
mkdir realtime-chat-app
cd realtime-chat-app
# package.json 초기화
npm init -y
# 필요한 패키지 설치
npm install express socket.io
npm install --save-dev nodemon
package.json 파일에 개발 스크립트를 추가합니다:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
프로젝트 구조는 다음과 같이 구성합니다:
realtime-chat-app/
├── server.js # 서버 진입점
├── public/ # 정적 파일
│ ├── index.html
│ ├── style.css
│ └── client.js
└── package.json
4. 단계별 구현 과정
4.1 서버 구축 (server.js)
Socket.io를 사용한 실시간 채팅 앱 만들기 with Socket.io의 핵심은 서버 설정입니다. Express와 Socket.io를 통합하여 기본 서버를 구축합니다:
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
const path = require('path');
// 정적 파일 제공
app.use(express.static(path.join(__dirname, 'public')));
// 연결된 사용자 관리
const users = new Map();
// Socket.io 연결 처리
io.on('connection', (socket) => {
console.log('새로운 사용자 접속:', socket.id);
// 사용자 입장
socket.on('join', (username) => {
users.set(socket.id, username);
socket.broadcast.emit('user-connected', username);
// 현재 접속자 목록 전송
io.emit('update-users', Array.from(users.values()));
});
// 메시지 수신 및 브로드캐스트
socket.on('chat-message', (data) => {
const message = {
username: users.get(socket.id),
message: data.message,
timestamp: new Date().toLocaleTimeString('ko-KR')
};
io.emit('chat-message', message);
});
// 타이핑 중 표시
socket.on('typing', () => {
socket.broadcast.emit('typing', users.get(socket.id));
});
socket.on('stop-typing', () => {
socket.broadcast.emit('stop-typing');
});
// 연결 해제
socket.on('disconnect', () => {
const username = users.get(socket.id);
users.delete(socket.id);
if (username) {
io.emit('user-disconnected', username);
io.emit('update-users', Array.from(users.values()));
}
console.log('사용자 접속 해제:', socket.id);
});
});
const PORT = process.env.PORT || 3000;
http.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`);
});
4.2 클라이언트 HTML 구조 (public/index.html)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>실시간 채팅 앱</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="login-screen" id="loginScreen">
<h1>실시간 채팅 앱</h1>
<input type="text" id="usernameInput" placeholder="사용자 이름을 입력하세요" maxlength="20">
<button id="joinBtn">입장하기</button>
</div>
<div class="chat-screen" id="chatScreen" style="display: none;">
<div class="sidebar">
<h2>접속자 목록</h2>
<ul id="usersList"></ul>
</div>
<div class="chat-container">
<div class="chat-header">
<h2>채팅방</h2>
<span id="currentUser"></span>
</div>
<div class="messages" id="messages"></div>
<div class="typing-indicator" id="typingIndicator"></div>
<div class="message-input">
<input type="text" id="messageInput" placeholder="메시지를 입력하세요..." autocomplete="off">
<button id="sendBtn">전송</button>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
4.3 클라이언트 로직 (public/client.js)
const socket = io();
const loginScreen = document.getElementById('loginScreen');
const chatScreen = document.getElementById('chatScreen');
const usernameInput = document.getElementById('usernameInput');
const joinBtn = document.getElementById('joinBtn');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const messages = document.getElementById('messages');
const usersList = document.getElementById('usersList');
const currentUser = document.getElementById('currentUser');
const typingIndicator = document.getElementById('typingIndicator');
let username = '';
let typingTimer;
// 로그인 처리
joinBtn.addEventListener('click', () => {
username = usernameInput.value.trim();
if (username) {
socket.emit('join', username);
loginScreen.style.display = 'none';
chatScreen.style.display = 'flex';
currentUser.textContent = `환영합니다, ${username}님!`;
messageInput.focus();
}
});
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') joinBtn.click();
});
// 메시지 전송
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('chat-message', { message });
messageInput.value = '';
socket.emit('stop-typing');
}
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
// 타이핑 감지
messageInput.addEventListener('input', () => {
socket.emit('typing');
clearTimeout(typingTimer);
typingTimer = setTimeout(() => {
socket.emit('stop-typing');
}, 1000);
});
// 메시지 수신
socket.on('chat-message', (data) => {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
if (data.username === username) {
messageDiv.classList.add('own-message');
}
messageDiv.innerHTML = `
`;
messages.appendChild(messageDiv);
messages.scrollTop = messages.scrollHeight;
});
// 사용자 연결/해제 알림
socket.on('user-connected', (username) => {
addSystemMessage(`${username}님이 입장했습니다.`);
});
socket.on('user-disconnected', (username) => {
addSystemMessage(`${username}님이 퇴장했습니다.`);
});
// 접속자 목록 업데이트
socket.on('update-users', (users) => {
usersList.innerHTML = '';
users.forEach(user => {
const li = document.createElement('li');
li.textContent = user;
if (user === username) li.classList.add('current-user');
usersList.appendChild(li);
});
});
// 타이핑 표시
socket.on('typing', (username) => {
typingIndicator.textContent = `${username}님이 입력 중...`;
});
socket.on('stop-typing', () => {
typingIndicator.textContent = '';
});
// 시스템 메시지 추가
function addSystemMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', 'system-message');
messageDiv.textContent = message;
messages.appendChild(messageDiv);
messages.scrollTop = messages.scrollHeight;
}
// XSS 방지
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
4.4 스타일링 (public/style.css)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 800px;
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
overflow: hidden;
}
.login-screen {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
gap: 20px;
padding: 40px;
}
.login-screen h1 {
color: #667eea;
font-size: 2.5em;
}
.login-screen input {
width: 300px;
padding: 15px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
.login-screen button {
width: 300px;
padding: 15px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
.login-screen button:hover {
background: #5568d3;
}
.chat-screen {
display: flex;
height: 100%;
}
.sidebar {
width: 250px;
background: #f7f7f7;
border-right: 1px solid #ddd;
padding: 20px;
}
.sidebar h2 {
font-size: 1.2em;
margin-bottom: 15px;
color: #333;
}
.sidebar ul {
list-style: none;
}
.sidebar li {
padding: 10px;
margin: 5px 0;
background: white;
border-radius: 5px;
font-size: 14px;
}
.sidebar li.current-user {
background: #667eea;
color: white;
font-weight: bold;
}
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-header {
padding: 20px;
background: #667eea;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #fafafa;
}
.message {
margin: 10px 0;
padding: 12px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.message.own-message {
background: #e3f2fd;
margin-left: auto;
max-width: 70%;
}
.message.system-message {
background: #fff3cd;
text-align: center;
font-style: italic;
color: #856404;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 0.9em;
}
.timestamp {
color: #999;
font-size: 0.85em;
}
.message-body {
word-wrap: break-word;
}
.typing-indicator {
padding: 10px 20px;
color: #667eea;
font-style: italic;
min-height: 30px;
}
.message-input {
display: flex;
padding: 20px;
background: white;
border-top: 1px solid #ddd;
}
.message-input input {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.message-input button {
margin-left: 10px;
padding: 12px 30px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
.message-input button:hover {
background: #5568d3;
}
5. 테스트 및 배포
5.1 로컬 테스트
개발 서버를 실행하여 로컬에서 테스트합니다:
npm run dev
브라우저에서 http://localhost:3000에 접속하여 여러 탭을 열어 실시간 채팅을 테스트합니다. 메시지 송수신, 사용자 입장/퇴장 알림, 타이핑 표시 등 모든 기능이 정상 작동하는지 확인합니다.
5.2 배포 (Heroku 예시)
# Heroku CLI 설치 후
heroku create my-chat-app
git init
git add .
git commit -m "Initial commit"
git push heroku main
또는 Vercel, Railway, Render 등 다양한 플랫폼에 배포할 수 있습니다. 배포 시 환경 변수 설정과 웹소켓 연결을 지원하는 플랫폼인지 확인해야 합니다.
5.3 추가 테스트 항목
- 다중 사용자 동시 접속 테스트
- 네트워크 끊김 시 재연결 동작 확인
- 긴 메시지, 특수문자, 이모지 처리 확인
- 모바일 브라우저 호환성 테스트
6. 마무리 및 확장 아이디어
실시간 채팅 앱 만들기 with Socket.io 프로젝트를 통해 웹소켓 통신의 핵심 개념과 실시간 애플리케이션 개발 방법을 학습했습니다. 이 기본 구조를 바탕으로 다음과 같은 기능들을 추가하여 더욱 완성도 높은 애플리케이션으로 발전시킬 수 있습니다:
- 다중 채팅방: Room 기능을 활용한 여러 채팅방 생성
- 파일 전송: 이미지, 문서 등 파일 공유 기능
- 메시지 저장: MongoDB를 사용한 채팅 기록 영구 저장
- 사용자 인증: JWT를 활용한 로그인 시스템
- 이모지 및 멘션: 이모지 피커, @멘션 기능
- 읽음 표시: 메시지 읽음/안읽음 상태 표시
- 음성/영상 통화: WebRTC를 활용한 화상 채팅
- 알림 시스템: 브라우저 푸시 알림
이 프로젝트는 포트폴리오로 활용하기에 적합하며, 실시간 통신이 필요한 다양한 애플리케이션의 기반 기술로 응용할 수 있습니다. 꾸준히 기능을 추가하고 개선하며 실력을 향상시켜 보세요!
📚 함께 읽으면 좋은 글
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 18.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 9.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 1.
🎯 실시간 채팅 앱 만들기 with Socket.io
JWT 인증 시스템 구현하기 – 완성까지 한번에!
📅 2025. 10. 24.
🎯 JWT 인증 시스템 구현하기
JWT 인증 시스템 구현하기 – 완성까지 한번에!
📅 2025. 10. 23.
🎯 JWT 인증 시스템 구현하기
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
이 글을 읽고 새롭게 알게 된 정보가 있다면 공유해주세요!
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!