如何使用 Mongodb、socket.io 和 NextJS14 创建简单的群聊应用程序

过去几天,我一直在开发一个家庭使用的任务应用程序,需要一个聊天功能。我的技术栈包括用于前端和后端的 Next.js、用于实时通信的 Socket.io、用于推送通知的 Knock 和用于即时通知的 React Toast。虽然我一开始很难找到将 Socket.io 与 MongoDB 集成的资源,但我最终还是想出了办法,所以这篇文章可能会对其他人有所帮助。

本文假定读者熟悉使用 Next.js 和/或 Express 进行全栈开发。许多必要的信息可以在 Socket.io 的文档中找到。

首先,安装 Socket.io 和 Socket 客户端,它允许我们与后端进行交互。我们还将安装 Mongoose 以使用 MongoDB。

npm i Socket.io mongooose mongodb Socket.io-client

现在,在根目录下创建一个新文件夹,命名为 server,然后创建一个文件并命名为 index.js。您可以在模型中创建一个消息文件,它将存储我们的简单聊天消息模式。

root directory/server/index.js
models/Messages.js

构建一个简单的信息模式,看起来像这样,但您可以根据自己的要求随意编辑。

import mongoose from "mongoose"
const {Schema, model,models} = mongoose
const MessageSchema = new Schema({

    creator:{
        type:string,
        required:true
      }
      message: {
      type: String,
      required: true,
    },
    roomId: {
      type: String,
      required: true,
    },
      { timestamps: true }
  },
export const Messsages = models.Messages || model("Messages, MessageSchema)

在项目的这一部分,我们为消息设置了一个模式。模式包括创建者 ID(用于识别发送者)、消息正文字段和房间 ID(用于识别群聊)。然后,我们导出模式,并在数据库中创建一个新的消息正文。

接下来,为前端创建客户端。

"use client"
import { useSession } from 'next-auth/react';
const [messages, setMessages] = useState<string[]>([]);
import { useState, useEffect, useRef } from 'react';
import io, { Socket } from "socket.io-client";

const MessageForm = () => {
    const [currentMessage, setCurrentMessage] = useState('');
    const [users, setUsers]= useState([])
    const inputRef = useRef(null)
    const { data: session } = useSession()

    //@ts-ignore
    const userId = session?.user?.id
    //@ts-ignore
    const role = session?.user?.role

    // Create a ref for the socket connection
    const socketRef = useRef<Socket | null>(null);
    // console.log(socketRef.current?.id);

    useEffect(() => {
      const fetchUsers=async()=>{
            const res = await fetch("apilinktousers")
              if(!res){
                    console.log("failed to fetch users")}
      }
          const data = await res.json()
            setKids(data)
    }, [userId, role])

    // console.log(state.data)
    const groupRoomId = role === "creator" ? userId : users?.[0]?.creator
    console.log(groupRoomId);

    useEffect(() => {
        // Initialize the socket connection once
        socketRef.current = io("http://localhost:8080");
        // Join the family room
        // socketRef.current.emit('join-room', familyRoomId, userId);

        // Set up the event listener for receiving messages
        socketRef.current.on("receive-message", (message: string,) => {
            console.log("message", message);
            setMessages((prevMessages) => [...prevMessages, message]);
        });

        // Cleanup function to remove the event listener and disconnect the socket
        return () => {
            if (socketRef.current) {
                // socketRef.current.off("receive-message");
                socketRef.current.disconnect();
            }
        };
    }, []);
 useEffect(() => {
        const fetchMessages = async () => {
            try {
                const res = await fetch(`api/messages?userId=${userId}`);
                // console.log(res);
                if (!res.ok) {
                    throw new Error("Failed to fetch messages");
                }
                const data = await res.json();
                setMessages(data);
            } catch (error) {
                console.error("Error fetching messages:", error);
                // Handle the error, e.g., show a notification to the user
            }
        }
        fetchMessages()
    }, [userId, familyRoomId])
    const joinRoomR = () => {
        if (userId && groupRoomId) {
            socketRef.current.emit("join-room", userId, groupRoomId);
        } else {
            console.log("User or Room ID is missing");
        }
    }
    // Function to handle sending messages
    const sendMessage = async () => {
        // Check that there is a nonempty message and socket is present
        if (socketRef.current && currentMessage && groupRoomId && userId) {
            await socketRef.current.emit("send-message", currentMessage, groupRoomId, userId);
            setCurrentMessage("")
        }
    };



    return (
        <div>

            <div>
                {messages?.map((message, index) => (
                    <p key={index}>{message}</p>
                ))}

                {/* <MessageCard userId={familyRoomId} /> */}
            </div>
            {/* Input field for sending new messages */}
            <input
                type="text"
                value={currentMessage}
                placeholder='messages'
                onChange={(e) => setCurrentMessage(e.target.value)}
                className='border border-gray-800 '
            />
            <input
                className='border border-gray-800 '
                type="text" ref={inputRef}
                placeholder='room name'
                //@ts-ignore
                value={inputRef.current?.value ? inputRef.current.value : ""}
            />

            {/* Button to submit the new message */}
            <div className='flex justify-around items-center'>
                <button onClick={sendMessage}>Send</button>
                <button onClick={joinRoomR}>Join Room</button>
            </div>
        </div>
    );
}

export default MessageForm

接下来,进入 Server.js 文件。首先,从相应的文件夹中导入消息模式。现在,socketRef 将作为套接字连接的引用。使用 NextAuth 中的会话,将获得当前用户的 ID。然后,将 groupRoomId 分配给该组的创建者,以确保不同用户的一致性,因为该字段已附加到他们的模式中。

连接到 Socket.io 后,从 MongoDB 获取所有消息并显示给用户。我们还创建了一个函数,用于向后端发送消息、groupRoomId 和 userId。该函数对于创建房间和识别消息发送者非常必要。

import {Messages} from diretory/models/messages
import {Server} from "socket.io"
import {mongoose} from "mongoose"

//initializing our server
//8080 is our server port 

const server = new Server(8080,{
     cors: {
    origin: ["http://localhost:3000"],
  })

//we then map our users to the group
const userRooms = {}

//this will allow any one to join the group chat
server.on("connection", (socket)=>{
  socket.on("join-room",(userId, groupRoomId)=>{
//we check if the userId and groupId is present
          if( userId && groupId){
            socket.join(groupId)
          console.log(`User ${userId} joined room ${groupRoomId}`);

           // Map the user to the room
          userRooms[socket.id] = groupRoomId;
        }
    })

socket.on("send-message", async(userId, groupRoomId,Message)=>{
    //we set the mongoose and connect to dp
    //NB, its better to set the connection else where, but im aiming for simplicity
     mongoose.set("strictQuery", true);
  
     const mongoUri =process.env.MONGO_URI
      if (!mongoUri) {
        console.log("Mongo uri isnt define");
      }

      await mongoose.connect(`${mongoUri}`, {
        dbName: "dbname",
        bufferCommands: false, // Disable command buffering
        socketTimeoutMS: 10000,

    // we save the passed message to the message scema
      const newMessage = new Messages({
            roomId:groupRoomId,
            message
            creator: userId
        }

        newMessage.save()
      if(roomId && userRooms[socket.id] === groupRoomId) {
          //this will send messages to all the group memebers
            socket.to(groupRoomId).emit("receive-message", message);
  
              //this will send message to the sender
            socket.emit("receive-message", message);
        }
}

}

在后端,首先连接到 Socket.io。检查是否提供了 userId 和 groupRoomId;如果提供了,我们会允许用户加入房间,并映射套接字 ID 以方便识别。接下来,我们创建一个异步函数来接收来自客户端的消息,并将其存储到 MongoDB 实例中。

存储消息后,我们将其发送给所有房间成员,包括发送者。

作者:Abusomwan Santos

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/47216.html

(0)

相关推荐

发表回复

登录后才能评论