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

실시간 채팅 앱 만들기 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}
${timestamp}
`; 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는 실시간 웹 애플리케이션 개발의 시작점으로 완벽한 프로젝트입니다!

📚 함께 읽으면 좋은 글

1

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼

2

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼

3

undefined 완벽 해결법 – 원인부터 예방까지

📂 프로젝트 아이디어
📅 2025. 9. 29.
🎯 undefined

4

FastAPI로 REST API 만들기 – 초보자도 쉽게 따라하는 완벽 가이드

📂 Python 튜토리얼
📅 2025. 10. 1.
🎯 FastAPI로 REST API 만들기

5

React Context API 마스터하기 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 10. 1.
🎯 React Context API 마스터하기

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

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

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

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

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

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

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기