Pushy is a simple server to send SSE (Server-Sent Events) to HTTP clients. It allows you to listen to a list of channels and 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 that connect to the server to listen for events on specific channels.
- Publishers: Authorized entities that can send events to channels, usually internal services.
- Flow: Deploy Pushy server(s) connected to a Redis instance for pub/sub with same or different client_id and client_secret. Publishers a.k.a trusted services create JWT tokens with allowed channels and hand them to clients. Clients connect to Pushy server with the token and listen to events on allowed channels.
Client Publisher Pushy Cluster
(Js or (Internal ┌──────────────────┐
any sse) or trusted) │ Server 1 │
│ Server 2 │
│ Server 3 │
(1) (2) │ + Redis │
Request ────> Get Token ────────>└──────────────────┘
Token with specific │
channels │
│
(3) Return Token <────────┘
│
(4) │
Connect <────────┘
with Token
Listen ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌> Events Stream
(SSE) <╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ (Real-time updates)
(6) Publish
Message ──────────> Channel
(7) Receive
Update ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Phoenix.PubSub distributes
Real-time <╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ to all subscribers
- Elixir ~> 1.12
- 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/news/stream \
-H "Authorization: Bearer <your-token-here>"This will keep the connection open and stream events as they arrive.
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.
docker 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": {}
}'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 client is 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.