Gorcure
Real-time superpowers for Goravel — powered by Mercure & SSE.
Laravel Broadcasting + Echo vibes for Go: publish updates to a Mercure hub in one line.
Install ·
Config ·
Usage ·
Running a hub ·
Roadmap
Gorcure brings real-time capabilities to Goravel through Mercure
and Server-Sent Events (SSE) — a Laravel Broadcasting + Echo–like experience. Gorcure is
the publisher: your Goravel app pushes updates to a Mercure hub over HTTP, and the hub
streams them to subscribed browsers via SSE.
// one line, real-time
facades.Gorcure().Publish("/users/1", map[string]any{"online": true})
Features
- Real-time updates over SSE through any Mercure hub (Docker / Caddy / Mercure.rocks)
- HTTP publisher with automatic publisher-JWT minting (HS256)
- Public and private updates (
private flag, multi-topic, custom SSE id/type/retry)
- First-class Goravel integration: Service Provider, facade, one-command install
- Mock client for testing
Requirements
- Go 1.26+
- Goravel v1.17+
- A running Mercure hub (see Running a hub)
Installation
One command does everything:
./artisan package:install github.com/eddyjj92/gorcure
It runs go get, then auto-wires the package — no manual edits:
- registers
&gorcure.ServiceProvider{} in your providers list
(config/app.go or bootstrap/providers.go, whichever layout you use)
- creates
config/gorcure.go
- runs
go mod tidy
Remove it just as cleanly:
./artisan package:uninstall github.com/eddyjj92/gorcure
Manual installation (no artisan)
go get github.com/eddyjj92/gorcure
Register the provider in config/app.go:
import "github.com/eddyjj92/gorcure"
func Providers() []foundation.ServiceProvider {
return []foundation.ServiceProvider{
// ...
&gorcure.ServiceProvider{},
}
}
Then create config/gorcure.go with the Configuration keys below.
Configuration
Add to your .env:
# Public hub URL consumed by the frontend (SSE EventSource)
GORCURE_HUB_URL=http://localhost:3000/.well-known/mercure
# URL the server publishes to (server -> hub). Defaults to GORCURE_HUB_URL.
GORCURE_HUB_PUBLISH_URL=http://localhost:3000/.well-known/mercure
# JWT keys (HS256 by default). Raw shared key OR a pre-signed JWT.
GORCURE_JWT_ALG=HS256
GORCURE_PUBLISHER_JWT=!ChangeMe!
GORCURE_SUBSCRIBER_JWT=!ChangeMe!
# Skip TLS verification when publishing (dev hubs, self-signed certs)
GORCURE_PUBLISH_INSECURE=false
| Key |
Env |
Default |
Notes |
hub_url |
GORCURE_HUB_URL |
http://localhost:3000/.well-known/mercure |
frontend SSE URL |
publish_url |
GORCURE_HUB_PUBLISH_URL |
falls back to hub_url |
server publish URL |
jwt.alg |
GORCURE_JWT_ALG |
HS256 |
signing algorithm |
jwt.publisher |
GORCURE_PUBLISHER_JWT |
!ChangeMe! |
shared key or pre-signed JWT |
jwt.subscriber |
GORCURE_SUBSCRIBER_JWT |
!ChangeMe! |
shared key (reserved for private channels) |
insecure |
GORCURE_PUBLISH_INSECURE |
false |
skip TLS verify on publish |
The publisher token is minted automatically from the shared key (granting publish
on all topics). If GORCURE_PUBLISHER_JWT is already a signed JWT (x.y.z), it is used
as-is. The shared key must match the hub's publisher JWT key.
Usage
import (
"github.com/eddyjj92/gorcure/contracts"
"github.com/eddyjj92/gorcure/facades"
)
// Single topic — non-string data is JSON-encoded automatically
id, err := facades.Gorcure().Publish("/users/1", map[string]any{"online": true})
// Full control: multiple topics, private, SSE id/type/retry
id, err := facades.Gorcure().PublishUpdate(contracts.Update{
Topics: []string{"/users/1", "/admins"},
Data: `{"online":true}`,
Private: true,
})
Publish / PublishUpdate return the hub-assigned event id.
Subscribing (frontend)
const url = new URL("http://localhost:3000/.well-known/mercure")
url.searchParams.append("topic", "/users/1")
const es = new EventSource(url)
es.onmessage = (e) => console.log(JSON.parse(e.data))
Running a hub
Gorcure publishes to a Mercure hub; you run the hub separately. The quickest dev setup is
the official Docker image:
docker run -d --name mercure -p 3000:80 \
-e MERCURE_PUBLISHER_JWT_KEY='!ChangeMe!' \
-e MERCURE_SUBSCRIBER_JWT_KEY='!ChangeMe!' \
-e 'MERCURE_EXTRA_DIRECTIVES=cors_origins "http://localhost:3000"' \
dunglas/mercure
The hub is then at http://localhost:3000/.well-known/mercure. Keep the JWT keys in sync
with GORCURE_PUBLISHER_JWT / GORCURE_SUBSCRIBER_JWT. For production see the
Mercure docs.
The Mercure hub itself is licensed AGPL-3.0; running the official image as a standalone
service keeps it separate from your application. Gorcure (this package) never links the
hub — it only talks to it over HTTP — so Gorcure stays MIT.
Testing
A mock implementing contracts.Gorcure is provided. It records every update so you
can assert on what your code published:
import "github.com/eddyjj92/gorcure/mocks"
m := mocks.New() // returns a *mocks.Gorcure (ID defaults to "mock-event-id")
id, err := m.Publish("/users/1", map[string]any{"online": true})
m.Count() // -> 1
last, _ := m.Last() // -> the recorded contracts.Update
// Force an error path:
m.Err = errors.New("hub down")
_, err = m.Publish("/users/1", "x") // err == "hub down", nothing recorded
Public API
type Gorcure interface {
Publish(topic string, data any) (string, error)
PublishUpdate(update Update) (string, error)
}
type Update struct {
Topics []string // at least one required
Data string // serialized payload (usually JSON)
Private bool // only authorized subscribers receive it
ID string // optional SSE event id
Type string // optional SSE event type
Retry int // optional reconnection time (ms)
}
Status
| Version |
Scope |
State |
| v0.1.0 |
Basic publisher (external hub) |
✅ |
| v0.2.0 |
Broadcastable events |
planned |
| v0.3.0 |
Private channels + subscriber tokens |
planned |
| v0.4.0 |
Broadcasting driver |
planned |
| v0.5.0 |
Inertia integration |
planned |
License
Gorcure is released under the MIT License.
SPDX-License-Identifier: MIT
Copyright (c) 2026 eddyjj92
Gorcure talks to a Mercure hub over HTTP and does not link any hub code, so this package
is MIT. The Mercure hub you run separately is AGPL-3.0 — running the official Docker
image as a standalone service keeps that boundary clean.