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

프로젝트 소개 및 목표

실시간 채팅 앱 만들기 with Socket.io는 WebSocket 기반의 양방향 통신을 구현하여 사용자 간 즉각적인 메시지 교환이 가능한 웹 애플리케이션을 개발하는 프로젝트입니다. 이 가이드를 통해 Node.js와 Socket.io를 활용한 실시간 통신의 핵심 개념을 익히고, 실무에서 활용 가능한 채팅 시스템을 구축할 수 있습니다. 프로젝트 완성 후에는 사용자 닉네임 설정, 다중 사용자 채팅, 접속/퇴장 알림, 타이핑 인디케이터 등의 기능을 갖춘 완전한 채팅 애플리케이션을 포트폴리오에 추가할 수 있습니다. 초보자도 따라할 수 있도록 단계별로 상세히 설명하겠습니다.

필요한 기술 스택

이 프로젝트를 진행하기 위해 필요한 기술 스택은 다음과 같습니다. 백엔드로는 Node.js(v14 이상)와 Express.js 프레임워크, Socket.io 라이브러리를 사용합니다. 프론트엔드는 HTML5, CSS3, Vanilla JavaScript로 구성하며, Socket.io Client 라이브러리를 활용합니다. 개발 도구로는 VS Code, Postman(API 테스트), Git(버전 관리)을 권장합니다. 추가로 npm 또는 yarn 패키지 매니저가 필요하며, 배포를 위해 Heroku 또는 Vercel 계정을 준비하면 좋습니다.

프로젝트 셋업

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

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').createServer(app);
const io = require('socket.io')(http);
const path = require('path');

// 정적 파일 제공
app.use(express.static(path.join(__dirname, '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('set username', (username) => {
    users[socket.id] = username;
    socket.broadcast.emit('user joined', {
      username: username,
      userCount: Object.keys(users).length
    });
    socket.emit('login success', {
      userCount: Object.keys(users).length
    });
  });

  // 채팅 메시지 처리
  socket.on('chat message', (data) => {
    io.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('user typing', users[socket.id]);
  });

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

  // 연결 해제 처리
  socket.on('disconnect', () => {
    if (users[socket.id]) {
      socket.broadcast.emit('user left', {
        username: users[socket.id],
        userCount: Object.keys(users).length - 1
      });
      delete users[socket.id];
    }
    console.log('사용자 퇴장:', socket.id);
  });
});

const PORT = process.env.PORT || 3000;
http.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">
    <div class="login-box">
      <h1>실시간 채팅 앱</h1>
      <input type="text" id="username-input" placeholder="닉네임을 입력하세요" maxlength="15">
      <button id="login-btn">입장하기</button>
    </div>
  </div>

  <div id="chat-container" style="display: none;">
    <div class="chat-header">
      <h2>채팅방</h2>
      <span id="user-count">접속자: 0명</span>
    </div>
    <div id="messages"></div>
    <div id="typing-indicator" style="display: none;"></div>
    <form id="message-form">
      <input type="text" id="message-input" placeholder="메시지를 입력하세요..." autocomplete="off">
      <button type="submit">전송</button>
    </form>
  </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 {
  width: 100%;
  max-width: 400px;
  padding: 20px;
}

.login-box {
  background: white;
  padding: 40px;
  border-radius: 10px;
  box-shadow: 0 10px 25px rgba(0,0,0,0.2);
  text-align: center;
}

.login-box h1 {
  margin-bottom: 30px;
  color: #333;
}

#username-input {
  width: 100%;
  padding: 12px;
  margin-bottom: 15px;
  border: 2px solid #ddd;
  border-radius: 5px;
  font-size: 16px;
}

#login-btn {
  width: 100%;
  padding: 12px;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
  transition: background 0.3s;
}

#login-btn:hover {
  background: #5568d3;
}

#chat-container {
  width: 100%;
  max-width: 800px;
  height: 600px;
  background: white;
  border-radius: 10px;
  box-shadow: 0 10px 25px 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;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

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

.message {
  margin-bottom: 15px;
  padding: 10px 15px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.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;
}

#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: 14px;
}

#message-form button {
  margin-left: 10px;
  padding: 12px 25px;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 14px;
}

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

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

public/client.js 파일을 생성합니다.

const socket = io();
let currentUsername = '';
let typingTimer;
const typingDelay = 1000;

// DOM 요소
const loginContainer = document.getElementById('login-container');
const chatContainer = document.getElementById('chat-container');
const usernameInput = document.getElementById('username-input');
const loginBtn = document.getElementById('login-btn');
const messageForm = document.getElementById('message-form');
const messageInput = document.getElementById('message-input');
const messagesDiv = document.getElementById('messages');
const userCountSpan = document.getElementById('user-count');
const typingIndicator = document.getElementById('typing-indicator');

// 로그인 처리
loginBtn.addEventListener('click', () => {
  const username = usernameInput.value.trim();
  if (username) {
    currentUsername = username;
    socket.emit('set username', username);
    loginContainer.style.display = 'none';
    chatContainer.style.display = 'flex';
    messageInput.focus();
  }
});

// Enter 키로 로그인
usernameInput.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') loginBtn.click();
});

// 메시지 전송
messageForm.addEventListener('submit', (e) => {
  e.preventDefault();
  const message = messageInput.value.trim();
  if (message) {
    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');
  }, typingDelay);
});

// 로그인 성공
socket.on('login success', (data) => {
  userCountSpan.textContent = `접속자: ${data.userCount}명`;
});

// 채팅 메시지 수신
socket.on('chat message', (data) => {
  const messageEl = document.createElement('div');
  messageEl.className = 'message';
  messageEl.innerHTML = `
    ${data.username}
    ${escapeHtml(data.message)}
    ${data.timestamp}
  `;
  messagesDiv.appendChild(messageEl);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

// 사용자 입장
socket.on('user joined', (data) => {
  addSystemMessage(`${data.username}님이 입장했습니다.`);
  userCountSpan.textContent = `접속자: ${data.userCount}명`;
});

// 사용자 퇴장
socket.on('user left', (data) => {
  addSystemMessage(`${data.username}님이 퇴장했습니다.`);
  userCountSpan.textContent = `접속자: ${data.userCount}명`;
});

// 타이핑 표시
socket.on('user typing', (username) => {
  typingIndicator.textContent = `${username}님이 입력 중...`;
  typingIndicator.style.display = 'block';
});

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

// 시스템 메시지 추가
function addSystemMessage(message) {
  const messageEl = document.createElement('div');
  messageEl.className = 'system-message';
  messageEl.textContent = message;
  messagesDiv.appendChild(messageEl);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
}

// XSS 방지
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

테스트 및 배포

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

npm run dev

브라우저에서 http://localhost:3000 으로 접속하여 여러 탭을 열어 실시간 채팅 기능을 테스트합니다. 닉네임 설정, 메시지 전송, 타이핑 인디케이터, 입장/퇴장 알림이 정상 작동하는지 확인합니다. 배포는 Heroku를 사용할 수 있습니다.

heroku create your-chat-app-name
git init
git add .
git commit -m "Initial commit"
git push heroku main

환경 변수 PORT는 Heroku가 자동으로 설정하므로 별도 설정이 필요 없습니다. 배포 후 앱 URL로 접속하여 최종 테스트를 진행합니다.

마무리 및 확장 아이디어

실시간 채팅 앱 만들기 with Socket.io 프로젝트를 완성했습니다! 이제 기본적인 채팅 기능을 갖춘 애플리케이션을 포트폴리오로 활용할 수 있습니다. 추가 기능으로는 다중 채팅방 구현, MongoDB를 활용한 채팅 히스토리 저장, 파일 및 이미지 전송, 이모지 지원, 다크 모드, 사용자 프로필 이미지, Private 메시지, 읽음 확인 기능 등을 구현할 수 있습니다. 또한 React나 Vue.js를 적용하여 UI를 개선하거나, JWT 인증을 추가하여 보안을 강화할 수도 있습니다. 지속적으로 기능을 추가하며 실력을 향상시켜 보세요!

📚 함께 읽으면 좋은 글

1

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

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

2

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

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

3

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

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

4

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 15.
🎯 30분만에 만드는 Todo App 완성 가이드

5

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

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

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

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

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

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

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

이 글에서 가장 도움이 된 부분은 어떤 것인가요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기