본문 바로가기
개발/개인, 사이드 프로젝트

Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 3

by 코딩하는짱구 2023. 11. 2.
반응형

Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 3 

1. chat, user table 완성 후 socket으로 front-back 상호작용 연결하기

2023.10.13 - [개발관련/개인프로젝트] - Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 1

 

Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 1

쇼핑몰 애플리케이션에서 간단하게 구현해봤던 웹소켓을 이용해서 간단한 채팅앱을 구현해보기로 했다. Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 1 1. 어떻게 만들 것인가? 2. 오늘 겪은 문제

veritas-crystal.tistory.com

 

2. user login 기능 구현, user 정보 전달/저장

2023.10.19 - [개발관련/개인프로젝트] - Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 2

 

Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 2

Express, React, Mongoose 로 웹소켓 채팅앱 만들기. 2 1탄에서 socket 으로 프론트/백 연결까지 했으니, 이제는 user login 기능을 구현해야한다. 1. Today I learned 2. 오늘 겪은 문제 위의 목차를 클릭하면 해당

veritas-crystal.tistory.com

 

오늘은 chat 내용을 db에 전달/저장하는 마지막 단계를 완성할 예정이다. 

1. Today I learned

2. 오늘 겪은 문제

위의 목차를 클릭하면 해당 글로 자동 이동 합니다.

 

 

 

1. Today I learned

login한 user가 채팅을 위해 text를 입력하면, 그 text는 message list에 저장되어야하고,

채팅방에 있는 모두가 알아야한다. 먼저 front 에서 sendMessage 함수를 통해 text를 전달받고, socket emit을 통해 back으로 전달하는 기능을 구현했다. 

 

import { useEffect, useState } from 'react';
import './App.css';
import socket from './server';
import InputField from './components/InputField/InputField';
import MessageContainer from './components/MessageContainer/MessageContainer';

function App() {
  const [user, setUser] = useState(null);
  const [message, setMessage] = useState('');
  const [messageList, setMessageList] = useState([]);
  console.log('messageList', messageList);
  useEffect(() => {
    askUserName();
    socket.on('message', (message) => {
      setMessageList((prevState) => prevState.concat(message));
    });
  }, []);

  const askUserName = () => {
    const userName = prompt('당신의 이름을 입력하세요');
    // console.log('uuu', userName);
    socket.emit('login', userName, (res) => {
      if (res?.ok) {
        setUser(res.data);
      }
      console.log('Res', res);
    });
  };

  const sendMessage = (event) => {
    event.preventDefault();
    socket.emit('sendMessage', message, (res) => {
      console.log('sendMessage res', res);
    });
  };

 

back으로 전달된 메세지는 user아이디를 확인 후 db에 저장되어야 한다. 

 

const chatController = require('../Controllers/chat.controller');
const userController = require('../Controllers/user.controller');
module.exports = function (io) {
  // io 관련된 모든 일
  io.on('connection', async (Socket) => {
    console.log('client connection', Socket.id);

    //1. 유저정보
    Socket.on('login', async (userName, cb) => {
      // 유저 정보를 저장
      console.log(`${userName}님이 채팅방에 입장하셨습니다.`);
      try {
        const user = await userController.saveUser(userName, Socket.id);
        const welcome = {
          chat: `${user.name}is joined to this room`,
          user: { id: null, name: 'system' },
        };
        io.emit('message', welcome);
        cb({ ok: true, data: user });
      } catch (error) {
        cb({ ok: false, data: error.message });
      }
    });

    Socket.on('sendMessage', async (message, cb) => {
      try {
        // socket id로 유저를 찾고
        const user = await userController.checkUser(Socket.id);
        // 메세지 저장(유저)
        const newMessage = await chatController.saveChat(message, user);
        io.emit('message', newMessage);
        cb({ ok: true });
      } catch (error) {
        cb({ ok: false, data: error.message });
      }
    });

    Socket.on('disconnect', () => {
      console.log('user disconnected');
    });
  });
};

newMessage는 message와 user를 인자로 받는 chatController.saveChat 메서드에서 반환된 값이다. 

 

인자로 받을 user는 checkUser 라는 함수안에서 socket.id 를 통해 확인한다. 

확인된 user와 message 는 io.emit를 통해 방에 있는 모두에게 알려진다. 

 

여기서 이용될 chatController.saveChat 함수는, 따로 chatController 파일 안에서 구현된다.

const Chat = require('../Models/chat');
const chatController = {};

chatController.saveChat = async (message, user) => {
  const newMessage = new Chat({
    chat: message,
    user: {
      id: user._id,
      name: user.name,
    },
  });

  await newMessage.save();
  return newMessage;
};
module.exports = chatController;

chatController.saveChat 함수는 chat model의 형태를 가진 chat을 저장하는 역할을 한다. 

chat schema는 chat(String), 해당 chat의 _id, user정보(user id, name)으로 이루어져있다. 

 

아래와 같이 닉네임 '짱구맨'으로 로그인 후, 채팅을 쳐보았다. 

 

짱구맨에 해당하는 socket id는 'N3BPzxYkWEXViddoAAAB' 

짱구맨의 _id 는 "6540b3f10b771a5cb2314c77" 이다. 

만약 코드가 정상적으로 작동한다면 chat db에는 chat schema는 chat(String), 해당 chat의 _id, user정보(user id, name)가 저장될 것이다. 

 

 

 

두근두근두근

.

.

.

.

.

 

 

짜잔~~! 

String 타입의 chat, chat의 _id, 그리고 Object타입의 user 정보가 정상적으로 저장되었다. 

user object에는 user의 id(ObjectId :  "6540b3f10b771a5cb2314c77")가 저장되었고, 

user name도 잘 저장된 것을 볼 수 있다. 

 

이렇게 프론트에서 sendMessage 이벤트를 받으면 백에서 user, message를 확인 저장하고 to.emit을 통해 새 메세지 객체를 모든 연결된 클라이언트에게 브로드캐스팅 하는 것이다. 😚

 

 

2. 오늘 겪은 문제 

1. user 정보가 제대로 전달되지 않는 문제

front-back 코드를 완성 후 messageContainer css를 통해 메세지를 출력하려 하는데, 오류가 발생했다. 

처음보는 화면 ㄷㄷㄷ..

내용을 살펴보면 어떤 객체의 name property를 읽으려고 시도했지만 해당 객체가 정의되지 않았다는 뜻이다. 

askUserName을 통해 name을 입력했는데도 name 을 읽을 수 없는 이유가 뭘까?

 

살펴보니 userController.checkUser(Socket.id)를 사용하여 사용자를 찾는 동안 await가 빠져 발생하는 문제였다. 

userController.checkUser(Socket.id)는 비동기 함수이기 때문에 await 를 사용하여 해당 함수가 완료될 때까지 기다려야한다. await가 없으면 해당 함수의 결과를 받아오기 전에 다음 코드로 넘어가기 때문에, user정보가 check되지 않고 넘어가게되므로 메세지가 저장될 수 없는 것이다. 

Socket.on('sendMessage', async (message, cb) => {
      try {
        // socket id로 유저를 찾고
        const user = userController.checkUser(Socket.id);
        // 메세지 저장(유저)
        const newMessage = await chatController.saveChat(message, user);
        io.emit('message', newMessage);
        cb({ ok: true });
      } catch (error) {
        cb({ ok: false, data: error.message });
      }
    });

 

await 를 추가하면 문제는 해결!

 

 

2. text를 입력한 후에 메세지 입력칸을 공백으로 만들기

text 입력 후에도 메세지 입력창에 문장이 그대로 있어서, 실제 app과 유사하게 수정했다!

 

//inputFeild.jsx

import React from 'react';
import { Input } from '@mui/base/Input';
import { Button } from '@mui/base/Button';
import './InputField.css';
const InputField = ({ message, setMessage, sendMessage }) => {
  const handleSendMessage = (event) => {
    event.preventDefault();
    // 메시지를 보낸 후 메시지 상태 초기화
    sendMessage(event);
    setMessage(''); // 메시지를 초기화하여 입력창을 비웁니다.
  };

 

 

이렇게 해서 채팅앱 구현을 해보았고, 추후 채팅방 기능을 구현할 생각이다. 

반응형