🛠️ 실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

개발 에러 해결 가이드 - FixLog 노트

프로젝트 소개 및 목표

실시간 채팅 앱 만들기 with Socket.io는 웹소켓 기술을 활용하여 사용자 간 즉각적인 메시지 교환이 가능한 애플리케이션을 구축하는 프로젝트입니다. 이 가이드에서는 Node.js와 Express를 백엔드로, Socket.io를 실시간 통신 라이브러리로 사용하여 완전히 동작하는 채팅 애플리케이션을 만들어봅니다. 프론트엔드는 HTML, CSS, JavaScript로 구성하며, 사용자 입장/퇴장 알림, 실시간 메시지 전송, 타이핑 표시 등의 기능을 구현합니다. 이 프로젝트를 통해 WebSocket의 동작 원리를 이해하고, 실시간 통신이 필요한 다양한 애플리케이션 개발의 기초를 다질 수 있습니다. 완성된 프로젝트는 포트폴리오로 활용하기에도 훌륭합니다.

필요한 기술 스택

이 프로젝트를 진행하기 위해 다음 기술들이 필요합니다. 백엔드: Node.js (v14 이상), Express.js (웹 서버 프레임워크), Socket.io (실시간 양방향 통신). 프론트엔드: HTML5, CSS3, Vanilla JavaScript, Socket.io 클라이언트 라이브러리. 개발 도구: VS Code 또는 원하는 코드 에디터, npm 또는 yarn (패키지 매니저), Git (버전 관리). 모든 기술은 무료로 사용 가능하며, 초보자도 쉽게 따라할 수 있도록 구성되어 있습니다.

프로젝트 셋업

먼저 프로젝트 폴더를 생성하고 npm을 초기화합니다. 터미널에서 다음 명령어를 실행하세요:

mkdir realtime-chat-app
cd realtime-chat-app
npm init -y

필요한 패키지를 설치합니다:

npm install express socket.io
npm install --save-dev nodemon

프로젝트 구조를 다음과 같이 만듭니다:

realtime-chat-app/
├── server.js
├── public/
│   ├── index.html
│   ├── style.css
│   └── client.js
└── package.json

package.json의 scripts 섹션에 개발 서버 실행 스크립트를 추가합니다:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

단계별 구현 과정

1단계: Express 서버 및 Socket.io 설정

server.js 파일을 생성하고 기본 서버를 구축합니다:

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 = new Map();

// Socket.io 연결 처리
io.on('connection', (socket) => {
  console.log('새 사용자 연결:', socket.id);

  // 사용자 입장 처리
  socket.on('user joined', (username) => {
    users.set(socket.id, username);
    io.emit('user joined', {
      username: username,
      userCount: users.size
    });
  });

  // 메시지 수신 및 전파
  socket.on('chat message', (data) => {
    io.emit('chat message', {
      username: users.get(socket.id),
      message: data.message,
      timestamp: new Date().toLocaleTimeString()
    });
  });

  // 타이핑 중 표시
  socket.on('typing', () => {
    socket.broadcast.emit('typing', {
      username: users.get(socket.id)
    });
  });

  socket.on('stop typing', () => {
    socket.broadcast.emit('stop typing');
  });

  // 연결 해제 처리
  socket.on('disconnect', () => {
    const username = users.get(socket.id);
    if (username) {
      users.delete(socket.id);
      io.emit('user left', {
        username: username,
        userCount: users.size
      });
    }
    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 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="chat-header">
        <h2>채팅방</h2>
        <span id="userCount" class="user-count">접속자: 0명</span>
      </div>
      
      <div id="messages" class="messages"></div>
      
      <div id="typingIndicator" class="typing-indicator" style="display: none;">
        <span></span> 님이 입력 중...
      </div>
      
      <div class="input-area">
        <input type="text" id="messageInput" placeholder="메시지를 입력하세요..." autocomplete="off">
        <button id="sendBtn">전송</button>
      </div>
    </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;
}

.container {
  width: 90%;
  max-width: 600px;
  height: 90vh;
  background: white;
  border-radius: 20px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.login-screen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 40px;
}

.login-screen h1 {
  color: #667eea;
  margin-bottom: 30px;
}

.login-screen input {
  width: 100%;
  padding: 15px;
  font-size: 16px;
  border: 2px solid #ddd;
  border-radius: 10px;
  margin-bottom: 20px;
  transition: border 0.3s;
}

.login-screen input:focus {
  outline: none;
  border-color: #667eea;
}

.login-screen button {
  width: 100%;
  padding: 15px;
  font-size: 16px;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.3s;
}

.login-screen button:hover {
  background: #5568d3;
}

.chat-screen {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.chat-header {
  background: #667eea;
  color: white;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.user-count {
  background: rgba(255, 255, 255, 0.2);
  padding: 5px 15px;
  border-radius: 20px;
  font-size: 14px;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
  background: #f5f5f5;
}

.message {
  margin-bottom: 15px;
  animation: slideIn 0.3s ease;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.message .username {
  font-weight: bold;
  color: #667eea;
  margin-right: 8px;
}

.message .timestamp {
  font-size: 12px;
  color: #999;
  margin-left: 8px;
}

.system-message {
  text-align: center;
  color: #999;
  font-size: 14px;
  margin: 10px 0;
  font-style: italic;
}

.typing-indicator {
  padding: 10px 20px;
  color: #667eea;
  font-size: 14px;
  font-style: italic;
}

.input-area {
  display: flex;
  padding: 20px;
  background: white;
  border-top: 1px solid #ddd;
}

.input-area input {
  flex: 1;
  padding: 15px;
  font-size: 16px;
  border: 2px solid #ddd;
  border-radius: 10px;
  margin-right: 10px;
}

.input-area input:focus {
  outline: none;
  border-color: #667eea;
}

.input-area button {
  padding: 15px 30px;
  font-size: 16px;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.3s;
}

.input-area button:hover {
  background: #5568d3;
}

4단계: 클라이언트 JavaScript 구현

public/client.js 파일을 생성하여 클라이언트 로직을 작성합니다:

const socket = io();

// DOM 요소
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 messagesDiv = document.getElementById('messages');
const userCountSpan = document.getElementById('userCount');
const typingIndicator = document.getElementById('typingIndicator');

let username = '';
let typingTimer;

// 입장 처리
joinBtn.addEventListener('click', () => {
  const name = usernameInput.value.trim();
  if (name) {
    username = name;
    socket.emit('user joined', username);
    loginScreen.style.display = 'none';
    chatScreen.style.display = 'flex';
    messageInput.focus();
  }
});

// Enter 키로 입장
usernameInput.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    joinBtn.click();
  }
});

// 메시지 전송
const 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 = 'message';
  messageEl.innerHTML = `
    ${escapeHtml(data.username)}:
    ${escapeHtml(data.message)}
    ${data.timestamp}
  `;
  messagesDiv.appendChild(messageEl);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

// 사용자 입장
socket.on('user joined', (data) => {
  const systemMsg = document.createElement('div');
  systemMsg.className = 'system-message';
  systemMsg.textContent = `${data.username}님이 입장하셨습니다.`;
  messagesDiv.appendChild(systemMsg);
  userCountSpan.textContent = `접속자: ${data.userCount}명`;
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

// 사용자 퇴장
socket.on('user left', (data) => {
  const systemMsg = document.createElement('div');
  systemMsg.className = 'system-message';
  systemMsg.textContent = `${data.username}님이 퇴장하셨습니다.`;
  messagesDiv.appendChild(systemMsg);
  userCountSpan.textContent = `접속자: ${data.userCount}명`;
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

// 타이핑 표시
socket.on('typing', (data) => {
  typingIndicator.querySelector('span').textContent = data.username;
  typingIndicator.style.display = 'block';
});

socket.on('stop typing', () => {
  typingIndicator.style.display = 'none';
});

// XSS 방지를 위한 HTML 이스케이프
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

테스트 및 배포

개발 서버를 실행하여 테스트합니다:

npm run dev

브라우저에서 http://localhost:3000 을 열고, 여러 탭에서 동시에 접속하여 실시간 채팅이 잘 동작하는지 확인합니다. 테스트 항목: 닉네임 입력 후 입장, 메시지 전송 및 수신, 타이핑 표시, 사용자 입장/퇴장 알림, 접속자 수 표시. 배포는 Heroku, Render, Railway 등의 플랫폼을 사용할 수 있습니다. 환경 변수로 PORT를 설정하고, package.json의 start 스크립트가 올바르게 설정되어 있는지 확인하세요. Git 저장소에 푸시한 후 배포 플랫폼과 연동하면 자동으로 배포됩니다.

마무리 및 확장 아이디어

실시간 채팅 앱 만들기 with Socket.io 프로젝트를 완성했습니다! 이제 기본적인 실시간 채팅 기능을 갖춘 애플리케이션이 작동합니다. 추가로 구현할 수 있는 기능들: 개인 메시지(DM) 기능, 채팅방 여러 개 생성, 이미지/파일 전송, 메시지 저장 (MongoDB 연동), 읽음 표시, 이모지 반응, 사용자 프로필 이미지, 메시지 검색 기능 등이 있습니다. 이 프로젝트를 기반으로 더 복잡한 실시간 협업 도구나 게임을 만들어볼 수 있습니다. GitHub에 코드를 업로드하고 포트폴리오에 추가하세요!

📚 함께 읽으면 좋은 글

1

실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 25.
🎯 실시간 채팅 앱 만들기 with Socket.io

2

실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 18.
🎯 실시간 채팅 앱 만들기 with Socket.io

3

실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 9.
🎯 실시간 채팅 앱 만들기 with Socket.io

4

React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 27.
🎯 React + Node.js 풀스택 앱 배포하기

5

JWT 인증 시스템 구현하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 25.
🎯 JWT 인증 시스템 구현하기

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨


📘 페이스북


🐦 트위터


✈️ 텔레그램

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

실시간 채팅 앱 만들기 with Socket.io 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

📱 전체 버전 보기