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를 쓰지 않고도 이정도면 훌륭하다고 생각한다.
잘 봤습니다. 좋은 글 감사합니다.