A Pusher-compatible WebSocket server for Elixir, inspired by Laravel Reverb.
Browsers connect with pusher-js (or any Pusher client library), and your
backend sends events with any Pusher server library or a plain signed HTTP
request. You host it yourself.
Run it two ways, from the same code:
- Standalone: a Docker image you run next to your app.
- Embedded: a Hex dependency inside your own Elixir/Phoenix app.
It runs on one server with nothing else, or on several servers sharing a Redis.
Socketelx is an independent open-source project. It implements the public Pusher Channels protocol so existing Pusher clients and libraries work with it. It is not affiliated with, endorsed by or sponsored by Pusher Ltd. "Pusher" is a trademark of its owner and is used here only to describe compatibility.
What you can build with it
Chat, live notifications, presence ("who is online"), live dashboards, collaborative cursors, order tracking: anything where the server pushes updates to browsers and phones as they happen.
- Public, private and presence channels, with signed channel auth
- Presence that counts a user once across tabs and devices
- Client events (
client-*) for things like typing indicators - REST API: trigger events, batch events, list channels, channel info
- Signed webhooks with retries
- Optional rate limits, connection cap, and Prometheus metrics
- Multi-server mode through Redis (optional)
- Graceful shutdown: clients are told to reconnect before a node exits
It works with the clients and libraries you probably already use: pusher-js,
laravel-echo, and the official Pusher server SDKs (Node, PHP, Python, Ruby,
Go and others).
Quickstart: Docker
docker run -p 6001:6001 \
-e SOCKETELX_APP_ID=my-app-id \
-e SOCKETELX_APP_KEY=my-app-key \
-e SOCKETELX_APP_SECRET=a-long-random-secret \
ghcr.io/socketelx/socketelx:latest
In the browser (npm install pusher-js):
import Pusher from 'pusher-js';
const pusher = new Pusher('my-app-key', {
cluster: 'unused', // required by pusher-js, ignored by this server
wsHost: 'localhost',
wsPort: 6001,
forceTLS: false,
enabledTransports: ['ws', 'wss'],
disableStats: true,
});
pusher.subscribe('news').bind('update', (data) => console.log(data));From your backend (npm install pusher, shown in Node; every official SDK works the same way):
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'my-app-id', key: 'my-app-key', secret: 'a-long-random-secret',
host: '127.0.0.1', port: '6001', useTLS: false,
});
await pusher.trigger('news', 'update', { hello: 'world' });The browser logs { hello: 'world' }. Private and presence channels also need an
auth endpoint in your backend; the chat example shows
the whole thing.
Quickstart: embed in your Elixir app
# mix.exs
{:socketelx, "~> 0.2"}
# lib/my_app/application.ex
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
{Socketelx,
name: MyApp.Socketelx,
port: 6001,
app_id: "my-app-id",
app_key: "my-app-key",
app_secret: "a-long-random-secret"}
]Publish from anywhere in your app, with no HTTP call, and sign channel joins:
Socketelx.trigger(MyApp.Socketelx, "news", "update", %{hello: "world"})
Socketelx.authorize_channel(MyApp.Socketelx, socket_id, "presence-room",
user_id: user.id, user_info: %{name: user.name})The server runs its own listener on its own port; it is not a Plug inside your Phoenix endpoint. See the Embedding guide.
A complete example: simple chat
examples/chat is a small chat app (online list,
messages, "is typing") in two versions: one using the Docker image with a tiny
Node backend, one fully embedded in an Elixir app. Each is a few short files and
a step-by-step setup.
Similar projects
- Poxa: an older, well-known Pusher-compatible server written in Elixir.
- Laravel Reverb: the PHP server that inspired this project, speaking the same protocol.
Several servers
Give every node the same app credentials and the same SOCKETELX_REDIS_URL, and
put a load balancer in front. Events, presence and channel occupancy are shared.
Without SOCKETELX_REDIS_URL no Redis connection is ever opened. See the
Multi-server guide.
Documentation
- Getting started: run it and connect a client
- Configuration: every option and environment variable
- Embedding: use it as a library
- Multi-server: Redis and failure behaviour
- Deployment: TLS, proxies, Docker, shutdown
- Load testing: simulate connections and measure capacity
- Protocol support: what is and isn't implemented
- Troubleshooting
- Using it with Laravel
Status
Early (0.x), and in use in a real chat application. The test suite starts real
nodes on real sockets, speaks the WebSocket protocol to them, signs REST calls,
and, with a real Redis, covers cross-node delivery, a node dying, Redis being
flushed and Redis being unreachable. The chat example is also tested end to end
in a real browser with pusher-js 8.
Not verified yet: behaviour at very large scale (see Load testing to measure your own), the Compose/nginx example, and Elixir versions other than the ones CI uses.
Not implemented: user sign-in features and the users endpoints, durable webhook retries, per-IP limits, built-in TLS, Redis Cluster. Delivery is at-most-once, like Pusher. Details in Protocol support.
Development and contributing
mix deps.get
mix test # Redis tests are skipped unless SOCKETELX_TEST_REDIS_URL is set
iex -S mix # starts the listener on PORT (default 6001)
See CONTRIBUTING.md. Security issues: see SECURITY.md. Changes are listed in CHANGELOG.md.
License
MIT, see LICENSE.