Pushy is a standalone server to send real-time messages to clients using Server-Sent Events (SSE) or WebSockets. It allows clients to listen to a list of channels and servers (publishers) to post events to them.
- Channels: Named streams that clients can subscribe to for receiving events. We prefer dot notation (e.g.,
news.sports) for channel names, so wildcards can be used (e.g.,news.*). - Clients: HTTP clients through SSE or WebSocket connections that subscribe to channels and receive real-time updates.
- Publishers/backends: Authorized entities that can send events to channels, usually internal services/backends.
Client Publisher Pushy Cluster
(SSE or WS) (Your backend) ┌──────────────────┐
│ Server 1 │
│ Server 2 │
│ Server 3 │
(1) (2) │ + Redis │
Request ────────> Create Token ────────> └──────────────────┘
Token for with specific │
sse connection channels permissions │
│
│
(3) Return Token <────────┘
│
(4) │
Connect <────────────┘
with Token
Listen ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> Events Stream
(SSE or WS) <╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ (Real-time updates)
(5) Publish
Message ───────────────> Channel
(6) Receive
Update ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Phoenix.PubSub distributes
Real-time <╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ to all subscribers
- Elixir ~> 1.14
- Erlang/OTP 23+
- Docker & Docker Compose (optional, for containerized deployment)
The fastest way to get Pushy running with Redis is using Docker Compose.
docker compose up -dThis starts:
- pushy-server-1 on http://localhost:4001
- pushy-server-2 on http://localhost:4002 (for clustering/failover)
- Redis on localhost:6543 (for pub/sub)
Default credentials are set in the docker-compose.yml:
AUTH_SECRET_KEY:your_secret_key_herePUBLISHER_CLIENT_ID:publisher_idPUBLISHER_CLIENT_SECRET:publisher_secret
Generate a token using your publisher credentials:
curl -X POST http://localhost:4001/token \
-H "Content-Type: application/json" \
-H "X-Client-ID: publisher_id" \
-H "X-Client-Secret: publisher_secret" \
-d '{
"sub": "user123",
"channels": ["news", "sports", "weather"],
"meta": {
"name": "John Doe",
"email": "john@example.com"
}
}'Response:
{
"token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."
}Save this token for the next steps.
Open a Server-Sent Events connection to listen to messages:
curl -X GET http://localhost:4001/channels/stream?channels=news \
-H "Authorization: Bearer <your-token-here>"This will keep the connection open and stream events as they arrive.
Alternatively, you can use WebSocket:
# Using websocat (install via: cargo install websocat)
websocat ws://localhost:4001/channels/ws?channels=news \
-H "Authorization: Bearer <your-token-here>"In another terminal, publish a message:
curl -X POST http://localhost:4001/publish/news \
-H "Content-Type: application/json" \
-H "X-Client-ID: publisher_id" \
-H "X-Client-Secret: publisher_secret" \
-d '{
"data": {
"title": "Breaking News",
"content": "Some important content here"
}
}'The subscribed client will receive the message in real-time! Also try sending on a different server at 4002, it will still be delivered, thanks to Phoenix.PubSub.
Pushy exposes Prometheus metrics at /metrics endpoint. You can check them with:
curl http://localhost:4001/metricsdocker compose downWhen you create a token, it contains:
sub: Subject identifier (e.g., user ID)channels: Array of channels the user can accessmeta: Optional metadata object
- Clients: Can only subscribe to and publish messages to channels listed in their token's
channelsarray - Publisher: Uses client credentials (
X-Client-IDandX-Client-Secretheaders) to create tokens with specific channel permissions
When creating a token, you can use wildcards in channel names:
curl -X POST http://localhost:4001/token \
-H "Content-Type: application/json" \
-H "X-Client-ID: publisher_id" \
-H "X-Client-Secret: publisher_secret" \
-d '{
"sub": "user456",
"channels": ["news.*", "sports.updates"],
"meta": {}
}'If you have many publishers, it is better to name channels with publisher prefix (e.g., publisher1.news, publisher2.sports) and create tokens with publisher-specific channel permissions. This way, you can easily manage access control and avoid accidental cross-publisher access.
git clone https://github.com/ananto30/pushy.git
cd pushymix deps.getmix compileexport AUTH_SECRET_KEY=your_secret_key_here
export PUBLISHER_CLIENT_ID=publisher_id
export PUBLISHER_CLIENT_SECRET=publisher_secretOptionally, configure Redis (defaults to localhost:6379):
export REDIS_URL=redis://localhost:6379mix testmix run --no-haltThe server will start on port 4000 by default.
The project includes a multi-stage Dockerfile that builds an optimized production image.
Important: Never bake secrets into image layers with --build-arg. Instead, pass AUTH_SECRET_KEY and other secrets at runtime via environment variables or your orchestrator's secret mechanism.
docker run -p 4000:4000 \
-e AUTH_SECRET_KEY=$AUTH_SECRET_KEY \
-e PUBLISHER_CLIENT_ID=$PUBLISHER_CLIENT_ID \
-e PUBLISHER_CLIENT_SECRET=$PUBLISHER_CLIENT_SECRET \
-e REDIS_URL=redis://your-redis-host:6379 \
pushy:latestThere is a simple JavaScript client library to connect to Pushy servers using SSE. See the client/sse-client.js file for usage details.
PS: I am not a JS expert, the clients are solely generated by LLMs 😅
See the example directory for sample scripts to listen to channels and publish events.
Proxy / nginx notes
When putting a reverse proxy in front of Pushy, ensure you do not buffer the upstream response and increase timeouts. Example nginx snippet:
location /channels/ {
proxy_pass http://pushy:4000;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_read_timeout 3600s;
}
This project is licensed under the MIT License.