Getting Started
Get Relay running and publishing your first real-time event in under 5 minutes.
Prerequisites
- Go 1.21+ or Docker
- A backend framework — Laravel, Node.js, Rails, or Django
- A JavaScript frontend
Run the Server
Choose how you want to run Relay on your machine or server.
docker run -d -p 6001:6001 \
-e RELAY_APP_KEY=my-key \
-e RELAY_APP_SECRET=my-secret \
relayhq/relay:latest
The server is now listening on ws://localhost:6001.
Download the latest binary from GitHub Releases for your platform, then run:
./relay-server
git clone https://github.com/DarkNautica/Relay.git && cd Relay
go build -o relay-server .
./relay-server
Connect the JavaScript Client
Install the Relay JavaScript SDK:
npm install @relayhq/relay-js
Then connect to your Relay server and subscribe to a channel:
import Relay from '@relayhq/relay-js';
const relay = new Relay('my-key', {
host: 'localhost',
port: 6001,
encrypted: false,
});
relay.on('connected', () => console.log('Connected to Relay!'));
relay.on('disconnected', () => console.log('Disconnected.'));
relay.on('error', (err) => console.error('Error:', err));
const channel = relay.subscribe('my-channel');
channel.bind('my-event', (data) => {
console.log('Received:', data);
});
Publish from Your Backend
Relay is wire-compatible with the Pusher protocol, so you can use any Pusher-compatible server SDK to trigger events.
Install
composer require relayhq/relay-php
config/broadcasting.php
'relay' => [
'driver' => 'pusher',
'key' => env('RELAY_APP_KEY'),
'secret' => env('RELAY_APP_SECRET'),
'app_id' => env('RELAY_APP_ID', 'app'),
'options' => [
'host' => env('RELAY_HOST', 'localhost'),
'port' => env('RELAY_PORT', 6001),
'scheme' => 'http',
],
],
.env
BROADCAST_CONNECTION=relay
RELAY_APP_KEY=my-key
RELAY_APP_SECRET=my-secret
Publish an event
broadcast(new OrderShipped($order));
const Pusher = require('pusher');
const pusher = new Pusher({
appId: 'app',
key: 'my-key',
secret: 'my-secret',
host: 'localhost',
port: 6001,
useTLS: false,
});
await pusher.trigger('my-channel', 'my-event', {
message: 'Hello!'
});
Gemfile
gem 'pusher'
config/initializers/pusher.rb
Pusher.app_id = 'app'
Pusher.key = 'my-key'
Pusher.secret = 'my-secret'
Pusher.host = 'localhost'
Pusher.port = 6001
Pusher.encrypted = false
Trigger an event
Pusher.trigger('my-channel', 'my-event', { message: 'Hello!' })
Install
pip install pusher
Trigger an event
import pusher
client = pusher.Pusher(
app_id='app',
key='my-key',
secret='my-secret',
host='localhost',
port=6001,
ssl=False
)
client.trigger('my-channel', 'my-event', {'message': 'Hello!'})
Private Channels
Private channels restrict access to authenticated users. Channel names must be prefixed with private-. When a client subscribes to a private channel, Relay sends an authentication request to your backend.
POST /broadcasting/auth endpoint that validates the user and returns a signed auth token.
Subscribe on the client
const privateChannel = relay.subscribe('private-orders');
privateChannel.bind('new-order', (data) => {
console.log('New order:', data);
});
Laravel auth setup
Laravel handles this automatically via the BroadcastServiceProvider. Define your channel authorization in routes/channels.php:
// routes/channels.php
Broadcast::channel('private-orders', function ($user) {
return $user->canViewOrders();
});
Make sure broadcasting is enabled in config/app.php by uncommenting the BroadcastServiceProvider:
// config/app.php — providers array
App\Providers\BroadcastServiceProvider::class,
Presence Channels
Presence channels extend private channels with awareness of who is currently subscribed. They are ideal for showing online indicators, typing notifications, or collaborative features. Channel names must be prefixed with presence-.
Subscribe on the client
const presenceChannel = relay.subscribe('presence-chat-room');
presenceChannel.bind('relay:subscription_succeeded', (members) => {
console.log('Online now:', members.count);
members.each((member) => {
console.log(member.id, member.info);
});
});
presenceChannel.bind('relay:member_added', (member) => {
console.log('Joined:', member.id);
});
presenceChannel.bind('relay:member_removed', (member) => {
console.log('Left:', member.id);
});
Authorize on the backend (Laravel)
// routes/channels.php
Broadcast::channel('presence-chat-room', function ($user) {
return ['id' => $user->id, 'name' => $user->name];
});
Returning an array (instead of true) tells Relay to treat this as a presence channel and share the returned data with all subscribers.
Common Issues
BROADCAST_CONNECTION vs BROADCAST_DRIVER
In Laravel 11+, the environment variable was renamed from BROADCAST_DRIVER to BROADCAST_CONNECTION. If events are not reaching Relay, make sure your .env uses the correct key for your Laravel version:
# Laravel 11+
BROADCAST_CONNECTION=relay
# Laravel 10 and earlier
BROADCAST_DRIVER=relay
CSRF Exemption for Auth Endpoint
The Relay JavaScript client sends a POST request to /broadcasting/auth. If you receive a 419 status code, add the route to your CSRF exceptions:
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'broadcasting/auth',
];
Event Name Prefixes
Laravel automatically prefixes broadcast event names with the fully qualified class name, e.g. App\Events\OrderShipped. If you are listening for events by their raw name on the client, prefix the listener with a dot to bypass the namespace:
// Listen for the raw event name (no App\Events\ prefix)
channel.bind('.my-event', (data) => {
console.log(data);
});