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

프로젝트 소개 및 목표

실시간 채팅 앱 만들기 with Socket.io 프로젝트는 웹소켓 기술을 활용하여 실시간 양방향 통신이 가능한 채팅 애플리케이션을 구축하는 실습 프로젝트입니다. 이 프로젝트를 통해 Node.js와 Socket.io의 핵심 개념을 이해하고, 실시간 데이터 통신의 원리를 배울 수 있습니다. 완성된 채팅 앱은 여러 사용자가 동시에 접속하여 메시지를 주고받을 수 있으며, 접속자 목록 표시, 타이핑 인디케이터, 입장/퇴장 알림 등의 기능을 포함합니다. 포트폴리오에 추가할 수 있는 실용적인 풀스택 프로젝트로, 실무에서 자주 사용되는 실시간 통신 기술을 직접 경험할 수 있습니다.

필요한 기술 스택

이 프로젝트를 구현하기 위해 필요한 기술 스택은 다음과 같습니다. 백엔드는 Node.js와 Express.js 프레임워크를 사용하며, 실시간 통신을 위해 Socket.io 라이브러리를 활용합니다. 프론트엔드는 HTML5, CSS3, 바닐라 JavaScript로 구성하며, Socket.io 클라이언트 라이브러리를 사용합니다. 개발 환경은 npm 또는 yarn 패키지 매니저가 필요하며, 코드 에디터로는 VS Code를 권장합니다. 기본적인 JavaScript ES6+ 문법과 비동기 프로그래밍에 대한 이해가 있으면 더욱 수월하게 진행할 수 있습니다.

프로젝트 셋업

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

mkdir realtime-chat-app
cd realtime-chat-app
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"
}

프로젝트 구조는 다음과 같이 구성합니다: server.js (서버 파일), public 폴더 (정적 파일), public/index.html (메인 페이지), public/style.css (스타일), public/client.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 = {};

// Socket.io 연결 이벤트
io.on('connection', (socket) => {
  console.log('새로운 사용자가 연결되었습니다:', socket.id);

  // 사용자 입장 처리
  socket.on('join', (username) => {
    users[socket.id] = username;
    socket.broadcast.emit('user-joined', username);
    io.emit('user-list', Object.values(users));
  });

  // 메시지 수신 및 전송
  socket.on('chat-message', (data) => {
    socket.broadcast.emit('chat-message', {
      username: users[socket.id],
      message: data.message,
      timestamp: new Date().toISOString()
    });
  });

  // 타이핑 이벤트
  socket.on('typing', () => {
    socket.broadcast.emit('typing', users[socket.id]);
  });

  socket.on('stop-typing', () => {
    socket.broadcast.emit('stop-typing', users[socket.id]);
  });

  // 연결 해제 처리
  socket.on('disconnect', () => {
    const username = users[socket.id];
    delete users[socket.id];
    socket.broadcast.emit('user-left', username);
    io.emit('user-list', Object.values(users));
    console.log('사용자 연결 해제:', username);
  });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행 중입니다`);
});

2단계: HTML 인터페이스 구현

public/index.html 파일을 생성하고 채팅 UI를 구현합니다:

<!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-form" id="loginForm">
      <h2>실시간 채팅 입장</h2>
      <input type="text" id="usernameInput" placeholder="사용자 이름을 입력하세요" maxlength="20">
      <button id="joinBtn">입장하기</button>
    </div>
    
    <div class="chat-container" id="chatContainer" style="display:none;">
      <div class="sidebar">
        <h3>접속자 목록</h3>
        <ul id="userList"></ul>
      </div>
      
      <div class="chat-main">
        <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="메시지를 입력하세요..." disabled>
          <button id="sendBtn" disabled>전송</button>
        </div>
      </div>
    </div>
  </div>
  
  <script src="/socket.io/socket.io.js"></script>
  <script src="client.js"></script>
</body>
</html>

3단계: 클라이언트 로직 구현

public/client.js 파일을 생성하고 클라이언트 측 Socket.io 연결 및 이벤트 처리를 구현합니다:

const socket = io();

const loginForm = document.getElementById('loginForm');
const chatContainer = document.getElementById('chatContainer');
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 userList = document.getElementById('userList');
const currentUserSpan = document.getElementById('currentUser');
const typingIndicator = document.getElementById('typingIndicator');

let currentUsername = '';
let typingTimeout;

// 입장 버튼 클릭
joinBtn.addEventListener('click', () => {
  const username = usernameInput.value.trim();
  if (username) {
    currentUsername = username;
    socket.emit('join', username);
    loginForm.style.display = 'none';
    chatContainer.style.display = 'flex';
    currentUserSpan.textContent = username;
    messageInput.disabled = false;
    sendBtn.disabled = false;
    messageInput.focus();
  }
});

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

// 메시지 전송
function sendMessage() {
  const message = messageInput.value.trim();
  if (message) {
    // 내 메시지 표시
    appendMessage('나', message, 'sent');
    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(typingTimeout);
  typingTimeout = setTimeout(() => {
    socket.emit('stop-typing');
  }, 1000);
});

// 메시지 추가 함수
function appendMessage(username, message, type = 'received') {
  const messageEl = document.createElement('div');
  messageEl.classList.add('message', type);
  
  const time = new Date().toLocaleTimeString('ko-KR', { 
    hour: '2-digit', 
    minute: '2-digit' 
  });
  
  messageEl.innerHTML = `
    
${username} ${time}
${message}
`; messagesDiv.appendChild(messageEl); messagesDiv.scrollTop = messagesDiv.scrollHeight; } // 시스템 메시지 추가 function appendSystemMessage(message) { const messageEl = document.createElement('div'); messageEl.classList.add('message', 'system'); messageEl.textContent = message; messagesDiv.appendChild(messageEl); messagesDiv.scrollTop = messagesDiv.scrollHeight; } // Socket.io 이벤트 리스너 socket.on('chat-message', (data) => { appendMessage(data.username, data.message, 'received'); }); socket.on('user-joined', (username) => { appendSystemMessage(`${username}님이 입장했습니다.`); }); socket.on('user-left', (username) => { appendSystemMessage(`${username}님이 퇴장했습니다.`); }); socket.on('user-list', (users) => { userList.innerHTML = users.map(user => `
  • ${user}${user === currentUsername ? ' (나)' : ''}
  • ` ).join(''); }); socket.on('typing', (username) => { typingIndicator.textContent = `${username}님이 입력 중입니다...`; typingIndicator.style.display = 'block'; }); socket.on('stop-typing', () => { typingIndicator.style.display = 'none'; });

    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: 90%;
      max-width: 1200px;
      height: 90vh;
    }
    
    .login-form {
      background: white;
      padding: 40px;
      border-radius: 10px;
      box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      text-align: center;
      max-width: 400px;
      margin: 0 auto;
    }
    
    .login-form h2 {
      margin-bottom: 20px;
      color: #333;
    }
    
    .login-form input {
      width: 100%;
      padding: 12px;
      margin-bottom: 15px;
      border: 2px solid #ddd;
      border-radius: 5px;
      font-size: 16px;
    }
    
    .login-form button {
      width: 100%;
      padding: 12px;
      background: #667eea;
      color: white;
      border: none;
      border-radius: 5px;
      font-size: 16px;
      cursor: pointer;
      transition: background 0.3s;
    }
    
    .login-form button:hover {
      background: #5568d3;
    }
    
    .chat-container {
      display: flex;
      background: white;
      border-radius: 10px;
      box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      height: 100%;
      overflow: hidden;
    }
    
    .sidebar {
      width: 250px;
      background: #f7f7f7;
      padding: 20px;
      border-right: 1px solid #ddd;
    }
    
    .sidebar h3 {
      margin-bottom: 15px;
      color: #333;
    }
    
    .sidebar ul {
      list-style: none;
    }
    
    .sidebar li {
      padding: 10px;
      margin-bottom: 5px;
      background: white;
      border-radius: 5px;
      color: #555;
    }
    
    .chat-main {
      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;
      padding: 20px;
      overflow-y: auto;
      background: #fafafa;
    }
    
    .message {
      margin-bottom: 15px;
      padding: 10px 15px;
      border-radius: 10px;
      max-width: 70%;
      animation: fadeIn 0.3s;
    }
    
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }
    
    .message.sent {
      background: #667eea;
      color: white;
      margin-left: auto;
    }
    
    .message.received {
      background: white;
      color: #333;
      border: 1px solid #e0e0e0;
    }
    
    .message.system {
      background: #fff3cd;
      color: #856404;
      text-align: center;
      margin: 10px auto;
      max-width: 100%;
      font-size: 14px;
    }
    
    .message-header {
      display: flex;
      justify-content: space-between;
      margin-bottom: 5px;
      font-size: 12px;
    }
    
    .message-text {
      word-wrap: break-word;
    }
    
    .time {
      opacity: 0.7;
      font-size: 11px;
    }
    
    .typing-indicator {
      padding: 10px 20px;
      font-size: 14px;
      color: #666;
      font-style: italic;
      display: none;
    }
    
    .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: 16px;
      margin-right: 10px;
    }
    
    .message-input button {
      padding: 12px 30px;
      background: #667eea;
      color: white;
      border: none;
      border-radius: 5px;
      font-size: 16px;
      cursor: pointer;
      transition: background 0.3s;
    }
    
    .message-input button:hover {
      background: #5568d3;
    }
    
    .message-input button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }

    테스트 및 배포

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

    npm run dev

    브라우저에서 http://localhost:3000에 접속하여 기능을 테스트합니다. 여러 브라우저 탭이나 시크릿 모드를 활용하여 다중 사용자 환경을 시뮬레이션할 수 있습니다. 배포는 Heroku, Render, Railway 등의 플랫폼을 활용할 수 있습니다. Heroku 배포 시에는 Procfile을 생성하고 다음 내용을 추가합니다:

    web: node server.js

    환경 변수로 PORT를 설정하고, package.json의 engines 필드에 Node.js 버전을 명시합니다. Git으로 코드를 푸시하면 자동으로 배포됩니다.

    마무리 및 확장 아이디어

    실시간 채팅 앱 만들기 with Socket.io 프로젝트를 성공적으로 완성했습니다! 이 프로젝트를 더욱 발전시킬 수 있는 확장 아이디어로는 방(Room) 기능 추가, 파일 및 이미지 전송, 이모지 지원, 메시지 검색 기능, MongoDB를 활용한 채팅 기록 저장, JWT 인증을 통한 사용자 관리, 다크 모드 지원 등이 있습니다. 또한 React나 Vue.js를 활용하여 프론트엔드를 개선하거나, Redis를 도입하여 확장성을 높일 수 있습니다. 이 프로젝트는 포트폴리오에 추가하기에 훌륭한 실습 프로젝트이며, 실시간 통신 기술에 대한 이해도를 크게 향상시킬 수 있습니다.

    📚 함께 읽으면 좋은 글

    1

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

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

    2

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

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

    3

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

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

    4

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

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

    5

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

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

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

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

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

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

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

    이 글을 읽고 새롭게 알게 된 정보가 있다면 공유해주세요!

    💡
    유용한 정보 공유

    궁금한 점 질문

    🤝
    경험담 나누기

    👍
    의견 표현하기

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

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

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

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

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

    💡
    최신 트렌드
    2025년 기준

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

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

    답글 남기기