package module
v0.0.0-...-90a05d2 Latest Latest

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2016 License: MIT Imports: 6 Imported by: 2


Thunderbird - Elegant WebSockets in Go

I'm competing at Gopher Gala 2016. Please give this project a star and follow me if you like what you see 🍻.

Thunderbird (a.k.a. ⚑🐦) seamlessly integrates WebSockets with your Go web application. It allows for real-time features to be written in idiomatic Go. It's a full-stack offering that provides both a client-side JavaScript framework and a server-side Go framework. Thunderbird is heavily inspired by Elixir's Phoenix and Rails' Action Cable.

How it works

Thunderbird is built around the concept of connections and channels. It has one connection per WebSocket connection. A single user may have multiple WebSockets open to your application if they use multiple browser tabs or devices. The client of a WebSocket connection is called a consumer and it can subscribe to multiple channels. Each channel responds to a name and encapsulates a logical unit of work similar to a controller in a regular MVC setup. Just like how pub/sub works, a broadcast can be made to consumers that subscribed to a channel.

Here is an example of multiple channels on multiple connections:

          β”‚          β”‚           Connection1
          β”‚ Client1  │◀──────[channel1, channel2]────────┐
          β”‚          β”‚                                   β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                   β–Ό
                          Connection2           β”‚                β”‚
                β”Œβ”€β”€β”€β”€β”€[channel2, channel3]─────▢│     Server     β”‚
                β”‚                               β”‚                β”‚
                β–Ό                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                   β–²
          β”‚          β”‚           Connection3             β”‚
          β”‚ Client2  │◀───────────[channel3]β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚          β”‚

Tutorial: a chat app

Let's walk through building a chat app with Thunderbird and understand how elegant the solution is. The code is available in the example folder. The example is running live here.

Server handling WebSocket connections

The first thing is to initialize Thunderbird and create a route to handle WebSocket connections:

func main() {
  tb := thunderbird.New()

  mux := http.NewServeMux()
  mux.Handle("/ws", tb.HTTPHandler())

Thunderbird.HTTPHandler() returns a http.Handler that deals with WebSocket connections.

Server handling a channel

We then create a channel handler that implements the ChannelHandler interface. ChannelHandlers are registered with Thunderbird.HandleChannel which takes a name and a ChannelHandler:

type RoomChannel struct {

func (rc *RoomChannel) Received(event thunderbird.Event) {
    // handle event

func main() {
    ch := &RoomChannel{}
    tb.HandleChannel("room", ch)
Server broadcasting messages to consumers

For a chat app, we want RoomChannel to broadcast received messages to all consumers of the channel. We do this by calling Thunderbird.Broadcast when there is a message event:

type RoomChannel struct {
    tb *thunderbird.Thunderbird

func (rc *RoomChannel) Received(event thunderbird.Event) {
    switch event.Type {
    case "message":
      rc.tb.Broadcast(event.Channel, event.Body)

Event.Type represents the type of the event received. Currently the value can be subscribed, unsubscribed or message which mean a subscription was made to the channel, a unsubscription was made to channel or a message was received on the channel correspondingly. More event types will be support in the future.

So far the Go side of the chat app is done. That was easy, right :-)? Let's take a look at the JavaScript part of the app.

Client connecting

On the client, we call Thunderbird.connect to connect to the Go server. The method takes a URL and a callback that accepts a connection object. In thie case url should be the WebSocket URL that we set up in previous step: ws://localhost:8080/ws.

Thunderbird.connect(url, function (conn) {
  // do something with the connection `conn`
Client subscribing to a channel

With the conn, we can subscribe to a channel (room in our example) and do something with the received message in the callback:

conn.subscribe("room", function (msg) {
  // handle message
Client sending messages

For a chat app, we would like to send messages to other consumers and we do that with the perform method of the connection:

// when enter key is pressed
conn.perform("room", msg)

###Β Client's host is different than server

If client is hosted on another server than Thunderbird runs, most likely they have different hostnames.
In such a case, you should use Thunderbird.HTTPHandlerWithUpgrader() and define your custom Origin header validator.

func main() {
  tb := thunderbird.New()
  tbUpgrader := websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
      return true

  mux := http.NewServeMux()
  mux.Handle("/ws", tb.HTTPHandlerWithUpgrader(tbUpgrader))


Thunderbird is under heavy development. Please be patient before it hits prime time :-).

  • WebSocket HTTP handler
  • Basic in memory pub/sub
  • JavaScript client
  • Example chat app
  • Redis pub/sub adapter
  • Postgres pub/sub adapter



Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

This section is empty.

Types ΒΆ

type Adapter ΒΆ

type Adapter interface {
	Subscribe(channel string) error
	Unsubscribe(channel string) error
	Broadcast(channel string, payload []byte) error

type ChannelHandler ΒΆ

type ChannelHandler interface {

type Connection ΒΆ

type Connection struct {
	// contains filtered or unexported fields

func (*Connection) Subscribed ΒΆ

func (c *Connection) Subscribed(channel string)

type Event ΒΆ

type Event struct {
	Type    string `json:"type"`
	Channel string `json:"channel"`
	Body    string `json:"body"`

type Thunderbird ΒΆ

type Thunderbird struct {
	// contains filtered or unexported fields

func New ΒΆ

func New() *Thunderbird

func (*Thunderbird) Broadcast ΒΆ

func (tb *Thunderbird) Broadcast(channel, body string)

func (*Thunderbird) Channels ΒΆ

func (tb *Thunderbird) Channels(channel string) []ChannelHandler

func (*Thunderbird) HTTPHandler ΒΆ

func (tb *Thunderbird) HTTPHandler() http.Handler

func (*Thunderbird) HTTPHandlerWithUpgrader ΒΆ

func (tb *Thunderbird) HTTPHandlerWithUpgrader(upgrader websocket.Upgrader) http.Handler

func (*Thunderbird) HandleChannel ΒΆ

func (tb *Thunderbird) HandleChannel(channel string, handler ChannelHandler)

Directories ΒΆ

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL