프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
실시간 채팅 앱 만들기 with Socket.io는 웹소켓 기술을 활용하여 실시간 양방향 통신을 구현하는 프로젝트입니다. 이 가이드를 통해 사용자 간 즉각적인 메시지 교환이 가능한 채팅 애플리케이션을 처음부터 끝까지 직접 구축해볼 수 있습니다. Socket.io는 WebSocket을 기반으로 하며, 연결이 끊어졌을 때 자동 재연결, 폴백 메커니즘 등 다양한 기능을 제공합니다. 본 프로젝트를 완성하면 실시간 통신의 원리를 이해하고, Node.js 백엔드와 프론트엔드 통합 능력을 향상시킬 수 있습니다. 또한 포트폴리오에 추가할 수 있는 실용적인 웹 애플리케이션을 완성하게 됩니다.
필요한 기술 스택
이 실시간 채팅 앱 만들기 with Socket.io 프로젝트를 구현하기 위해서는 다음의 기술 스택이 필요합니다:
- Node.js: 서버 사이드 JavaScript 런타임 환경
- Express.js: Node.js 웹 애플리케이션 프레임워크
- Socket.io: 실시간 양방향 통신 라이브러리
- HTML/CSS/JavaScript: 프론트엔드 기본 기술
- npm: 패키지 관리 도구
추가적으로 MongoDB나 Redis를 활용하면 메시지 저장 및 세션 관리 기능을 확장할 수 있습니다.
프로젝트 셋업
먼저 프로젝트 디렉토리를 생성하고 필요한 패키지를 설치합니다.
# 프로젝트 디렉토리 생성
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 섹션에 개발 서버 실행 명령어를 추가합니다:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
이제 기본 프로젝트 구조가 준비되었습니다. 다음 단계에서 실제 서버와 클라이언트 코드를 작성하겠습니다.
단계별 구현 과정
1단계: Express 서버 설정
프로젝트 루트에 server.js 파일을 생성하고 기본 Express 서버를 설정합니다:
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require('socket.io');
const io = new Server(server);
const PORT = process.env.PORT || 3000;
// 정적 파일 제공
app.use(express.static('public'));
// 기본 라우트
app.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
2단계: Socket.io 이벤트 핸들러 구현
서버에서 클라이언트 연결 및 메시지 처리를 위한 이벤트 핸들러를 추가합니다:
// 온라인 사용자 추적
const users = {};
io.on('connection', (socket) => {
console.log('새로운 사용자 연결:', socket.id);
// 사용자 입장
socket.on('join', (username) => {
users[socket.id] = username;
socket.broadcast.emit('user joined', {
username: username,
message: `${username}님이 입장했습니다.`
});
// 현재 온라인 사용자 목록 전송
io.emit('user list', Object.values(users));
});
// 채팅 메시지 수신 및 브로드캐스트
socket.on('chat message', (data) => {
io.emit('chat message', {
username: users[socket.id],
message: data.message,
timestamp: new Date().toISOString()
});
});
// 타이핑 중 표시
socket.on('typing', () => {
socket.broadcast.emit('typing', {
username: users[socket.id]
});
});
socket.on('stop typing', () => {
socket.broadcast.emit('stop typing');
});
// 사용자 연결 해제
socket.on('disconnect', () => {
const username = users[socket.id];
if (username) {
delete users[socket.id];
io.emit('user left', {
username: username,
message: `${username}님이 퇴장했습니다.`
});
io.emit('user list', Object.values(users));
}
console.log('사용자 연결 해제:', socket.id);
});
});
3단계: 클라이언트 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 id="login-container">
<h1>실시간 채팅방</h1>
<input type="text" id="username-input" placeholder="닉네임을 입력하세요" maxlength="20">
<button id="join-btn">입장하기</button>
</div>
<div id="chat-container" style="display: none;">
<div id="chat-header">
<h2>채팅방</h2>
<div id="online-users"></div>
</div>
<div id="messages"></div>
<div id="typing-indicator"></div>
<div id="input-container">
<input type="text" id="message-input" placeholder="메시지를 입력하세요..." autocomplete="off">
<button id="send-btn">전송</button>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
4단계: 클라이언트 JavaScript 로직 구현
public/client.js 파일을 생성하여 Socket.io 클라이언트 코드를 작성합니다:
const socket = io();
const loginContainer = document.getElementById('login-container');
const chatContainer = document.getElementById('chat-container');
const usernameInput = document.getElementById('username-input');
const joinBtn = document.getElementById('join-btn');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const messagesDiv = document.getElementById('messages');
const typingIndicator = document.getElementById('typing-indicator');
const onlineUsers = document.getElementById('online-users');
let username = '';
let typingTimer;
// 입장 처리
joinBtn.addEventListener('click', () => {
username = usernameInput.value.trim();
if (username) {
socket.emit('join', username);
loginContainer.style.display = 'none';
chatContainer.style.display = 'flex';
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 messageEl = document.createElement('div');
messageEl.className = data.username === username ? 'message own' : 'message';
messageEl.innerHTML = `
<strong>${data.username}</strong>
<p>${data.message}</p>
<span class="timestamp">${new Date(data.timestamp).toLocaleTimeString()}</span>
`;
messagesDiv.appendChild(messageEl);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});
// 사용자 입장/퇴장
socket.on('user joined', (data) => {
addSystemMessage(data.message);
});
socket.on('user left', (data) => {
addSystemMessage(data.message);
});
// 온라인 사용자 목록
socket.on('user list', (users) => {
onlineUsers.innerHTML = `온라인: ${users.length}명 - ${users.join(', ')}`;
});
// 타이핑 표시
socket.on('typing', (data) => {
typingIndicator.textContent = `${data.username}님이 입력 중...`;
});
socket.on('stop typing', () => {
typingIndicator.textContent = '';
});
function addSystemMessage(message) {
const messageEl = document.createElement('div');
messageEl.className = 'message system';
messageEl.textContent = message;
messagesDiv.appendChild(messageEl);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
5단계: CSS 스타일링
public/style.css 파일을 생성하여 UI를 꾸밉니다:
* {
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;
}
#login-container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
text-align: center;
}
#login-container h1 {
margin-bottom: 20px;
color: #333;
}
#login-container input {
width: 100%;
padding: 12px;
margin-bottom: 15px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
#login-container button, #send-btn {
width: 100%;
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
#login-container button:hover, #send-btn:hover {
background: #5568d3;
}
#chat-container {
width: 90%;
max-width: 800px;
height: 90vh;
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
}
#chat-header {
padding: 20px;
background: #667eea;
color: white;
border-radius: 10px 10px 0 0;
}
#online-users {
font-size: 14px;
margin-top: 5px;
opacity: 0.9;
}
#messages {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f5f5f5;
}
.message {
margin-bottom: 15px;
padding: 10px;
background: white;
border-radius: 8px;
max-width: 70%;
}
.message.own {
margin-left: auto;
background: #667eea;
color: white;
text-align: right;
}
.message.system {
text-align: center;
background: #e0e0e0;
font-size: 14px;
max-width: 100%;
}
.timestamp {
font-size: 12px;
opacity: 0.6;
}
#typing-indicator {
padding: 5px 20px;
font-size: 14px;
color: #666;
font-style: italic;
min-height: 25px;
}
#input-container {
display: flex;
padding: 20px;
border-top: 1px solid #ddd;
}
#message-input {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
margin-right: 10px;
}
#send-btn {
width: auto;
padding: 12px 30px;
}
테스트 및 배포
개발 서버를 실행하여 로컬에서 테스트합니다:
npm run dev
브라우저에서 http://localhost:3000에 접속하여 여러 탭을 열어 실시간 채팅이 정상 동작하는지 확인합니다. 배포는 Heroku, Render, 또는 Railway를 추천합니다. Heroku 배포 예시:
# Procfile 생성
echo "web: node server.js" > Procfile
# Git 초기화 및 배포
git init
git add .
git commit -m "Initial commit"
heroku create
git push heroku main
환경 변수 PORT는 자동으로 설정되므로 별도 설정이 필요 없습니다. CORS 설정이 필요한 경우 Socket.io 초기화 시 옵션을 추가합니다.
마무리 및 확장 아이디어
이제 실시간 채팅 앱 만들기 with Socket.io 프로젝트가 완성되었습니다! 기본적인 채팅 기능을 구현했으며, 다음과 같은 기능을 추가하여 확장할 수 있습니다:
- MongoDB를 활용한 채팅 히스토리 저장
- 이미지 및 파일 전송 기능
- 다중 채팅방(Room) 기능
- 사용자 인증 및 프로필 시스템
- 메시지 읽음 표시
- 이모티콘 및 리액션 기능
이 프로젝트를 통해 실시간 통신의 핵심 개념을 익히고 포트폴리오를 강화할 수 있습니다.
📚 함께 읽으면 좋은 글
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 11. 4.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 29.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 27.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 25.
🎯 실시간 채팅 앱 만들기 with Socket.io
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 18.
🎯 실시간 채팅 앱 만들기 with Socket.io
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
실시간 채팅 앱 만들기 with Socket.io 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!