Code and Life

Programming, electronics and other cool tech stuff

Supported by

Supported by Picotech

WebSocket Magic: Create a Simple Server and Client in Go and JavaScript

Title image, generated with Stable Diffusion

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:

  1. Serve a simple WebSocket echo server at /echo
  2. Serve static files from static subfolder – essentially other addresses including / will try content from there. We'll put index.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:

  1. Upgrade the connection into a WebSocket one with websocket.Accept
  2. Log errors and defer connection close in case of errors
  3. 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!