Building a Chat Web Application; Implementing Web Socket

Grey Minimalist Hello Facebook Cover (2).png

A Web socket is useful when you want real time communication. Ideally, you can say it is required in the event of real time data exchange from client - server. The communication is bidirectional and would be kept alive until either of the client or the server terminates the connection. On this episode, I will explain the steps I used to integrate Web sockets with my Web application. I have been doing a lot of reading, trying to understand the concepts and how it works before I implement it. What's a chat application without a Web socket though? You absolutely do not want to endlessly reload your page in anticipation of a new message, That is bad for UX. You want to instantly receive that message from your friend and send your replies too without delay.

Initially, I thought the server takes time to save data to the database and send back a response, so would the socket speed up this process? Apparently, it is not bothered about all those. While you are saving your data and sending the response back to a user, the socket is receiving those requests in real time and emitting events simultaneously.

Websocket can be installed by running

npm i websocket

SERVER

Then the first set of logic would be done on our server. The module is required and passed to a constant

const socket = require("socket.io")


Then the socket is initialized

const io = socket(server, { 
     cors: { 
      origin: `http://localhost:3000`, 
      credentials:true 
     }         
  })  
   

'server' is our app that listens on a predefined PORT, in this case 5000. So socket needs to take in that variable and also an object of cors. This will ALLOW requests coming from our frontend, mine is running on http://localhost:3000. If the cors is not set, the request will not go through.

global.onlineUsers = new Map() 
  io.on("connection", (socket)=> { 
     global.chatSocket = socket 
     socket.on("newUser", (userId)=> { 
         onlineUsers.set(userId, socket.id) 
        
       })


We set the javascript global object and add a new Map called onlineUsers. Map is an ES6 collection type that holds key-value pairs. Items in a map are unique with their respective keys, and they are iterated in the order of insertion. The map is initially empty but it is populated once there is a connection and there is a new User Event (This will be Emitted later after the user Logs In). The user Id comes from the frontEnd and the socket.Id is generated automatically by the socket.

socket.on("send-msg", (data)=> {
  const sendUserSocket = onlineUsers.get(data.to) 
 if (sendUserSocket) {    
 socket.to(sendUserSocket).emit("msg- 
 received", data.message)
}

We also listen to the "send-msg" event to let the server know when a message has been sent. We get the data, query our onlineUsers to know who the message is intended for and emit a new socket to the user along with the message.

socket.on("disconnect", (userId)=> { 
     onlineUsers.delete(userId)
      
   }) 


Clear the map when the user is disconnected.

FRONTEND

const socket = useRef()

A variable is created in the chat page with the useRef hook. The useRef hook is used because we do not need our component to re-render during socket transmissions

useEffect(()=> {
    if (user) { 
       socket.current = io("http://localhost:5000") 
         socket.current.emit("newUser", user._id)        
     }
},[])


The user and the user. _id are coming from my redux state. When there is a user it emits the newUser event to the server and the server adds the user. _id to the global.onlineUsers Map.

const sendChat = (e)=> {
              socket.current.emit("send-msg", { 
                   to: recipient._id,
                   from: user._id, 
                   message: message 
                                 chat = [...chat]; 
               chat.push({fromSelf:true, message: message}) 
               dispatch(addMessage(chat))  
               
               setMessage("");
               }) 
} 

In the function that sends our message, we emit the "send-msg" event to the server along with the recipient id, my Id and the message.

***Kindly note that this not the only logic in my sendChat function. I truncated it for readability. I only extracted the socket functionality

useEffect(()=> {  
            if (socket.current){ 
             socket.current.on("msg-received", (message)=> { 
                 chat = [...chat] 
                chat.push({fromSelf:false, message})       
                dispatch(addMessage(chat))                
             } ) 
         } 
     },[chat])



Finally, this useEffect hook listens to the msg-received event, takes the message and pushes it to the state. This will instantly update the page with the new message.

We can also improve this further by listening to typing events

const handleTyping =() => { 
       socket.current.emit("typing", recepient.id)
   } 

Handle typing is a function that is fired by the onKeyDown Props on the input element.

On the server, we can listen to this typing event

socket.current.on("typing", (recipientId)=> {  
        const sendUserSocket = onlineUsers.get(recipientId) 
        if (sendUserSocket) {    
  socket.to(sendUserSocket).emit("typing")
    })

Then in the chat Page we can include a new socket event that listens to the typing event

  useEffect(()=> {
socket.current.on("typing"), () => {
setTyping(true) 
} 
}, [])

In the next series, we would be implementing video call with Web RTC

Cover Image Designed with Canva
Follow for more ❤️

H2
H3
H4
3 columns
2 columns
1 column
1 Comment
Ecency