Create Your Own Chat App with Golang

Create Your Own Chat App with Golang

In this blog we will get straight to building chat app using websockets in golang and react. If you want to learn about web sockets and how to use it in golang then read this beginners guide to ws

Building chat server

Main function

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func main() {
    // this http request is initially sent by client to connect to websocket connection
    http.HandleFunc("/ws", SocketHandler)
    fmt.Println("web server running on port 8000")
    err := http.ListenAndServe(":8000", nil)
    if err != nil {
        fmt.Println(err)
    }
}

// socket handler handles the messageing form respective client's websocket
func SocketHandler(w http.ResponseWriter, r *http.Request) {
    // an upgrader takes in clients conn info and converts it to websocket connection
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("there was a connection error : ", err)
        return
    }
    // this function will run at the end of function
    // to close the connection for that client
    defer ws.Close() 

    for {
        _, bytes, err := ws.ReadMessage()
        if err != nil {
            handleDisconnection(ws) // removing connection from our ws pool
            break
        }
        err1 := handleIncomingMessage(ws, bytes) // this handles all the messages sent by user
        if err1 != nil {
            log.Print("Error handling message", err1)
        }
    }
}

Websocket Pools

// this pool/map stores the connectionIds and its respective usernames
// connId/ws is just the clients address
// this can be used to check if user already exists and while disconnection of client ws
var WebSocketToUsername = make(map[*websocket.Conn]string)

// this pool/map can be used to get the address of user we want to send our message to
// with which we can directly send that message to the respective client
var UsernameToWebSocket = make(map[string]*websocket.Conn)

Disconnect function

func handleDisconnection(sender *websocket.Conn) {
    // this function removes the client from our websocket pools
    user_id, _ := WebSocketToUsername[sender]
    delete(WebSocketToUsername, sender)
    delete(UsernameToWebSocket, user_id)
}

Message structure

// this is the api structure that we are using to communicate between client and server
type msg struct {
    MessageType string // tells us the type of msg. ex ChatMsg, LoginMsg
    Data        string // the actual message by user
    Reciever    string
    Sender      string
}

const ChatMsg = "chatmsg"
const LoginMsg = "loginmsg"

Message handler

// this function takes in all the messages sent by client and does appropriate action
func handleIncomingMessage(sender *websocket.Conn, data []byte) error {
    // converting data into json
    var DataRecieved msg
    err := json.Unmarshal(data, &DataRecieved)
    if err != nil {
        return err
    }

    // checking the msg type and doing appropriate action on it
    switch DataRecieved.MessageType {
    case ChatMsg:
        // How this works is, 
        // the username of reciever will be in the msg so we take that username
        // and check for its ws conn using UsernameToWebSocket map
        // and forward that message to that connection
        UsernameToWebSocket[DataRecieved.Reciever].WriteJSON(DataRecieved)

        // you can also just send the msg only like this
        // .WriteMessage(1, []byte(DataRecieved.Data)) 

    case LoginMsg:
        // To login a user we must first check if that username is already in the pool
        // if it exists we will just return the error msg
        if _, ok := UsernameToWebSocket[DataRecieved.Sender]; ok {
            sender.WriteJSON("User already exists")
            return nil
        }
        // if the username is not in the pool then we will add it 
        WebSocketToUsername[sender] = DataRecieved.Sender
        UsernameToWebSocket[DataRecieved.Sender] = sender
    }
    return nil
}

Testing using postman

logging in user with username tan

logging in user with username yan

sending a message to tan from yan

message received to tan

And there you have it!

Let me know if theres any improvement that can be done in explanation. And thanks for reading.