Websockets guide

In this guide we'll be creating a simple realtime application using Nitric websockets.

Prerequisites

To complete this guide you'll need the following:

Getting Started

Let's start by setting up a Nitric project:

nitric new websocket-example ts-starter

Install dependencies:

cd websocket-example
yarn install

You can go ahead and open this new project in your editor of choice. You should see a project structure similar to:

├── services
│   ├── hello.ts
├── node_modules
│   ├── ...
├── .gitignore
├── nitric.yaml
├── package.json
├── tsconfig.json
├── README.md
└── yarn.lock

In this structure you'll notice the services folder. By default, this is where Nitric expects the entrypoint code for your application. However, that's just a convention, we can change that to anything else that suits our needs.

Let's update our hello.ts file with some websocket code to get started.

services/hello.ts
import { websocket } from '@nitric/sdk'
const socket = websocket('example-websocket')

socket.on('connect', async (ctx) => {
  console.log(`connecting: ${ctx.req.connectionId}`)
})

socket.on('disconnect', async (ctx) => {
  console.log(`disconnecting: ${ctx.req.connectionId}`)
})

socket.on('message', async (ctx) => {
  const message = ctx.req.text()
  console.log(`got message from  ${ctx.req.connectionId}: ${message}`)
})

At this point, we're ready to start testing locally. Let's start the local Nitric environment.

nitric start

Your websocket will now be running with Nitric acting as a proxy, in this case it's available on port 4001.

In this guide we'll test this using Insomnia, however feel free to use any websocket capable testing client you like.

When you send messages you will start seeing your server log messages.

websocket testing

Sending messages from server to clients

Websockets only make sense when communication is bi-directional, now we've confirmed that the client can talk to our server lets get our server talking to the client.

To do this we'll need to add some connection management.

You can update the hello.ts file like so:

services/hello.ts
import { websocket, kv } from '@nitric/sdk'

// Initialize KV store for connections and a WebSocket
const kvStore = kv('connections').for('getting', 'setting', 'deleting')
const socket = websocket('example-websocket')

// Helper function to get current connections
async function getCurrentConnections() {
  try {
    const serializedList = await kvStore.get('connections')
    return serializedList && serializedList['ids']
      ? JSON.parse(serializedList['ids'])
      : []
  } catch (error) {
    console.error('Error getting current connections:', error)
    return []
  }
}

// Helper function to update connections list
async function updateConnections(connections) {
  try {
    const updatedSerializedList = JSON.stringify(connections)
    await kvStore.set('connections', { ids: updatedSerializedList })
  } catch (error) {
    console.error('Error updating connections:', error)
  }
}

// Handle new connections
socket.on('connect', async (ctx) => {
  const connections = await getCurrentConnections()
  connections.push(ctx.req.connectionId)
  await updateConnections(connections)
})

// Handle messages
socket.on('message', async (ctx) => {
  const message = ctx.req.text()
  const connections = await getCurrentConnections()

  // Send the message to each connection
  connections.forEach(async (connectionId) => {
    try {
      if (connectionId !== ctx.req.connectionId) {
        await socket.send(connectionId, message)
      }
    } catch (error) {
      console.error(`Error sending message to ${connectionId}:`, error)
    }
  })
})

// Handle disconnections
socket.on('disconnect', async (ctx) => {
  const connections = await getCurrentConnections()
  const index = connections.indexOf(ctx.req.connectionId)

  if (index > -1) {
    connections.splice(index, 1)
    await updateConnections(connections)
  }
})

If you connect multiple clients using your preferred client and send messages each client should receive messages from other clients:

multiple clients test

Deploy to the cloud

To perform the deployment we'll create a stack, stacks give Nitric the configuration needed for a specific cloud instance of this project, such as the provider and region.

The new stack command can help you create the stack by following prompts.

nitric stack new

This command will create a file named nitric.dev.yaml, with contents like this:

nitric.dev.yaml
provider: nitric/aws@1.0.0
region: us-east-1

With the stack file in place we can run the deployment:

terminal
nitric up

Go ahead and test your app in the cloud, you can start by connecting wscat to the websocket endpoint printed in the output for up.

You'll need to add $default to the URL provided to hit the deployed stage. This will be simplified in future versions of the AWS provider.

What next?

Now that you have the basics down, try exploring other Nitric resources available to enhance your app.