Simple Chat Application Using Golang

Simple Chat Application Using Golang

The purpose of this article is not to make a fully fledged chatting application but to introduce the concept of websockets.

I used the package github.com/gorilla/websocket for the project, since it is well maintained project and is used by many in the industry.

Another notable package for implementing websockets is the github.dev/nhooyr/websocket .

If you are just here for the codes, skip to the bottom of the page.


Why websocket ?

Imagine you're having a conversation with a friend across town. You wouldn't shout every minute, "Hey, any messages?" and wait for a reply, right? It's clunky, slow, and inefficient. That's the analogy of polling in online chat.

Traditional APIs work like this: you send a request, the server responds, and you have to ask again for updates. For chat, this creates a frustrating experience with delays and missed messages.

But what if you could have a direct connection, like a dedicated phone line, where messages are instantly delivered both ways? That's the power of WebSockets.

Think of WebSockets as live, two-way tunnels between your browser and the server. Messages flow instantly, without the need for constant requests. It's like having a direct line to your friend, where you both speak and hear in real-time.

Here's how it compares to polling:

Polling:

  • Slow: You wait for updates, creating delays and a laggy feel.

  • Inefficient: You send many requests, even if there are no messages.

  • Limited: Messages pile up and might be delivered in bursts, not instantly.

WebSockets:

  • Real-time: Messages arrive the moment they're sent, creating a smooth chat experience.

  • Efficient: Only one connection is needed, reducing server load and data usage.

  • Interactive: Both users can send and receive messages simultaneously.

Benefits beyond Chat:

WebSockets aren't just for chat. They power applications like:

  • Live score updates: Sports scores or stock prices change instantly, without refreshing the page.

  • Multiplayer games: Players react to each other's actions in real-time, creating a more immersive experience.

  • Collaborative editing: Multiple users work on documents simultaneously, seeing changes instantly.


Code

Directory Structure

Installation

  • Create a folder and start a new project

      go mod init chat-app-using-golang
    
  • Install dependencies

      go get github.com/gorilla/websocket
    

Files

main.go

package main

import (
    "log"
    "net/http"
    "strings"

    "github.com/gorilla/websocket"
)


// Create a map where key is username and value is a pointer to a WebSocket connection
var clients = make(map[string]*websocket.Conn)
// Specify the parameters to be used for upgrading HTTP connections to WebSocket connections
var upgrader = websocket.Upgrader{}

/*
main is the entry point of the program. It creates a WebSocket endpoint and a file server to serve static files. It starts the server on port 8080.
*/

func main() {
    // Create a WebSocket endpoint
    http.HandleFunc("/ws", wsHandler)

    /* Create a file server to serve static files
     Whenever a request is made to the root URL, the server will serve the index.html file
     from the static directory. 
     */
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "./static/index.html")
    })

    // Start the server on port 8080
    log.Println("Server started on port 8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

/*
wsHandler is a function that handles WebSocket connections. It reads the username from the client, allows the client to send messages, and broadcasts the messages to all other clients.
*/
func wsHandler(w http.ResponseWriter, r *http.Request) {
    // Upgrade the HTTP connection to a WebSocket connection
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Failed to upgrade to WebSocket:", err)
        return
    }
    defer conn.Close()

    // The client sends its username as the first message
    _, msg, err := conn.ReadMessage()
    if err != nil {
        log.Println("Failed to read username:", err)
        return
    }
    // Trim any leading and trailing whitespace from the username
    username := strings.TrimSpace(string(msg))

    // Map the username to the WebSocket connection
    clients[username] = conn
    // Log that the user has connected
    log.Printf("User '%s' connected", username)

    // Continuously read messages from the client and broadcast them to all other clients
    for {
        // Read a message from the client
        _, msg, err := conn.ReadMessage()
        if err != nil {
            // If the client disconnects, remove the client from the map and break the loop
            if err.Error() == "websocket: close 1001 (going away)" {
                log.Printf("User '%s' disconnected", username)
                delete(clients, username)
                break
            } else {
                log.Println("Error reading message:", err)
                break
            }
        }
        // Broadcast the message to all other clients
        broadcastMessage(username, string(msg))
    }
}

/*
broadcastMessage sends a message to all clients except the sender.
*/
func broadcastMessage(sender string, message string) {
    // Iterate over all clients
    for username, conn := range clients {
        // If the client is not the sender, send the message to the client
        if  username != sender{
            conn.WriteMessage(websocket.TextMessage, []byte(sender+": "+message))
        }
    }
}

index.html inside static folder

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
</head>
<body>
    <h1>WebSocket Chat</h1>
    <div id="chatOutput"></div>
    <input type="text" id="messageInput" placeholder="Type your message...">
    <button onclick="sendMessage()">Send</button>

    <script>
        const username = prompt("Enter your username:");
        // Use the WebSocket API to connect to the WebSocket server
        const ws = new WebSocket(`ws://localhost:8080/ws`);

        // Event listener for when the WebSocket connection is open
        ws.onopen = function(event) {
            ws.send(username);
        };

        // Event listener for when a message is received from the server
        ws.onmessage = function(event) {
            const message = event.data;
            const chatOutput = document.getElementById("chatOutput");
            const p = document.createElement("p");
            p.textContent = message;
            chatOutput.appendChild(p);
        };

        // Function to send a message to the server
        function sendMessage() {
            const messageInput = document.getElementById("messageInput");
            const message = messageInput.value.trim();
            if (message !== "") {
                ws.send(message);
                messageInput.value = "";
            }
        }
    </script>
</body>
</html>

Usage

go run .
  • Open the localhost:8080 in your browser

  • Asks for your username first and then you can chat with multiple persons. You can improve the code according to your logic

Did you find this article valuable?

Support Aswin Benny by becoming a sponsor. Any amount is appreciated!