Building your first WebSocket Application with Nitric
What we'll be doing
- Use Nitric to create a WebSocket endpoint
- Manage WebSocket connections using a Key-Value store
- Handle WebSocket events:
- Register connections on connect
- Remove connections on disconnect
- Broadcast messages to all connected clients
- Run locally for testing
- Deploy to a cloud of your choice
Prerequisites
- Go
- The Nitric CLI
- An AWS, GCP, or Azure account (your choice)
Getting started
We'll start by creating a new project for our WebSocket application.
nitric new my-websocket-app go-starter
Next, open the project in your editor of choice.
cd my-websocket-app
Make sure all dependencies are resolved:
go mod tidy
The scaffolded project should have the following structure:
+--services/
| +-- hello/
| +-- main.go
| ...
+--nitric.yaml
+--go.mod
+--go.sum
+--golang.dockerfile
+--.gitignore
+--README.md
You can test the project to verify everything is working as expected:
nitric start
If everything is working as expected, you can now delete all files/folders in the services/
folder. We'll create new services in this guide.
Building the WebSocket Application
Let's begin by setting up the WebSocket application. First, create a new folder called websockets
within the services directory. Inside this folder, add a file named main.go
, and include the following code:
package main
import (
"context"
"fmt"
"github.com/nitrictech/go-sdk/handler"
"github.com/nitrictech/go-sdk/nitric"
)
func main() {
ws, err := nitric.NewWebsocket("public")
if err != nil {
fmt.Println("Error creating WebSocket:", err)
return
}
connections, err := nitric.NewKv("connections").Allow(nitric.KvStoreGet, nitric.KvStoreSet, nitric.KvStoreDelete)
if err != nil {
fmt.Println("Error creating KV store:", err)
return
}
// Add event handlers here
if err := nitric.Run(); err != nil {
fmt.Println("Error running Nitric service:", err)
}
}
Here we're creating:
- A WebSocket endpoint named
public
- A Key-Value store named
connections
to track WebSocket connections
From here, let's add some features to that function that allow us to manage connections and broadcast messages.
You could separate some or all of these event handlers into their own services if you prefer. For simplicity, we'll group them together in this guide.
Register connections on connect
ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) {
err := connections.Set(context.TODO(), ctx.Request.ConnectionID(), map[string]interface{}{
"connectionId": ctx.Request.ConnectionID(),
})
if err != nil {
return
}
})
Remove connections on disconnect
ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) {
err := connections.Delete(context.TODO(), ctx.Request.ConnectionID())
if err != nil {
return
}
})
Broadcast messages to all connected clients
ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) {
connectionStream, err := connections.Keys(context.TODO())
if err != nil {
return
}
senderId := ctx.Request.ConnectionID()
for {
connectionId, err := connectionStream.Recv()
if err != nil {
break
}
if connectionId == senderId {
continue
}
message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message())
err = ws.Send(context.TODO(), connectionId, []byte(message))
if err != nil {
return
}
}
})
Bringing it all together
Your code should look like this:
package main
import (
"context"
"fmt"
"github.com/nitrictech/go-sdk/nitric"
"github.com/nitrictech/go-sdk/nitric/websockets"
)
func main() {
ws, err := nitric.NewWebsocket("public")
if err != nil {
fmt.Println("Error creating WebSocket:", err)
return
}
connections, err := nitric.NewKv("connections").Allow(nitric.KvStoreGet, nitric.KvStoreSet, nitric.KvStoreDelete)
if err != nil {
fmt.Println("Error creating KV store:", err)
return
}
ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) {
err := connections.Set(context.TODO(), ctx.Request.ConnectionID(), map[string]interface{}{
"connectionId": ctx.Request.ConnectionID(),
})
if err != nil {
return
}
})
ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) {
err := connections.Delete(context.TODO(), ctx.Request.ConnectionID())
if err != nil {
return
}
})
ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) {
connectionStream, err := connections.Keys(context.TODO())
if err != nil {
return
}
senderId := ctx.Request.ConnectionID()
for {
connectionId, err := connectionStream.Recv()
if err != nil {
break
}
if connectionId == senderId {
continue
}
message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message())
err = ws.Send(context.TODO(), connectionId, []byte(message))
if err != nil {
return
}
}
})
if err := nitric.Run(); err != nil {
fmt.Println("Error running Nitric service:", err)
}
}
Do a quick go mod tidy
to make sure all new dependencies are resolved.
Ok, let's run this thing!
Now that you have your WebSocket application defined with handlers for each event, it's time to test it locally.
nitric start
Once it starts, the application will be ready to accept WebSocket connections. You can use a WebSocket client like Postman or any other WebSocket tool to test the application.
We will keep it running for our tests. If you want to update your services, just save them, and they'll be reloaded automatically.
Deploy to the cloud
At this point, you can deploy what you've built to any of the supported cloud providers. To do this, start by setting up your credentials and any configuration for the cloud you prefer:
Next, we'll need to create a stack
. A stack represents a deployed instance of an application, which is a key value store of resources defined in your project. You might want separate stacks for each environment, such as stacks for dev
, test
, and prod
. For now, let's start by creating a dev
stack.
The stack new
command below will create a stack named dev
that uses the aws
provider.
nitric stack new dev aws
Continue by checking your stack file nitric.dev.yaml
and adding in your preferred region. Let's use us-east-1
.
AWS
Note: You are responsible for staying within the limits of the free tier or any costs associated with deployment.
We called our stack dev
. Let's try deploying it with the up
command:
nitric up
When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your WebSocket application.
To tear down your application from the cloud, use the down
command:
nitric down
Summary
In this guide, we've created a serverless WebSocket application using Go and Nitric. We've demonstrated how to set up WebSocket connections, track clients using a Key-Value store, and broadcast messages to all connected clients. This application can be easily deployed to the cloud, allowing you to build scalable, real-time communication systems.
For more information and advanced usage, refer to the Nitric documentation.