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

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.