socket.io로 채팅 구현해보기( client, server, mongo )

돌리의 하루·2023년 7월 24일

socket.io로 채팅 방을 구현하고, 방에 들어와서 메세지를 남기는 기능을 구현해보았다.

사실 처음에는 websocket을 이용해서 백엔드와 통신하는 것이 목표였는데, 어찌하다보니 socket.io를 통한 js로 client와 server를 내가 구현해보게 되었다!

맨 위는 모바일 버전이고, 웹으로 보게 되면 이렇다. 내용과 쓴 날짜를 보여주게 되는데, client쪽에서는 room과 들어갈 내용을 설정해주고, 받는 서버의 uri와 메세지를 뿌려주는 map 형식으로 구성된다.

react의 useRef처럼, socket에서는 socketRef를 쓰게 되는데, socketRef.current는 소켓 인스턴스를 저장하는데 사용되고, 구성 요소가 해제될 때 자동으로 연결이 끊어지게 된다.

import React, { useState, useEffect, useRef } from 'react';
import io, { Socket } from 'socket.io-client';
import Pet from '../../Style/icon_Pet.png';

type ChatEvent = {
  message: string;
  room: string;
  createdAt: string;
};

const ChatComponent: React.FC = () => {
  const [message, setMessage] = useState<string>('');
  const [messages, setMessages] = useState<
    { room: string; message: string; createdAt: Date }[]
  >([]);

  const socketRef = useRef<Socket | null>(null);
  const [room, setRoom] = useState<string>('defaultRoom');
  const [inputValue, setInputValue] = useState<string>('');
  const filteredMessages = messages.filter(msg => msg.room === room);
  useEffect(() => {
    console.log('socketRef.current:', socketRef.current);
    socketRef.current = io('http://localhost:4000');

    socketRef.current.emit('joinRoom', room);

    socketRef.current?.on('chat', (data: ChatEvent) => {
      console.log('Received chat event:', data);
      setMessages(messages => [
        ...messages,
        { ...data, createdAt: new Date() },
      ]);
    });
    socketRef.current?.on('load_messages', messages => {
      setMessages(messages);
    });

    return () => {
      socketRef.current?.disconnect();
    };
  }, [room]);

  const joinRoom = () => {
    console.log('조인룸');
    if (socketRef.current && inputValue) {
      setRoom(inputValue);
      socketRef.current.emit('joinRoom', inputValue);
    }
  };

  const sendMessage = (e: React.FormEvent) => {
    console.log('메세지');
    e.preventDefault();

    if (socketRef.current) {
      socketRef.current.emit('chat', {
        message,
        room,
      });

      setMessage('');
    }
  };

중간중간 console을 찍어 오류를 확인하고 코드를 수정했다.

이제 통신할 서버쪽도 만들어줘야 한다. node.js를 사용해서 server/index.js 안에 코드를 구성했다.

사실, 서버쪽에서 제일 힘들게 경험했던 것은 mongo를 설치하고 채팅 내용을 저장하는 부분이었다.

client쪽에서 채팅을 뿌려주기만 하면 금방 될 것 같아서 간단하게 생각하고 있었는데, 서버쪽과 같이 만드

면서 채팅 내역까지 저장해보려니 음....생각보다 빡셌다.

message.js로 저장하는 코드는 index.js와 다른 장소에 코드를 넣었다.

//message.js
const mongoose = require("mongoose");

const messageSchema = new mongoose.Schema(
  {
    room: {
      type: String,
      required: true,
    },
    message: {
      type: String,
      required: true,
    },
    createdAt: {
      type: Date,
      default: Date.now,
    },
  },
  { timestamps: true }
);

module.exports = mongoose.model("Message", messageSchema);
const express = require("express");
const { Server } = require("socket.io");
const http = require("http");
const cors = require("cors");
const app = express();
app.use(cors());

const mongoose = require("mongoose");
const Message = require("./message");

const PORT = process.env.PORT || 4000;
const MONGODB_URI = "mongodb://localhost:27017/chatting";

mongoose.connect(MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function () {
  console.log("Connected to MongoDB");
});

const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
  },
});

io.on("connection", (socket) => {
  console.log("New client connected");

  socket.on("joinRoom", async (room) => {
    console.log("socket - joinRoom");
    socket.join(room);
    socket.emit("notification", `You have joined room ${room}`);

    const messages = await Message.find({ room: room })
      .sort({ createdAt: -1 })
      .limit(50)
      .exec();

    socket.emit(
      "load_messages",
      messages.reverse().map((msg) => ({
        room: msg.room,
        message: msg.message,
        createdAt: msg.createdAt,
      }))
    );
  });

  socket.on("chat", async (data) => {
    console.log(data);
    io.to(data.room).emit("chat", data);
    const message = new Message({
      room: data.room,
      message: data.message,
    });
    message
      .save()
      .then((result) => console.log("Message saved:", result))
      .catch((err) => console.error("Error saving message:", err));
  });

  socket.on("disconnect", () => {
    console.log("Client disconnected");
  });
});

server.listen(PORT, () => console.log(`Server started on ${PORT}`));

지금 보니까 서버쪽 코드에 console이 유난히 많은게, 어디가 잘못된지 몰라서 하루종일 오류를 보면서 찍어봤던것같다....

사실 완전히 처음 시도해보고 도전해보았던 부분이라, 완벽하게 했다고는 말할 수가 없다.

부족한 부분이 있는건 알 수 있었지만, 내가 할 수 있는 부분부터 차근히 고쳐 나가볼 예정이다.

++(본문과는 관계없는 넋두리)그런데 확실히 tailwind.css를 쓰니까 반응형을 훨~씬 쉽게 만들 수 있다. 혁명이다,,!

모바일 환경으로도 나쁘지 않게 틀이 잡힌걸 보면 너무 신기하다. 뭐, 약간 코드가 좀 너저분 해지긴 하지만... media query를 쓰지 않고도 이정도면 훌륭하다고 생각한다.

profile
진화중인 돌리입니다 :>

1개의 댓글

comment-user-thumbnail
2023년 7월 24일

잘 봤습니다. 좋은 글 감사합니다.

답글 달기