July 10, 2019

Building a live chat with Go, NATS, Redis and Websockets

Building a live-chat server is a good practice for learning a ‘backend’ programming language. You need to provide an uninterrupted stream of data (think WebSockets), message storer and ideally a pubsub mechanism to send a message to all subscribed consumers. Goch is no different, besides HTTP and REST endpoint it uses WebSockets, Redis, and NATS-streaming to support live-chat messaging. Read how it runs and how you can build your own live-chat in Go.

Besides a learning project, a live-chat server can be useful for many things. For instance, we use it for support chat in our automotive SaaS. There are plenty of solutions out there for something like this, but integrating it with a product and customizing for your needs turns out to be beneficial. Goch is a fork of Gossip, with clearer dependencies (gorilla/mux instead of aneshas/kit) and various issues fixed.

What does it take to build a live chat (with Go)? In fact not much. I’ll go through all of the packages and explain what they do and why are they needed.

Goch is available on GitHub: https://github.com/ribice/goch

  • cmd/goch: The executable package that wraps up everything
  • internal/agent: WebSocket implementation. Provides /connect endpoint which clients use to connect to the chat.
  • internal/broker: Serves as an implementation of pubsub/NATS. It holds and manages channel subscriptions.
  • internal/chat: Provides HTTP endpoints for creating and listing channels, registering to channel, getting channel members and number of unread messages in a channel
  • internal/ingest: Manages chat history functionality by fetching and updating the chat history.
  • pkg/config: Configuration package managing nats, Redis, server and chat limits configuration via YAML file and environment variables
  • pkg/nats and pkg/redis: NATS and Redis connector and implementation. It would be better to move the implementation to the dependent packages, but it would introduce a lot of duplication for such a small codebase.

How does it actually work?

Starting the application via docker compose launches Redis, NATS-Streaming and Goch, which connects to these two services. The configuration is loaded via /pkg/config from YAML file and environment variables. Among the things it sets admin username/password that will be used by a service creating chatrooms.

Upon creating a new channel (we do it after a ticket in our support system is created), a POST request to /admin/channels should be created with ChannelName (can be a token) and IsPrivate. If a channel is created with IsPrivate indicator, a secret will be generated, associated with the channel and responded to the client.

After creating a channel, the user can join it by sending a POST request to /channels with UID, Email, DisplayName, ChannelName, and Secrets. It tries to fetch the channel from Redis, add a user to it, save it and return user’s secret for the channel.

In order to initiate a chat, a user has to make a request to /connect with ChannelName, UID and a Secret. Optionally a Unix timestamp can be provided for fetching history since that date. The server creates a WebSocket connection, tries to fetch the channel from Redis, and if one exists creates a channel of a Message type. It then tries to fetch chat history from a given sequence and subscribes to the channel on NATS-Streaming. Requests for new messages and chat history are then sent via this connection.

Currently, goch only supports opening a single chat at a time. You can still see the number of unread messages on other chats. This covered our use case, but can easily be extended to support multi-chat as well.

MSV

Alongside goch I’ve also released MSV - A minimal server in Go. Quite often I start working on a new project, I find myself writing things from scratch like middleware, graceful shutdown. MSV wraps these things up in a small package with sensible and minimal dependencies.

2024 © Emir Ribic - Some rights reserved; please attribute properly and link back. Code snippets are MIT Licensed

Powered by Hugo & Kiss.