Beaver: a Redis-backed pubsub server with JWT clients and no release since 2021
💨 A real time messaging system to build a scalable in-app notifications, multiplayer games, chat apps in web and mobile apps.
At a glance
- What is it?
- Beaver is a Go real-time messaging server where channels come in private, public and presence flavours, backed by Redis and fronted by an HTTP API keyed on an X-API-Key header., and the pinned dependencies and 2021 release tags are the first things to weigh.
- Who is it for?
- Adopt Beaver if you want a self-hosted pubsub service with channel presence semantics and an HTTP control API and you are prepared to own the dependencies. Do not adopt it for a client ecosystem: the repository carries no JavaScript or mobile client, and the release tags stop at v1.5.0 from 2021-09-03, so you should review the pinned dependencies before deployment.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Channel types are the model, and presence is the interesting one
Beaver is a real-time messaging server for building in-app notifications, realtime graphs, multiplayer games, chat applications and geotracking in web and mobile apps. The topics name the shape of it: messaging, pubsub, realtime, websocket, socket-io and pusher.
Channels are the central object, and they come in three types. You create a private channel, a public channel or a presence channel, each by POSTing a name and a type to the API:
# Private Channel
curl -X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: 6c68b836-6f8e-465e-b59f-89c1db53afca' \
-d '{"name": "app_x_chatroom_1", "type": "private"}' \
'http://localhost:8080/api/channel'The channel resource carries `listeners_count` and `subscribers_count` alongside timestamps, and the presence channel response shows `listeners` and `subscribers` as arrays, currently null when empty. That is the shape of a presence channel: it tracks who is connected as well as what they are subscribed to, which is what a chat room or a multiplayer lobby needs and a plain topic does not.
Channel names are namespaced by convention in the examples, app_x_chatroom_1 for private, app_y_chatroom_1 for public, app_z_chatroom_5 for presence, so the naming is your tenancy scheme rather than something the server enforces.
Everything, including the config store, goes through one API key
Authentication is a single static API key in a header, X-API-Key, on every endpoint. There is no per-user token in the examples, no OAuth flow, and no scoping of the key to particular channels.
That is the simplest possible design and it has an obvious consequence: anyone holding the key can create, read, update and delete channels and config entries across the whole server. There is no second layer of authorisation in the documented API.
The config API is the clearest example, since it is a plain key-value store exposed over HTTP. Creating an entry is a POST with a key and a value, reading is a GET returning {"key":"app_name","value":"Beaver"}, and updating and deleting follow the same path with PUT and DELETE. That is convenient for bootstrapping an application and dangerous if left open, which is why the key is not something you leave at its default value.
The config file also carries a `secret` used by Beaver itself, plus a distinct `api.key`. Those are two separate values with two separate jobs, and confusing them during deployment is the kind of mistake that leaves your server open.
The default config ships a real API key and a real secret
config.dist.yml is the template you copy to config.yml, and every value in it is environment-driven with a default in the placeholder. The defaults are worth reading closely:
# API Configs
api:
key: ${BEAVER_API_KEY:-6c68b836-6f8e-465e-b59f-89c1db53afca}
# Beaver Secret
secret: ${BEAVER_SECRET:-sWUhHRcs4Aqa0MEnYwbuQln3EW8CZ0oD}Those literal strings are the defaults, and they are the same values used in every curl example in the README. Anyone who deploys without setting BEAVER_API_KEY and BEAVER_SECRET runs a server whose API key and secret are published in its documentation.
The substitution syntax is what drone/envsubst in the dependency list provides, and the rest of the file uses the same pattern throughout: mode is ${BEAVER_APP_MODE:-prod}, the port is ${BEAVER_API_PORT:-8080}, the hostname is ${BEAVER_API_HOSTNAME:-127.0.0.1}, Redis is ${BEAVER_DB_REDIS_ADDR:-localhost:6379} with a password and a database index, logging is ${BEAVER_LOG_LEVEL:-info} with json format to stdout.
Note that mode defaults to prod while the hostname defaults to localhost. That combination is safe on a single machine and wrong the moment you set a hostname without also setting a key and a secret.
Redis is the only datastore, and TLS and metrics come with it
The database section of the config has a driver key defaulting to redis, with address, password and db number, and the README instructs you to install a redis cluster or a single node first. There is no other supported backend, which is a simplification and also a limit: state lives in Redis and Redis is what has to be durable and available.
TLS is built in and off by default. The app block has a tls status that defaults to off, with a pemPath and keyPath pointing at cert/server.pem and cert/server.key, so you terminate TLS in the application rather than in a reverse proxy. That is workable but unusual, and a team with a standard ingress termination path will probably want the reverse proxy in front and TLS status left off.
Metrics are Prometheus, again off to be configured but present, with an endpoint defaulting to /metrics and the prometheus/client_golang client in the dependency list. A metrics endpoint on the same port as the API, protected by the same API key story or not protected at all, is worth thinking about before it goes near a network.
Logging goes through logrus with a level, an output target and a json format option, all environment-driven.
Running it is a single cobra subcommand
The README's run instructions use systemd, and the actual command is one line:
$ beaver api -c /path/to/config.ymlThe `api` subcommand and the `-c` flag come from cobra in the dependency list. Binaries are downloaded from the releases page, with the README giving a shell snippet that resolves the latest tag through the GitHub API and pipes the archive through tar:
$ export BEAVER_LATEST_VERSION=$(curl --silent "https://api.github.com/repos/Clivern/Beaver/releases/latest" | jq '.tag_name' | sed -E 's/.*"([^"]+)".*/\1/'| tr -d v)
$ curl -sL https://github.com/Clivern/Beaver/releases/download/v{$BEAVER_LATEST_VERSION}/beaver_{$BEAVER_LATEST_VERSION}_Linux_x86_64.tar.gz | tar xzThat snippet requires jq and GNU-style sed, so it is a starting point rather than something to paste into a hardened provisioning script. Release archives are built by goreleaser with a .goreleaser.yml in the repository.
The repository also carries a web/ directory, a Makefile with style, license-header, unit and integration test targets, a deployment/ directory, and techstack.yml and techstack.md, which look like they were generated to describe the project's own stack.
Go 1.20 and a dependency set from 2021
go.mod declares go 1.20 and pins a dependency set whose vintage is visible in the versions. gin v1.9.1, viper v1.16.0, cobra v1.7.0, logrus v1.9.3, prometheus/client_golang v1.18.0, gorilla/websocket v1.5.0, go-redis/redis v6.15.9+incompatible and drone/envsubst v1.0.3.
The one to look at hardest is github.com/dgrijalva/jwt-go v3.2.0+incompatible. That module path is the deprecated one; the library moved to github.com/golang-jwt/jwt after the original author stopped maintaining it, and the +incompatible suffix reflects a major version bump without a module path change. A JWT library that a service relies on for client authentication is the kind of dependency an assessor will query.
There is also satori/go.uuid v1.2.0, another package that was abandoned at that version. Both of these are the direct cost of the release cadence.
The release history explains it: v1.3.0 and v1.4.0 were both tagged on 2021-09-01, v1.5.0 on 2021-09-03, and nothing since. The repository is not archived and the last push was on 2026-10-13, so there is activity on the default branch, but no release has captured it in five years. Anyone adopting this is adopting v1.5.0 unless they build from source, and should read the diff between the two before assuming the branch is an upgrade.
Where Beaver is the wrong tool
The first wrong-tool case is a client requirement. The repository has no JavaScript client, no mobile SDK and no browser library, unlike the hosted services its topic list compares itself to. You would be writing the WebSocket protocol handling yourself against whatever the documentation describes.
The second is multi-tenancy with real authorisation. One API key governs the whole server, and channel names are namespaced by naming convention rather than by enforced isolation. If two of your customers must not see each other's channels, that guarantee has to live in your own backend, and Beaver will not enforce it.
The third is a need for message history or delivery guarantees. The documented API surface in the README covers configs and channels; there is no persistence of messages described, and Redis is configured as the application database rather than as a queue with an offset model. Anything that must survive a disconnect or be replayed belongs in your own storage.
The fourth is operational appetite for a five-year-old release. The project is small, MIT licensed, and shows signs of life on main, but a service holding client connections is exactly the kind of thing where the dependency set and the release cadence are the decision, not the code.
Editorial conclusion
Adopt Beaver if you want a self-hosted pubsub service with channel presence semantics and an HTTP control API and you are prepared to own the dependencies. Do not adopt it for a client ecosystem: the repository carries no JavaScript or mobile client, and the release tags stop at v1.5.0 from 2021-09-03, so you should review the pinned dependencies before deployment. Replace the shipped default API key and secret in config.yml before the server is reachable from anything but localhost.
Frequently asked questions
How do I install and run the Beaver messaging server?
Download the latest release binary, create config.yml from config.dist.yml with your Redis settings, and run `beaver api -c /path/to/config.yml`, which the README suggests doing under systemd. Redis, either a cluster or a single node, must be installed first.
What channel types does Beaver support?
Three, created by POSTing a name and type to the API: private, public and presence. The channel resource reports subscribers_count and listeners_count, and presence channels also track listeners and subscribers as arrays.
How does Beaver authenticate API requests?
With a static key in the X-API-Key header on every request, taken from `api.key` in the config file or the BEAVER_API_KEY environment variable. There is no per-user token or scoping in the documented API.
Does Beaver support TLS and metrics?
Yes, both are configured and both default to off. TLS is terminated in the application using tls pemPath and keyPath, and metrics are exposed through Prometheus at an endpoint defaulting to /metrics.
Is Beaver still released?
The most recent release is v1.5.0 from 2021-09-03, after v1.4.0 and v1.3.0 on 2021-09-01. The repository is not archived and the last push was on 2026-10-13, so the default branch has moved ahead of the last release.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/clivern-beaver)Was this useful?