실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
실시간 채팅 앱 만들기 with Socket.io는 웹소켓 기술을 활용하여 실시간 양방향 통신을 구현하는 프로젝트입니다. 이 프로젝트를 통해 사용자들이 즉시 메시지를 주고받을 수 있는 채팅 애플리케이션을 만들어보겠습니다. Socket.io는 실시간 이벤트 기반 통신을 쉽게 구현할 수 있게 해주는 라이브러리로, 웹소켓을 지원하지 않는 브라우저에서도 자동으로 폴링 방식으로 전환하여 안정적인 통신을 보장합니다. 이번 가이드에서는 Node.js와 Express를 사용한 서버 구축부터 클라이언트 인터페이스 개발, 실시간 메시징 기능 구현까지 전 과정을 다룹니다. 완성된 채팅 앱은 여러 사용자가 동시에 접속하여 대화할 수 있으며, 입장/퇴장 알림, 타이핑 인디케이터 등의 기능도 포함됩니다.
필요한 기술 스택
이 프로젝트를 완성하기 위해 다음 기술들이 필요합니다:
- Node.js: 서버 사이드 자바스크립트 런타임 환경
- Express: Node.js 웹 애플리케이션 프레임워크
- Socket.io: 실시간 양방향 통신 라이브러리
- HTML/CSS: 클라이언트 인터페이스 구성
- JavaScript: 클라이언트 로직 구현
추가로 npm(Node Package Manager)을 통해 패키지를 관리하며, 기본적인 HTML, CSS, JavaScript 지식이 있으면 더욱 수월하게 진행할 수 있습니다.
프로젝트 셋업
먼저 프로젝트 디렉토리를 생성하고 npm을 초기화합니다. 터미널에서 다음 명령어를 실행하세요:
mkdir realtime-chat-app
cd realtime-chat-app
npm init -y
필요한 패키지들을 설치합니다:
npm install express socket.io
npm install --save-dev nodemon
nodemon은 코드 변경 시 자동으로 서버를 재시작해주는 개발 도구입니다. package.json 파일을 열어 scripts 섹션을 다음과 같이 수정합니다:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
이제 프로젝트 구조를 만듭니다. 루트 디렉토리에 server.js 파일과 public 폴더를 생성하고, public 폴더 안에 index.html, style.css, client.js 파일을 만듭니다.
단계별 구현 과정
1단계: Express 서버 구축
server.js 파일에 기본 Express 서버와 Socket.io를 설정합니다:
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);
// 정적 파일 제공
app.use(express.static('public'));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
// 사용자 목록 관리
const users = {};
// Socket.io 연결 처리
io.on('connection', (socket) => {
console.log('새 사용자 접속:', socket.id);
// 사용자 입장 처리
socket.on('join', (username) => {
users[socket.id] = username;
socket.broadcast.emit('user-connected', username);
io.emit('users-list', Object.values(users));
});
// 메시지 수신 및 브로드캐스트
socket.on('chat-message', (data) => {
socket.broadcast.emit('chat-message', {
username: users[socket.id],
message: data.message,
timestamp: new Date().toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit'
})
});
});
// 타이핑 인디케이터
socket.on('typing', () => {
socket.broadcast.emit('typing', users[socket.id]);
});
socket.on('stop-typing', () => {
socket.broadcast.emit('stop-typing');
});
// 사용자 퇴장 처리
socket.on('disconnect', () => {
const username = users[socket.id];
if (username) {
socket.broadcast.emit('user-disconnected', username);
delete users[socket.id];
io.emit('users-list', Object.values(users));
}
console.log('사용자 퇴장:', socket.id);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`);
});
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 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="sidebar">
<h2>접속자 목록</h2>
<ul id="users-list"></ul>
</div>
<div id="main-chat">
<div id="messages"></div>
<div id="typing-indicator"></div>
<form id="message-form">
<input type="text" id="message-input" placeholder="메시지를 입력하세요..." autocomplete="off">
<button type="submit">전송</button>
</form>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
3단계: 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 30px rgba(0, 0, 0, 0.3);
text-align: center;
}
#login-container h1 {
margin-bottom: 20px;
color: #333;
}
#username-input {
width: 100%;
padding: 12px;
margin-bottom: 15px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
#join-btn {
width: 100%;
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
#join-btn:hover {
background: #5568d3;
}
#chat-container {
display: flex;
width: 90%;
max-width: 1200px;
height: 80vh;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
#sidebar {
width: 250px;
background: #f7f7f7;
padding: 20px;
border-right: 1px solid #ddd;
}
#sidebar h2 {
font-size: 18px;
margin-bottom: 15px;
color: #333;
}
#users-list {
list-style: none;
}
#users-list li {
padding: 10px;
margin-bottom: 5px;
background: white;
border-radius: 5px;
border-left: 3px solid #667eea;
}
#main-chat {
flex: 1;
display: flex;
flex-direction: column;
}
#messages {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.message {
margin-bottom: 15px;
padding: 10px 15px;
border-radius: 8px;
max-width: 70%;
}
.message.sent {
background: #667eea;
color: white;
margin-left: auto;
text-align: right;
}
.message.received {
background: #f1f1f1;
color: #333;
}
.message .username {
font-weight: bold;
margin-bottom: 5px;
font-size: 14px;
}
.message .timestamp {
font-size: 11px;
opacity: 0.7;
margin-top: 5px;
}
.system-message {
text-align: center;
color: #999;
font-size: 14px;
margin: 10px 0;
}
#typing-indicator {
padding: 10px 20px;
height: 30px;
color: #999;
font-style: italic;
font-size: 14px;
}
#message-form {
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;
}
#message-form button {
padding: 12px 30px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
#message-form button:hover {
background: #5568d3;
}
4단계: 클라이언트 로직 구현
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 messageForm = document.getElementById('message-form');
const messageInput = document.getElementById('message-input');
const messagesDiv = document.getElementById('messages');
const typingIndicator = document.getElementById('typing-indicator');
const usersList = document.getElementById('users-list');
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();
}
});
// 메시지 전송
messageForm.addEventListener('submit', (e) => {
e.preventDefault();
const message = messageInput.value.trim();
if (message) {
const timestamp = new Date().toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit'
});
appendMessage('sent', username, message, timestamp);
socket.emit('chat-message', { message });
messageInput.value = '';
socket.emit('stop-typing');
}
});
// 타이핑 인디케이터
messageInput.addEventListener('input', () => {
socket.emit('typing');
clearTimeout(typingTimer);
typingTimer = setTimeout(() => {
socket.emit('stop-typing');
}, 1000);
});
// 메시지 표시 함수
function appendMessage(type, user, message, timestamp) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
messageDiv.innerHTML = `
${user}
${message}
`;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 시스템 메시지
function appendSystemMessage(message) {
const systemDiv = document.createElement('div');
systemDiv.className = 'system-message';
systemDiv.textContent = message;
messagesDiv.appendChild(systemDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 소켓 이벤트 리스너
socket.on('chat-message', (data) => {
appendMessage('received', data.username, data.message, data.timestamp);
});
socket.on('user-connected', (user) => {
appendSystemMessage(`${user}님이 입장하셨습니다.`);
});
socket.on('user-disconnected', (user) => {
appendSystemMessage(`${user}님이 퇴장하셨습니다.`);
});
socket.on('typing', (user) => {
typingIndicator.textContent = `${user}님이 입력 중...`;
});
socket.on('stop-typing', () => {
typingIndicator.textContent = '';
});
socket.on('users-list', (users) => {
usersList.innerHTML = '';
users.forEach(user => {
const li = document.createElement('li');
li.textContent = user;
usersList.appendChild(li);
});
});
테스트 및 배포
로컬 테스트
개발 서버를 실행합니다:
npm run dev
브라우저에서 http://localhost:3000 에 접속하여 테스트합니다. 여러 브라우저 탭이나 창을 열어 다중 사용자 환경을 시뮬레이션할 수 있습니다. 메시지 전송, 타이핑 인디케이터, 입장/퇴장 알림이 정상적으로 작동하는지 확인하세요.
배포 옵션
Heroku 배포:
heroku create your-chat-app
git init
git add .
git commit -m "Initial commit"
git push heroku main
Render 배포: Render 대시보드에서 Web Service를 생성하고, GitHub 저장소를 연결한 후 빌드 명령어를 npm install, 시작 명령어를 npm start로 설정합니다.
Railway 배포: Railway는 간단한 설정으로 배포할 수 있으며, 환경 변수 PORT를 자동으로 처리합니다.
마무리 및 확장 아이디어
실시간 채팅 앱 만들기 with Socket.io 프로젝트를 통해 웹소켓 기반 실시간 통신의 기본 개념을 익혔습니다. 이 프로젝트는 포트폴리오에 추가하기 좋은 실용적인 애플리케이션입니다.
확장 아이디어:
- 채팅방 분리 기능 (여러 방 생성)
- 파일 및 이미지 전송 기능
- MongoDB를 활용한 채팅 기록 저장
- 사용자 인증 시스템 (JWT, OAuth)
- 개인 메시지(DM) 기능
- 이모지 및 반응 기능
- 음성/영상 통화 기능 (WebRTC)
이러한 기능들을 추가하면 더욱 완성도 높은 메신저 애플리케이션으로 발전시킬 수 있습니다. 실시간 채팅 앱 만들기 with Socket.io는 실시간 웹 애플리케이션 개발의 시작점으로 완벽한 프로젝트입니다!
📚 함께 읽으면 좋은 글
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
undefined 완벽 해결법 – 원인부터 예방까지
📅 2025. 9. 29.
🎯 undefined
FastAPI로 REST API 만들기 – 초보자도 쉽게 따라하는 완벽 가이드
📅 2025. 10. 1.
🎯 FastAPI로 REST API 만들기
React Context API 마스터하기 – 초보자도 쉽게 따라하는 완벽 가이드
📅 2025. 10. 1.
🎯 React Context API 마스터하기
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
이 글에서 가장 도움이 된 부분은 어떤 것인가요?
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!