본문 바로가기
Framework/NestJS

NestJS - Socket 통신을 이용한 간단한 채팅페이지 구현하기 # 4

by 아보_ 2023. 4. 28.
반응형

NestJS와 Socket.io를 사용한 채팅 서버 구현

 

이번에는 채팅 페이지의 기능을 고도화하고, 간단한 css를 입혀보겠습니다.

 

 

index.ejs 수정

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO Chat App with NestJS and EJS</title>
    <link rel="stylesheet" href="/css/styles.css" />
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="/js/socket.js"></script>
  </head>
  <body>
    <div id="messages">
      <ul id="message-list"></ul>
    </div>
    <form id="form">
      <input id="message-input" autocomplete="off" />
      <button>Send</button>
    </form>
    <script>
      socket.on('connect', () => {
        const username = prompt('당신의 이름을 입력해주세요:', '');
        socket.emit('set_username', username);

        socket.on('username_taken', () => {
          alert('이미 사용 중인 이름입니다. 다른 이름을 사용해주세요.');
          location.reload();
        });

        $('form').submit((e) => {
          e.preventDefault();
          socket.emit('chat message', {
            text: $('#message-input').val(),
            sender: username,
          });
          $('#message-input').val('');
          return false;
        });

        socket.on('chat message', (msg) => {
          const isCurrentUser = msg.sender === username;
          const messageContainer = $('<div>').addClass(
            isCurrentUser ? 'sender' : 'receiver',
          );
          const messageElement = $('<span>').addClass('message').text(msg.text);
          messageContainer.append(messageElement);
          $('#message-list').append(messageContainer);
          window.scrollTo(0, document.body.scrollHeight);
        });
      });
    </script>
  </body>
</html>
  1. 필요한 파일과 스크립트 불러오기:
    이 코드는 채팅 애플리케이션에 필요한 스타일시트, Socket.IO 라이브러리, jQuery 라이브러리, 그리고 자체적으로 작성한 socket.js 파일을 불러옵니다.
  2. 채팅 메시지를 표시할 영역 생성:
    <div id="messages"> 태그는 채팅 메시지를 보여주는 영역을 생성하며, <ul id="message-list"> 태그를 사용해 메시지 목록을 표시합니다.
  3. 메시지 입력 및 전송을 위한 폼 생성:
    <form id="form"> 태그는 메시지를 입력하고 전송할 수 있는 폼을 생성합니다. <input> 태그는 사용자가 메시지를 작성할 수 있는 입력 필드를 생성하며, <button> 태그는 메시지를 전송할 수 있는 버튼을 생성합니다.
  4. 소켓 이벤트 처리:
    이 코드는 Socket.IO를 사용해 클라이언트 측에서 발생하는 여러 이벤트를 처리합니다.
  • 'connect' 이벤트: 사용자가 서버에 연결되면 이름을 입력하라는 메시지를 표시합니다. 입력한 이름은 서버에 전송되어 저장됩니다.
  • 'username_taken' 이벤트: 사용자가 이미 사용 중인 이름을 입력하면 경고 메시지를 표시하고 페이지를 새로고침합니다.
  • 'chat message' 이벤트: 사용자가 메시지를 전송하면 해당 메시지와 사용자 이름을 함께 서버에 전송합니다.

   5. 메시지 목록 갱신:
    서버로부터 'chat message' 이벤트를 받으면 메시지 목록에 새로운 메시지를 추가합니다. 메시지가 사용자가 작성한 것인지 아닌지를      확인하여 스타일을 적용하고, 메시지를 메시지 목록에 추가합니다. 이후 페이지 스크롤을 가장 아래로 이동시켜 새로운 메시지를 확인할      있도록 합니다.

 

chat.gateway.ts 수정

import {
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  WsResponse,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway()
export class ChatGateway {
  @WebSocketServer()
  server: Server;

  connectedClients = new Map<string, string>(); // Map<Socket ID, Username>

  async handleConnection(client: Socket) {
    console.log('Client connected:', client.id);
  }

  async handleDisconnect(client: Socket) {
    this.connectedClients.delete(client.id);
    console.log('Client disconnected:', client.id);
  }

  @SubscribeMessage('set_username')
  setUsername(client: Socket, username: string): void {
    if (this.isUsernameTaken(username)) {
      client.emit('username_taken');
      client.disconnect(true);
    } else {
      this.connectedClients.set(client.id, username);
      client.emit('username_accepted', username);
    }
  }

  isUsernameTaken(username: string): boolean {
    return Array.from(this.connectedClients.values()).includes(username);
  }

  @SubscribeMessage('chat message')
  handleMessage(client: any, payload: any): WsResponse<void> {
    console.log(payload);
    this.server.emit('chat message', payload);
    return;
  }
}
  1. 필요한 모듈 임포트:
    코드는 NestJS 웹소켓 모듈과 Socket.IO 라이브러리의 타입을 임포트합니다.
  2. ChatGateway 클래스 생성:
    @WebSocketGateway() 데코레이터를 사용하여 웹소켓 게이트웨이를 정의하고, ChatGateway 클래스를 생성합니다.
  3. 서버 인스턴스 정의:
    @WebSocketServer() 데코레이터를 사용하여 서버 인스턴스를 정의하고, Server 타입의 server 변수에 할당합니다.
  4. 클라이언트 관리:
    connectedClients 맵은 연결된 클라이언트의 소켓 ID 사용자 이름을 저장합니다.
  5. 클라이언트 연결 해제 처리:
    handleConnection handleDisconnect 메서드는 각각 클라이언트가 연결되거나 연결이 해제될 호출됩니다. 메서드들은 클라이언트의 연결 상태를 콘솔에 출력하고, connectedClients 맵에서 클라이언트를 제거합니다.
  6. 사용자 이름 설정:
    @SubscribeMessage('set_username') 데코레이터를 사용하여 set_username 메시지를 처리하는 setUsername 메서드를 정의합니다. 메서드는 클라이언트가 보낸 사용자 이름을 확인하여 이미 사용 중인 이름인 경우 연결을 해제하고 'username_taken' 이벤트를 발생시키며, 그렇지 않은 경우 connectedClients 맵에 클라이언트를 추가하고 'username_accepted' 이벤트를 발생시킵니다.
  7. 사용자 이름 중복 확인:
    isUsernameTaken
    메서드는 주어진 사용자 이름이 이미 사용 중인지 확인하고 결과를 불리언 값으로 반환합니다.
  8. 메시지 처리:
    @SubscribeMessage('chat message') 데코레이터를 사용하여 chat message 메시지를 처리하는 handleMessage 메서드를 정의합니다. 메서드는 전송된 메시지를 콘솔에 출력하고, 모든 클라이언트에게 'chat message' 이벤트를 발생시켜 메시지를 전달합니다.

css파일 생성

public 폴더에 css폴더를 생성하고 css폴더에 styles.css를 생성합니다.

body {
    font-family: Arial, Helvetica, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f7f7f7;
  }
  
  #chat {
    display: flex;
    flex-direction: column;
    height: 100vh;
  }
  
  #messages {
    flex: 1;
    list-style-type: none;
    margin: 0;
    padding: 20px 20px 80px;
    overflow-y: scroll;
    background-color: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    max-width: 800px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
  }
  
  .sender,
  .receiver {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 10px;
  }
  
  .sender {
    justify-content: flex-end;
  }
  
  .message {
    padding: 8px 16px;
    border-radius: 18px;
    display: inline-block;
    max-width: 70%;
    word-wrap: break-word;
  }
  
  .receiver .message {
    background-color: #f0f0f0;
    color: #333;
  }
  
  .sender .message {
    background-color: #4caf50;
    color: #ffffff;
  }
  
  #form {
    background-color: #f0f0f0;
    display: flex;
    margin: 0;
    padding: 20px;
    position: fixed;
    bottom: 0;
    width: 100%;
    justify-content: center;
    box-sizing: border-box;
  }
  
  #input {
    flex: 1;
    padding: 10px;
    font-size: 16px;
    border-radius: 4px;
    border: 1px solid #ccc;
    max-width: 800px;
    margin-right: 10px;
    box-sizing: border-box;
  }
  
  button {
    background-color: #4caf50;
    border: none;
    color: white;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
    padding: 10px 24px;
    border-radius: 4px;
  }
  
  button:hover {
    background-color: #45a049;
  }

 

이제 NestJS와 EJS, 그리고 Socket.io를 사용한 간단한 채팅 애플리케이션이 완성되었습니다. 애플리케이션을 실행하고 여러 브라우저에서 테스트해보세요.

터미널에서 다음 명령을 실행하여 애플리케이션을 시작합니다.

npm run start

 

간단한 채팅 서비스가 완료 되었습니다 !!
궁금하신점이나 안되시는 부분이 있으면 댓글 남겨주세요!!
반응형