
Distributed real-time chat and voice backend written in Go.
REST API · WebSocket delivery · File uploads · Full-text search · Link-preview embeds · WebRTC voice and streaming
Changelog · Go client · TypeScript client · License
Architecture
GoChat is service-oriented — each binary has a single focused responsibility. Services communicate through NATS for async events and share YugabyteDB YSQL, ScyllaDB, and KeyDB for state.
→ Full diagram, data-store reference, and voice flow walkthrough
Services
| Service |
Path |
Responsibility |
| API |
cmd/api |
Public REST surface — guilds, channels, messages, search, uploads, voice control |
| Auth |
cmd/auth |
Registration, login, token refresh, email flows, password reset |
| WebSocket Gateway |
cmd/ws |
Real-time event delivery, presence updates, session management |
| Attachments |
cmd/attachments |
Upload pipeline for files, avatars, and icons; S3 storage and metadata |
| Webhook |
cmd/webhook |
Internal callbacks — SFU/stream heartbeats, stream lifecycle, and attachment finalization |
| SFU |
cmd/sfu |
WebRTC media relay and WebSocket signaling for voice channels |
| Stream |
cmd/stream |
WebRTC media relay and WebSocket signaling for voice-channel screen/app sharing |
| Indexer |
cmd/indexer |
Consumes NATS message events, writes search documents to OpenSearch |
| Embedder |
cmd/embedder |
Builds link-preview embeds from remote metadata |
| Telemetry Gateway |
cmd/telemetrygateway |
OTEL proxy — collects signals from all services, forwards to observability backend |
| Tools |
cmd/tools |
Operational helpers: observability bootstrap, webhook token generation |
Features
- Account lifecycle with JWT-based authentication and email flows
- Guilds, channels, roles, permissions, invites, bans, and custom emoji
- Direct messages, threads, message history, mentions, and reactions
- File attachments, avatars, and icons via S3-compatible storage
- Link-preview embed generation from remote metadata
- Presence updates and real-time event fanout over WebSocket
- Full-text search indexing and query through OpenSearch
- Voice channels with region-aware SFU discovery, WebRTC relay, and separate screen/app streaming
- Structured observability — distributed traces, metrics, and logs via OTEL
Stack
| Area |
Technology |
| Language |
Go 1.25.8 |
| HTTP / WebSocket |
Fiber v2, Fiber WebSocket |
| Voice / WebRTC |
Pion WebRTC |
| Relational DB |
YugabyteDB YSQL |
| Wide-column DB |
ScyllaDB |
| Cache / sessions |
Redis / KeyDB |
| Message bus |
NATS |
| Search |
OpenSearch |
| Object storage |
S3-compatible |
| Service discovery |
etcd |
| Reverse proxy |
Traefik |
| Observability |
OpenTelemetry + OpenObserve |
Getting Started
Prerequisites
- Go
1.25.8 or newer
- Docker and Docker Compose
- GNU Make
migrate CLI for creating migration files locally (make tools installs it)
Quick setup
make setup
Installs local tooling, starts the full Compose stack, initializes ScyllaDB, and applies all migrations.
Manual bootstrap
# Start infrastructure
docker compose up -d
docker compose exec scylla bash ./init-scylladb.sh
# Apply all migrations
make migrate
To test the migration image used in CI:
make build_migration_image
make migrate_image \
YUGABYTE_ADDRESS="postgres://yugabyte:yugabyte@host.docker.internal:5433/gochat?sslmode=disable" \
CASSANDRA_ADDRESS="cassandra://host.docker.internal/gochat?x-multi-statement=true"
CI publishes ghcr.io/<owner>/gochat-migrations:<tag> for releases and ghcr.io/<owner>/gochat-migrations:dev from the dev branch. Database bootstrap steps outside the migration files (ScyllaDB keyspace creation and YugabyteDB database creation) must be completed before running the container.
The container accepts YUGABYTE_ADDRESS, PG_ADDRESS as a backward-compatible YugabyteDB YSQL alias, CASSANDRA_ADDRESS, and MIGRATION_SCOPE=all|yugabyte|yb|ysql|cassandra|scylla.
For local Compose development, make migrate runs the official migrate/migrate container on the Compose network and mounts ./migration read-only. This uses service names like yugabyte:5433 and scylla, so host-port conflicts on 127.0.0.1:5433 do not affect development migrations. Override YUGABYTE_DB, YUGABYTE_USER, YUGABYTE_PASSWORD, or COMPOSE_PROJECT_NAME in .env when needed.
Local Compose creates the gochat YugabyteDB database with YUGABYTE_COLOCATION=false by default. This keeps core relational metadata sharded for high-load deployments; enable colocation only for small isolated test databases or tiny reference datasets.
Service configuration
Copy and edit the example config for each service:
api_config.example.yaml ws_config.example.yaml
auth_config.example.yaml sfu_config.example.yaml
stream_config.example.yaml telemetry_gateway_config.example.yaml
attachments_config.example.yaml indexer_config.example.yaml
webhook_config.example.yaml embedder_config.example.yaml
Running Services
go run ./cmd/api
go run ./cmd/auth
go run ./cmd/ws
go run ./cmd/attachments
go run ./cmd/webhook
go run ./cmd/indexer
go run ./cmd/embedder
go run ./cmd/telemetrygateway
go run ./cmd/sfu # voice media; runs separately from Compose
go run ./cmd/stream # screen/app streaming media; runs separately from Compose
Useful Make targets:
| Target |
What it does |
make up |
Start the Compose stack and initialize ScyllaDB |
make down |
Stop the stack |
make migrate |
Apply all database migrations |
make swag |
Rebuild docs/api/swagger.json |
make client |
Regenerate Go and TypeScript API clients |
make rebuild_all |
Rebuild API, Auth, Indexer, Embedder, and WS containers |
make build_migration_image |
Build the versioned migration container locally |
Observability
The local stack uses OpenObserve and the OpenTelemetry Collector. Bootstrap dashboards with the Tools CLI:
go run ./cmd/tools observability bootstrap \
--url http://localhost:5080 --org default \
--user root@example.com --password Complexpass#123
go run ./cmd/tools observability smoke \
--url http://localhost:5080 --org default \
--user root@example.com --password Complexpass#123
Documentation
|
|
| Architecture |
Full service diagram, data-store reference, voice flow |
| Services overview |
Per-service responsibilities and config reference |
| Channels & messages |
Channel types, message types, threads, embeds |
| Guilds, roles & permissions |
Roles, permissions bitmask, moderation, custom emoji |
| Presence system |
Presence state model and delivery |
| WebSocket protocol |
Event types, subscription model, connection lifecycle |
| Voice & SFU |
WebRTC signaling, SFU protocol, permissions |
| Voice-channel streaming |
Screen/app streaming service, lifecycle, presence, and region migration |
| Direct-message calls |
Private 1:1 voice calls, settings bootstrap, pair-scoped events, and DM call streaming |
| Observability |
OTEL signals, dashboards, runbooks, external SFU |
| Auth security |
Token design, expiry, refresh flow |
| Database schema |
YugabyteDB YSQL and ScyllaDB schema diagrams |
| Tools CLI |
Operational helper commands |
| OpenAPI schema |
Machine-readable API spec |
| Go API client |
Generated Go client |
| TypeScript API client |
Generated TypeScript client |
| Frontend repo |
React web client |
| Desktop client |
Electron desktop app |
| Deployment repo |
Production deployment manifests |
Repository Layout
cmd/ runnable services and operational tools
internal/ shared packages (transport, storage, search, mail, presence, server wiring)
migration/ YugabyteDB YSQL and ScyllaDB migrations
docs/ project documentation and generated OpenAPI schema
clients/api/ generated Go and TypeScript API clients
compose.yaml local development stack
Makefile bootstrap, migration, client generation, and rebuild targets