WebSockets provide a full-duplex communication channel over a single TCP connection, enabling real-time communication between clients and servers. In this article, we’ll build an anonymous chat server in Golang using the net/http
and golang.org/x/net/websocket
packages, and we’ll style the chat interface using Tailwind CSS.
Prerequisites
Ensure you have Go installed on your system. You can download it from here. Also, make sure you have tailwindcss
installed or you can link it via CDN.
Setting Up the Project
Let’s start by setting up the project structure and installing the necessary dependencies.
Create a new directory for your project and initialize it as a Go module:
mkdir chat
cd chat
go mod init chat
Next, create the following files within the project directory:
main.go
: This file will contain the Golang server code.index.html
: This file will contain the HTML code for the chat interface.go.mod
: This file specifies the module’s name and its dependencies.
main.go
package main
import (
"fmt"
"io"
"net/http"
"golang.org/x/net/websocket"
)
// Chat Server with connection pool
type Server struct {
connections map[*websocket.Conn]string
}
// Create New Server which holds WebSocket connections
func NewServer() *Server {
return &Server{
connections: make(map[*websocket.Conn]string),
}
}
// Listen on WebSocket's for messages
func (s *Server) listen(name string, chatWS *websocket.Conn) {
// Buffer for fetching chat data
buffer := make([]byte, 1024)
for {
// Read connection in buffer
dataLength, err := chatWS.Read(buffer)
if err != nil {
// If WebSocket is terminated
if err == io.EOF {
break
}
// Log Read error and continue listening
fmt.Println("Read Error:", err)
continue
}
msg := string(buffer[:dataLength])
fmt.Println(name+":", msg)
// Broadcast message
msgJson := "{\"name\": \"" + name + "\", \"message\": \"" + msg + "\"}"
s.broadcast([]byte(msgJson))
}
}
// Broadcast Message to all users/connections
func (s *Server) broadcast(data []byte) {
// Loop all connections
for ws, name := range s.connections {
// Start new process to send message to connection
go func(ws *websocket.Conn, name string) {
if _, err := ws.Write(data); err != nil {
fmt.Println("Write Error ("+name+"): ", err, " -> closing connection")
// Terminate & Delete connection if closed
ws.Close()
delete(s.connections, ws)
}
}(ws, name)
}
}
// Initiate Chat Websocket Connection
func (s *Server) handleChatWS(chatWS *websocket.Conn) {
// Get Name of User from Parameters
urlParams := chatWS.Request().URL.Query()
name := urlParams.Get("name")
fmt.Println("New Connection: ", name+" ("+chatWS.RemoteAddr().String()+")")
// Add websocket connection to server pool
s.connections[chatWS] = name
// Start listening on socket
s.listen(name, chatWS)
}
func main() {
server := NewServer()
// Load Chat Page
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
// Handle Chat WebSocket's
http.Handle("/chatWS", websocket.Handler(server.handleChatWS))
// Start Server
fmt.Println("Server listening on :8000")
http.ListenAndServe(":8000", nil)
}
Run go get
which will load golang.org/x/net
into the project go.mod
.
index.html
<!doctype html>
<html>
<head>
<title>Golang Tailwind Chat</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body translate="no">
<div class="max-w-screen-md mx-auto w-full flex flex-col h-screen max-h-screen overflow-hidden" id="chat">
<div id="messagesDiv" class="messages flex-1 overflow-y-scroll border-box">
<div class="inline-flex items-center justify-center w-full">
<hr class="w-96 h-px my-8 border-0 bg-gray-300">
<span id="chatTitle" class="absolute px-3 font-medium text-center -translate-x-1/2 left-1/2 text-grey bg-white">
Golang Tailwind Chat
</span>
</div>
</div>
<div class="message-input bg-gray-100 p-4 flex flex-row">
<input id="chatMsg" class="flex-1 p-2 rounded" type="text" placeholder="Write message and press enter" />
</div>
</div>
<script>
// DOM elements
var msgField = document.getElementById("chatMsg");
var messagesDiv = document.getElementById('messagesDiv');
var chatTitle = document.getElementById('chatTitle');
// Random Name List
var nameList = [
'Time', 'Past', 'Future', 'Dev',
'Fly', 'Flying', 'Soar', 'Soaring', 'Power', 'Falling',
'Fall', 'Jump', 'Cliff', 'Mountain', 'Rend', 'Red', 'Blue',
'Green', 'Yellow', 'Gold', 'Demon', 'Demonic', 'Panda', 'Cat',
'Kitty', 'Kitten', 'Zero', 'Memory', 'Trooper', 'XX', 'Bandit',
'Fear', 'Light', 'Glow', 'Tread', 'Deep', 'Deeper', 'Deepest',
'Mine', 'Your', 'Worst', 'Enemy', 'Hostile', 'Force', 'Video',
'Game', 'Donkey', 'Mule', 'Colt', 'Cult', 'Cultist', 'Magnum',
'Gun', 'Assault', 'Recon', 'Trap', 'Trapper', 'Redeem', 'Code',
'Script', 'Writer', 'Near', 'Close', 'Open', 'Cube', 'Circle',
'Geo', 'Genome', 'Germ', 'Spaz', 'Shot', 'Echo', 'Beta', 'Alpha',
'Gamma', 'Omega', 'Seal', 'Squid', 'Money', 'Cash', 'Lord', 'King',
'Duke', 'Rest', 'Fire', 'Flame', 'Morrow', 'Break', 'Breaker', 'Numb',
'Ice', 'Cold', 'Rotten', 'Sick', 'Sickly', 'Janitor', 'Camel', 'Rooster',
'Sand', 'Desert', 'Dessert', 'Hurdle', 'Racer', 'Eraser', 'Erase', 'Big',
'Small', 'Short', 'Tall', 'Sith', 'Bounty', 'Hunter', 'Cracked', 'Broken',
'Sad', 'Happy', 'Joy', 'Joyful', 'Crimson', 'Destiny', 'Deceit', 'Lies',
'Lie', 'Honest', 'Destined', 'Bloxxer', 'Hawk', 'Eagle', 'Hawker', 'Walker',
'Zombie', 'Sarge', 'Capt', 'Captain', 'Punch', 'One', 'Two', 'Uno', 'Slice',
'Slash', 'Melt', 'Melted', 'Melting', 'Fell', 'Wolf', 'Hound',
'Legacy', 'Sharp', 'Dead', 'Mew', 'Chuckle', 'Bubba', 'Bubble', 'Sandwich',
'Smasher', 'Extreme', 'Multi', 'Universe', 'Ultimate', 'Death', 'Ready', 'Monkey',
'Elevator', 'Wrench', 'Grease', 'Head', 'Theme', 'Grand', 'Cool', 'Kid', 'Boy', 'Girl',
'Vortex', 'Paradox'
];
// Get Random Name for chat
const name = nameList[Math.floor(Math.random() * nameList.length)];
// Update Chat Title for name
chatTitle.innerHTML = "Golang Tailwind Chat (" + name + ")";
document.title = "Chat: " + name;
// Initiate WebSocket
let socket = new WebSocket("ws://localhost:8000/chatWS?" + new URLSearchParams({ name: name }));
console.log("Connected to Chat WebSocket as", name);
socket.onmessage = (event) => {
console.log("Received: ", event.data);
data = JSON.parse(event.data);
// if message is from sender
if (data.name == name) {
messagesDiv.innerHTML += '<div class="message-row flex flex-row-reverse text-white">\
<div class="message m-2 p-4 bg-pink-500 rounded max-w-full inline relative shadow">\
<p class="message-content">'+ data.message + '</p>\
</div>\
</div>';
} else {
messagesDiv.innerHTML += '<div class="message-row flex flex-row">\
<div class="message m-2 p-4 pb-8 bg-gray-100 rounded max-w-full inline relative shadow">\
<p class="message-content">'+ data.message + '</p>\
<div class="message-name absolute bg-gray-300 px-2 py-1 text-xs rounded-bl rounded-tr left-0 bottom-0">'+ data.name + '</div>\
</div>\
</div>';
}
// Scroll to bottom
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// Add event listener for keypress to get chat message
msgField.addEventListener("keypress", function (event) {
// Check if the Enter key is pressed
if (event.keyCode === 13) {
// Retrieve the value of the input field
var msg = msgField.value;
// Send message to socket
socket.send(msg);
// Clear the input field (optional)
msgField.value = "";
}
});
</script>
</body>
</html>
Running the Server
To run the server, execute the following command in your terminal:
go run .
This will start the server on port 8000
.
Output:
$ go run .
Server listening on :8000
New Connection: Camel (http://localhost:8000)
New Connection: Duke (http://localhost:8000)
Camel: Hello there, I am Camel
Duke: Hi, I am Duke
Camel: Duke, Isn't Golang fun ?
Duke: Sure it is!
Duke: And Tailwind too
Accessing the Chat Interface
Open your web browser and navigate to http://localhost:8000. You should see the chat interface where you can enter messages and see them displayed in real-time.
Conclusion
In this article, we’ve built an anonymous chat server in Golang using WebSockets and styled the chat interface using Tailwind CSS. WebSockets provide a powerful mechanism for real-time communication between clients and servers, making them ideal for building chat applications and other real-time systems.
Happy coding! 🚀
References
- Build Chat And Data Feed With WebSockets In Golang by Anthony GG https://www.youtube.com/watch?v=JuUAEYLkGbM&t=631s
- Tailwind Chat - https://codepen.io/brookesb91/details/OJpdqOm