简体   繁体   中英

Converting MERN chat app to real-time using socket.io

I've been trying to develop a realtime chat app using the MERN stack (React-Native instead of React) and was successful, but I cannot convert it to real time using the socket.io library. Below I have provided some code from my project that describes the situation/structure before using socket.io and after :

FLOW

A user registers/login using his phone number, on every first render of home screen, I will fetch the rooms, if the user has any. (rooms => conversations with people, like homescreen of whatsapp). Each room will have a unique roomId and data of these 2 users (refer to the Room schema). Now when he/she will tap to enter the ChatScreen , users can send message (refer Message schema) to eachother. To fetch all messages of a particular chat, I make use of the unique roomId to fetch all the messages having that roomId only. Now, the problem is, when other user sends any message, I have to re-render the whole app to get new messages, therefore, no real-timeness.

SERVER

I have 3 collections in my mongodb, 1) users , 2) rooms 3) messages .

schemas:

const RoomSchema = mongoose.Schema({
  roomId: String,
  usersId: [String],
  users: Object,
}, {
  timestamps: true,
});

module.exports = mongoose.model('Room', RoomSchema);

const MessageSchema = mongoose.Schema({
  roomId: String,
  senderId: String,
  text: String,
}, {
  timestamps: true,
});

module.exports = mongoose.model('Message', MessageSchema);

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,

  },
  phone: {
    type: String,
    required: true,
    trim: true,
    unique: true,
  },
  otp: String,
}, {
  timestamps: true,
});

module.exports = mongoose.model('User', UserSchema);

index.js

const express = require('express');
const mongoose = require('mongoose');
const http = require('http');
const socketio = require('socket.io');

const app = express();
const server = http.createServer(app);

const io = socketio(server).sockets;

mongoose.connect(process.env.MONGO_URL);
mongoose.connection.on('connected', () => {
  console.log('[INFO] Connected to MongoDB');
});
mongoose.connection.on('error', (error) => {
  console.log(`[ERROR] ${error}`);
});

// SOCKETS MIDDLEWARE
require('./middlewares/socket')(io);

server.listen(process.env.PORT, () => {
  console.log(`[INFO] Server running at ${process.env.PORT}`);
});

socket.js

const Message = require('../models/Message');

module.exports = (io) => {
  io.on('connection', (socket) => {
    console.log('A user connected.');

    socket.on('sent_messages', async ({ roomId }, cb) => {
      const messages = await Message.find({roomId});
      cb(messages);
    });

    socket.on('send2user', async (data) => {
      socket.broadcast.to(data.roomId).emit();

      const message = new Message({
        roomId: data.roomId,
        senderId : data.senderId,
        text: data.text,
      });
      const result = await message.save();
    });
  });
};

Now, basically, whenever a user touch on a contact item in frontend, a room will be created (chat room) for these 2 users (private 1-1 chat app). So, now the 2 users are ready to chat real time. The endpoints for fetching and creating messages (Although, I have created a new message in socket.js file but don't know how to proceed further):

router.post('/create_message', async (req, res) => {
  const {roomId, senderId, text} = req.body;
  try {
    const message = new Message({
      roomId,
      senderId,
      text,
    });
    const result = await message.save();

    return res.status(200).json({
      type: 'success',
      data: result,
    });
  } catch (error) {
    return res.status(422).send({error: `${error.message}`});
  }
});

router.post('/get_messages', async (req, res) => {
  const {roomId} = req.body;
  try {
    const messages = await Message.find({roomId});
    return res.status(200).json({
      type: 'success',
      data: messages,
    });
  } catch (error) {
    return res.status(422).send({error: `${error.message}`});
  }
});

FRONTEND

utility.js

export const socket = io(API_URL, {forceNew: true});

socket.on('connection', () => {
  console.log('Connected to server');
});

export const fetchMessages = (roomId, setMessages) => {

  // socket.emit('sent_messages', {roomId}, (data) => {
  //   setMessages(data);
  // });

  AsyncStorage.getItem('token')
    .then(token => {
      if (token) {
        fetch(`${API_URL}/message/get_messages`, {
          method: 'POST',
          headers: {
            ...
          },
          body: JSON.stringify({roomId}),
        })
          .then(response => response.json())
          .then(data => {
            if (data.type === 'success') {
              setMessages(data.data);
            }
            if (data.error) {
              console.log(data.error);
            }
          })
          .catch(error => {
            console.log('[ERROR] While fetching messages: ' + error.message);
          });
      } else {
          console.log('token is null');
      }
    })
    .catch(error => {
      console.log('[ERROR] While fetching token: ' + error.message);
    });
};

export const createMessage = (message, setMessages) => {
  AsyncStorage.getItem('token')
    .then(token => {
      if (token) {
        fetch(`${API_URL}/message/create_message`, {
          method: 'POST',
          headers: {
            ...
          },
          body: JSON.stringify(message),
        })
          .then(response => response.json())
          .then(data => {
            if (data.type === 'success') {
              const latestMessage = data.data;
              setMessages((prevMessages) => ([
                ...prevMessages,
                latestMessage,
              ]));
              // socket.emit('send2user', latestMessage);
            }
            if (data.error) {
              console.log(data.error);
            }
          })
          .catch(error => {
            console.log('[ERROR] While fetching messages: ' + error.message);
          });
      } else {
          console.log('token is null');
      }
    })
    .catch(error => {
      console.log('[ERROR] While fetching token: ' + error.message);
    });
};

ChatScreen.js

const ChatScreen = () => {
  const {params} = useRoute();
  const roomId = params?.roomId;
  const navigator = useNavigation();
  const {user, rooms} = useAuth();

  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');

  const sendMessage = () => {
    if (input) {
      const message = {
        roomId,
        senderId: user._id,
        text: input,
      };
      createMessage(message, setMessages);
      setInput('');
    }
  };

  useEffect(() => {
    fetchMessages(roomId, setMessages);
  }, []);

  const scrollViewRef = useRef();

  return (
    <SafeAreaView>
      <KeyboardAvoidingView>
        <>
          {/* RENDER MESSAGES WITH SCROLLVIEW */}
          <ScrollView
            ref={scrollViewRef}
            onContentSizeChange={() =>
              scrollViewRef.current.scrollToEnd({animated: true})
            }
            onLayout={() =>
              scrollViewRef.current.scrollToEnd({animated: true})
            }>
            {messages.length > 0 ? (
              messages.map((message, index) => (
                <MessageItem key={index} myID={user._id} data={message} />
              ))
            ) : (
              <Text>Start Chatting</Text>
            )}
          </ScrollView>

          <View>
            <View>
              <TextInput
                value={input}
                onChangeText={setInput}
                placeholder="Type here"
              />
            </View>

            <TouchableOpacity
              onPress={sendMessage}>
              <IonIcon name="ios-add" size={28} color="#fff" />
            </TouchableOpacity>
          </View>
        </>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

export default ChatScreen;

So, as you can see, I cannot make it real time using socket.io and I'm also confused whether to fetch/create messages using socket or api endpoint. If someone could help me in this problem, I would really appreciate it. I just want to make this work like a real time chat app using socketio.

In frontend, you're using API to send a message instead of that use Socket on and emit to be real time. Since you're using API so chat will update only if it's refreshed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM