add realtime
Reach for this generator when the project needs to push live updates to connected clients: a notification badge, a live order status, a chat message.
go tool gpsystem add realtime
Adds cmd/realtime: a dedicated server that holds client streaming connections (WebSocket, with an HTTP-streaming/SSE fallback) and delivers messages published through notify/broadcast from any process. It carries no business dependencies, so it scales freely and independently of the API/worker processes; see Realtime for the architecture and scaling story.
An explicit, opt-in command, not a new project default: like add worker, you run it when the project actually needs the gateway, and it adds a Traefik-exposed service.
What it generates
func main() {
cfg := envconf.MustLoad[config.Config]()
ctx := context.Background()
err := realtime.Run(ctx, cfg.Realtime, func(topics *realtime.TopicAuth) error {
// Authorize shared topic channels here, e.g.:
// topics.Allow("announcements", func(id *rbac.Identity) bool { return true })
// gpsystem:topics
return nil
},
// gpsystem:closers
)
if err != nil {
log.Fatal(err)
}
}
Realtime realtime.Config
realtime.Config reuses VALKEY_* (queue.Config) and JWT_* (auth.Config); no new secrets to manage. See the Realtime page for the full REALTIME_* table.
compose.yml and .env.example
Adds a realtime service (path-routed under /realtime on the project's existing APP_HOST, so no new DNS entry or CORS case): no container_name (so --scale works), a Traefik health-check label pointed at /realtime/healthz (so a draining instance is evicted from the load-balancer pool), responseForwarding.flushInterval=-1 (so streamed bytes flush immediately through the proxy).
realtime:
build: { context: ., dockerfile: Dockerfile, target: development, args: { CMD: realtime } }
depends_on:
valkey:
condition: service_started
stop_grace_period: 30s
labels:
- traefik.http.routers.shopRealtime.rule=Host(`${APP_HOST}`) && PathPrefix(`/realtime`)
- traefik.http.services.shopRealtime.loadbalancer.healthCheck.path=/realtime/healthz
REALTIME_LISTEN_ADDR=:3000
REALTIME_DRAIN_TIMEOUT=20s
REALTIME_CHANNEL_PREFIX=<project>
REALTIME_MAX_CONNECTIONS=0
Scale it independently of every other service:
docker compose up -d --scale realtime=3
Authorizing shared topics
Every connection is server-side subscribed to its own user:<subject> channel automatically; no code needed for that. Shared topics are opt-in: register a callback in cmd/realtime/main.go at the // gpsystem:topics anchor, and a client subscribes to it by name. A topic with no registered Allow is never subscribable; see Realtime.
The gateway defaults to server-to-client only; if the client also needs to
write (not just POST to a separate endpoint), extend the realtime.Run call
with realtime.WithRPC(...) and register methods in main.go: see
Realtime: Client-to-server RPC. The
generator does not scaffold this: it is a manual Option added to
realtime.Run's variadic arguments.
Wiring a notification to it
add realtime only wires the gateway itself. Run it before your first add notification to have the broadcast channel wired into the shared notify.Sender automatically; if notifications already exist, add notify.ChannelBroadcast to their Via and the broadcast construction by hand (see the note on add notification).
add realtime writes at the gpsystem:imports/gpsystem:config anchors in platform config and the gpsystem:services/gpsystem:env anchors in compose.yml/.env.example. If one of these anchor comments is missing, the command stops and prints the block to paste in by hand, the same way add mail does.