This tutorial is out of date and no longer maintained.
Modern web applications are becoming more and complex. Users are often greeted with an experience that is both reactive and engaging. Pages update in real time without the user having to initiate calls to the server or refreshing their browser. In the early days, developers relied on AJAX requests to create applications that were pretty close to realtime. Now they’re able to use the power of WebSockets to create fully realtime applications.
In this tutorial we’ll create a realtime chat application using the Go programming language and WebSockets. The frontend will be written using HTML5 and Vue.js. A basic understanding of the Go language, JavaScript and HTML5 are assumed. It’s also assumed that you have a little bit of experience working with Vue.js.
To get started with Go, you can check out the excellent interactive tutorial on the official Go website
And for Vue you can check out the excellent free video series by Jeffrey Way at Laracasts.
Normal web applications are served up using at least one or more requests to an HTTP server. A piece of client software, normally a web browser sends a request to the server and the server sends back a response. The response is usually HTML which the browser then renders as a web page. Stylesheets, JavaScript code and images can also be sent back in a response to complete the entire web page. Each request and response is part of its own separate connection and a large website like Facebook can actually yield hundreds of these connections just to render a single page.
AJAX works the exact same way. Using JavaScript, developers can make requests to the HTTP server for little pieces of information and then update a single part of the webpage based on the response. This can all be done without having to refresh the browser. This still has some limitations though.
Each HTTP request/response connection is closed after the response and to get any new information another connection must be made. The server has no idea the client is looking for new information without a new request to tell it so. One technique to make AJAX applications seem realtime is to run AJAX requests in a timed loop. After a set interval, the application can rerun a request to the server to see if there has been any update which needs to be reflected in the browser. This works fine for small applications but is not very efficient. This is where WebSockets come in handy.
WebSockets are part of a prosposed standard created by the Internet Engineering Task Force (IETF). The full specification of how WebSockets should be implemented are detailed in RFC6455. Here is how the document defines a WebSocket.
The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.
In other words, a WebSocket is a connection that is always open and allows a client and server to send messages back and forth unprompted. The server can push new information to the client whenever it deems it necessary and the client can do the same to the server.
Most modern browsers have support for WebSockets in their JavaScript implementation. To initiate a WebSocket connection from the browser you can use the simple WebSocket JavaScript object like this.
var ws = new Websocket("ws://example.com/ws");
The only argument you need is a URL to where the WebSocket connection is going to be accepted by the server. The request is actually an HTTP request initially but we use ws://
or wss://
for a secure connection. This lets the server know that we are trying to create a new WebSocket connection. The server will then “upgrade” the connection to a persistent two-way connection between the client and server.
Once a new WebSocket object is created and the connected is successfully created we can use the send()
method to send text to the server and define a handler function on our WebSocket’s onmessage
property to do something with messages sent from the server. This will be explained later in our chat application code.
WebSockets are not included as part of the Go standard library but thankfully there are a few nice third-party packages that make working with WebSockets a breeze. In this example we will use a package called gorilla/websocket
which is part of the popular Gorilla Toolkit collection of packages for creating web applications in Go. To install it, simply run the following.
- go get github.com/gorilla/websocket
The first piece of this application is going to be the server. It will be a simple HTTP server that handles requests. It will serve up our HTML5 and JavaScript code as well as complete the setup of WebSocket connections from clients. Going a step further, the server will also keep track of each WebSocket connection and relay chat messages sent from one client to all other clients connected by WebSocket. Start by creating a new empty directory then inside that directory, create a src
and public
directory. Inside the src
directory create a file called main.go
.
The first part of the application is some setup. We start our application like all Go applications and define our package namespace, in this case main
. Next we import some useful packages. log
and net/http
are both part of the standard library and will be used to log and create a simple HTTP server. The final package, github.com/gorilla/websocket
, will help us easily create and work with our WebSocket connections.
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
The next two lines are some global variables that will be used by the rest of the app. Global variables are usually a bad practice but we will use them this time for simplicity. The first variable is a map where the key is actually a pointer to a WebSocket. The value is just a boolean. The value isn’t actually needed but we are using a map because it is easier than an array to append and delete items.
The second variable is a channel that will act as a queue for messages sent by clients. Later in the code, we will define a goroutine to read new messages from the channel and then send them to the other clients connected to the server.
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
Next we create an instance of an upgrader. This is just an object with methods for taking a normal HTTP connection and upgrading it to a WebSocket as we’ll see later in the code.
// Configure the upgrader
var upgrader = websocket.Upgrader{}
Finally we’ll define an object to hold our messages. It’s a simple struct with some string attributes for an email address, a username and the actual message. We’ll use the email to display a unique avatar provided by the popular Gravatar service.
The text surrounded by backticks is just metadata which helps Go serialize and unserialize the Message object to and from JSON.
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
}
The main entry point of any Go application is always the main()
function. The code is pretty simple. We first create a static fileserver and tie that to the /
route so that when a user accesses the site they will be able to view index.html
and any assets. In this example we will have an app.js
file for our JavaScript code and a simple style.css
for any styling.
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("../public"))
http.Handle("/", fs)
The next route we want to define is /ws
which is where we will handle any requests for initiating a WebSocket. We pass it the name of a function called handleConnections
which we will define later.
func main() {
...
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
In the next step we start a goroutine called handleMessages
. This is a concurrent process that will run along side the rest of the application that will only take messages from the broadcast channel from before and the pass them to clients over their respective WebSocket connection.
Concurrency in Go is one of it’s greatest features. How it all works is beyond the scope of this article but you can check it out in action for yourself on Go’s official tutorial site.
Just think of concurrent processes or goroutines as backround processes or asynchronous functions if you’re familiar with JavaScript.
func main() {
...
// Start listening for incoming chat messages
go handleMessages()
Finally, we print a helpful message to the console and start the webserver. If there are any errors we log them and exit the application.
func main() {
...
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8000")
err := http.ListenAndServe(":8000", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Next we need to create the function to handle our incoming WebSocket connections. First we use the upgrader’s Upgrade()
method to change our initial GET request to a full on WebSocket. If there is an error, we log it but don’t exit. Also take note of the defer statement. This is neat way to let Go know to close out our WebSocket connection when the function returns. This saves us from writing multiple Close()
statements depending on how the function returns.
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
Next we register our new client by adding it to the global clients
map we created earlier.
func handleConnections(w http.ResponseWriter, r *http.Request) {
...
// Register our new client
clients[ws] = true
The final piece is an inifinite loop that continuously waits for a new message to be written to the WebSocket, unserializes it from JSON to a Message object and then throws it into the broadcast channel. Our handleMessages()
goroutine can then take it can send it to everyone else that is connected.
If there is some kind of error with reading from the socket, we assume the client has disconnected for some reason or another. We log the error and remove that client from our global “clients” map so we don’t try to read from or send new messages to that client.
Another thing to note is that HTTP route handler functions are run as goroutines. This allows the HTTP server to handle multiple incoming connections without having to wait for another connection to finish.
func handleConnections(w http.ResponseWriter, r *http.Request) {
...
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
The final piece of the server is the handleMessages()
function. This is simply a loop that continuously reads from the broadcast
channel and then relays the message to all of our clients over their respective WebSocket connection. Again, if there is an error with writing to the WebSocket, we close the connection and remove it from the clients
map.
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
A chat application wouldn’t be complete without a pretty UI. We’ll create a simple, clean interface using some HTML5 and Vue.js. We’ll also take advantage of some libraries like Materialize CSS and EmojiOne for some styling and emoji goodness. Inside the public
directory, create a new file called index.html
.
The first piece is pretty basic. We also pull in some stylesheets and fonts to make everything pretty. style.css
is our own stylesheet to customize a few things.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple Chat</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/emojione/2.2.6/assets/css/emojione.min.css"/>
<link rel="stylesheet" href="/style.css">
</head>
The next piece is just the interface. It’s just a few fields to handle choosing a username and sending messages along with displaying new chat messages. The details of working with Vue.js are beyond the scope of this article but check out the documentation if you’re unfamiliar.
<body>
<header>
<nav>
<div class="nav-wrapper">
<a href="/" class="brand-logo right">Simple Chat</a>
</div>
</nav>
</header>
<main id="app">
<div class="row">
<div class="col s12">
<div class="card horizontal">
<div id="chat-messages" class="card-content" v-html="chatContent">
</div>
</div>
</div>
</div>
<div class="row" v-if="joined">
<div class="input-field col s8">
<input type="text" v-model="newMsg" @keyup.enter="send">
</div>
<div class="input-field col s4">
<button class="waves-effect waves-light btn" @click="send">
<i class="material-icons right">chat</i>
Send
</button>
</div>
</div>
<div class="row" v-if="!joined">
<div class="input-field col s8">
<input type="email" v-model.trim="email" placeholder="Email">
</div>
<div class="input-field col s8">
<input type="text" v-model.trim="username" placeholder="Username">
</div>
<div class="input-field col s4">
<button class="waves-effect waves-light btn" @click="join()">
<i class="material-icons right">done</i>
Join
</button>
</div>
</div>
</main>
<footer class="page-footer">
</footer>
The last piece is just importing all the required JavaScript libraries to inclue Vue, EmojiOne, jQuery, and Materialize. We also need an MD5 library to grab URLs for avatars from Gravatar. This will be explained better when we tackle the JavaScript code. The last import, app.js
, is our custom code.
<script src="https://unpkg.com/vue@2.1.3/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/emojione/2.2.6/lib/js/emojione.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js"></script>
<script src="/app.js"></script>
</body>
</html>
Next create a file called style.css
in the public
directory. Here we’ll just place a little bit of styling.
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
#chat-messages {
min-height: 10vh;
height: 60vh;
width: 100%;
overflow-y: scroll;
}
The final part of the client is our JavaScript code. Create a new file in the public
directory called app.js
.
As with any Vue.js application we start by creating a new Vue object. We mount it to a div with the id of #app
. This allows anything within that div to share scope with our Vue instance. Next we define a few variables.
new Vue({
el: '#app',
data: {
ws: null, // Our websocket
newMsg: '', // Holds new messages to be sent to the server
chatContent: '', // A running list of chat messages displayed on the screen
email: null, // Email address used for grabbing an avatar
username: null, // Our username
joined: false // True if email and username have been filled in
},
Vue provides an attribute called created
which is meant to be a function you define that handles anything you want to as soon as the Vue instance has been created. This is helpful for any setup work you need to do for the application. In this case we want to create a new WebSocket connection with the server and create a handler for when new messages are sent from the server. We store the new WebSocket in our ws
variable created in the data
property.
The addEventListener()
method takes a function that will be used to handle incoming messages. We expect all messages to be a JSON string so we parse it so that it is an object literal. Then we can use the different properties to format a pretty message line complete with an avatar. The gravatarURL()
method will be explained later. Also we are using a nifty library called EmojiOne to parse emoji codes. The toImage()
method will turn those emoji codes into actual images. For example if you type :robot:
it will be replaced with a robot emoji.
created: function() {
var self = this;
this.ws = new WebSocket('ws://' + window.location.host + '/ws');
this.ws.addEventListener('message', function(e) {
var msg = JSON.parse(e.data);
self.chatContent += '<div class="chip">'
+ '<img src="' + self.gravatarURL(msg.email) + '">' // Avatar
+ msg.username
+ '</div>'
+ emojione.toImage(msg.message) + '<br/>'; // Parse emojis
var element = document.getElementById('chat-messages');
element.scrollTop = element.scrollHeight; // Auto scroll to the bottom
});
},
The methods
property is where we define any functions we would like to use in our Vue.js app. The send
method handles sending messages to the server. First we make sure the message isn’t blank. Then we format the message as an object and then stringify()
it so that the server can parse it. We use a little jQuery trick to escape HTML and JavaScript from any incoming message. This prevents any sort of injection attacks.
methods: {
send: function () {
if (this.newMsg != '') {
this.ws.send(
JSON.stringify({
email: this.email,
username: this.username,
message: $('<p>').html(this.newMsg).text() // Strip out html
}
));
this.newMsg = ''; // Reset newMsg
}
},
The join
function will make sure the user enters an email and username before they can send any messages. Once they do, we set join to true
and allow them to start chatting. Again we strip out any HTML or JavaScript.
join: function () {
if (!this.email) {
Materialize.toast('You must enter an email', 2000);
return
}
if (!this.username) {
Materialize.toast('You must choose a username', 2000);
return
}
this.email = $('<p>').html(this.email).text();
this.username = $('<p>').html(this.username).text();
this.joined = true;
},
The final function is a nice little helper function for grabbing the avatar URL from Gravatar. The final piece of the URL needs to be an MD5 encoded string based on the user’s email address. MD5 is a one way encryption algorithm so it helps keep emails private while at the same time allowing the email to be used as a unique identifier.
gravatarURL: function(email) {
return 'http://www.gravatar.com/avatar/' + CryptoJS.MD5(email);
}
}
});
To run the application, open a console window and make sure you are in the “src” directory of your application then run the following command.
- go run main.go
Next open a web browser and navigate to http://localhost:8000
. The chat screen will be displayed and you can now enter an email and username.
To see how the app works with multiple users, just open another browser tab or window and navigate to http://localhost:8000
. Enter a different email and username. Take turns sending messages from both windows.
This just a basic chat application but there are many more improvements you can make to it. I challenge you to play around with the source code and add some other features, see if you can implement private messaging or notifications when a new user joins or leaves the chat. The sky is the limit!
I hope you found this helpful and are now inspired to start creating your own realtime applications using WebSockets and Go.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!