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

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

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일

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

답글 달기