WebSocket Magic: Create a Simple Server and Client in Go and JavaScript
WebSocket is a protocol that allows for real-time, bidirectional communication between a client and a server. It is often used in web applications to enable features such as chat, live updates, and multiplayer games.
In this tutorial, I will show you how to create a minimalistic WebSocket server using Go and the nhooyr websocket library, and a JavaScript client to test it out. You will learn how to handle WebSocket connections, send and receive messages, and close the connection when necessary.
By the end of this tutorial, you will have a working WebSocket server and client that you can use as a starting point for your own WebSocket-based applications.
Setting up the project
You should first set up a simple "Hello world" go project, something along the lines of this tutorial. After you have a project going, let's install nhooyr.io/websocket WebSocket library (Go's own seems deprecated and Gorilla development has ceased some years ago):
$ go get nhooyr.io/websocket
The whole system will consist of main.go
that will contain a simple net/http
server that will:
- Serve a simple WebSocket echo server at
/echo
- Serve static files from
static
subfolder – essentially other addresses including/
will try content from there. We'll putindex.html
under that subfolder.
Basic webserver stuff:
func main() {
address := "localhost:1234"
http.HandleFunc("/echo", echoHandler)
log.Printf("Starting server, go to http://%s/ to try it out!", address)
http.Handle("/", http.FileServer(http.Dir("static")))
err := http.ListenAndServe(address, nil)
log.Fatal(err)
}
Now the echoHandler
will do a few essential items:
- Upgrade the connection into a WebSocket one with
websocket.Accept
- Log errors and defer connection close in case of errors
- Loop forever (or actually 10 minutes in this sample), reading messages from the socket and writing them back.
Note that I've used InsecureSkipVerify
to accept connections from any
origin, you might want to modify the code for a tighter policy:
func echoHandler(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
InsecureSkipVerify: true,
})
if err != nil {
log.Println(err)
return
}
defer c.Close(websocket.StatusInternalError, "the sky is falling")
for {
// Create a context with a timeout of 1 second
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
defer cancel()
// Read a message from the client
_, message, err := c.Read(ctx)
if err != nil {
break
}
log.Printf("Received %v", message)
// Echo the message back to the client
err = c.Write(ctx, websocket.MessageText, message)
if err != nil {
break
}
}
}
You can get the full code with required imports from websocket.go. You should be able to place the
code in the project directory and use go run .
to run the example.
Simple Javascript client
Now we'll create a simple "Client" for our server at static/index.html
. It
simply has a input field where you can write a message, and pressing enter will send it over to the server. Response from server (echo) is logged onto console.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket test</title>
</head>
<body>
<input id="msg" type="text" placeholder="Write message here">
<p>Press enter to send a message to server.</p>
<script>
const input = document.getElementById('msg');
const socket = new WebSocket('ws://localhost:1234/echo');
socket.onmessage = (msg) => { console.log('Got message', msg); };
input.addEventListener('keydown', function(event) {
if (event.keyCode === 13) {
console.log(input.value);
socket.send(input.value);
}
});
</script>
</body>
</html>
Once you have this set up, you should be able to run the project and visit http://localhost:1234/ to try out the server!
Acknowledgements
To accelerate the creation of the sample code, I tried out ChatGPT. It worked quite well, but did not quite know how to call Read
and Write
methods properly. After a bit of hand-holding and mixing sufficed to get it work. I also prompted AI for that keydown
handling functionality as well as simple HTML template. Furthermore, introductory chapter in this article was (mostly) done by ChatGPT, and it provided me with a couple of suggestions for the headline. Truly amazing times!