Skip to content

DobryySoul/chatter

Repository files navigation

Chatter

Real-time video conferencing and chat platform built with Go, React, and WebRTC.

Chatter enables peer-to-peer video calls, audio communication, and text messaging through a modern web interface. It features JWT-based authentication, device-aware session management, WebSocket signaling, and a full observability stack.


Table of Contents


Features

Communication

  • Video Calls — peer-to-peer video via WebRTC with STUN/TURN support
  • Audio Calls — real-time audio streaming between participants
  • Text Chat — instant messaging within rooms
  • Room System — create and join rooms by ID or direct WebSocket URL
  • Presence — real-time join/leave notifications and participant list
  • Display Names — custom names visible to all room participants

Authentication & Sessions

  • User Registration & Login — secure username/password authentication
  • JWT Access Tokens — short-lived tokens for API authorization
  • Refresh Tokens — long-lived tokens stored as SHA-256 hashes in the database
  • Device-aware Sessions — each device tracked independently via X-Device-ID
  • Session Management — view all active sessions, identify current device
  • Auto Token Refresh — seamless token renewal without re-login

Infrastructure

  • Containerized Deployment — full Docker Compose setup for dev and production
  • Reverse Proxy — Nginx with SSL/TLS termination
  • SSL Certificates — automated Let's Encrypt via Certbot
  • Database Migrations — versioned schema changes with Goose
  • Health Checks — all services monitored with Docker health checks

Observability

  • Metrics — Prometheus metrics for HTTP requests, duration, and response size
  • Distributed Tracing — OpenTelemetry traces exported to Tempo
  • Log Aggregation — structured JSON logs shipped to Loki via Docker logging driver
  • Dashboards — Grafana with pre-configured Prometheus, Loki, and Tempo datasources

Architecture

High-Level Overview

graph TB
    subgraph Client["Browser"]
        FE["React SPA<br/>(Vite)"]
        WR["WebRTC Engine"]
    end

    subgraph Backend["Go Backend"]
        API["REST API<br/>(net/http)"]
        WS["WebSocket<br/>Signaling Server"]
        AUTH["Auth Service"]
        JWT["JWT Manager"]
        REPO["Repositories"]
    end

    subgraph Data["Data Layer"]
        PG[(PostgreSQL)]
        RD[(Redis<br/><i>reserved</i>)]
    end

    subgraph Monitoring["Observability"]
        PROM["Prometheus"]
        TEMPO["Tempo"]
        LOKI["Loki"]
        GRAF["Grafana"]
    end

    FE -->|HTTP| API
    FE -->|WebSocket| WS
    WR <-->|P2P Media| WR

    API --> AUTH
    AUTH --> JWT
    AUTH --> REPO
    REPO --> PG

    API -->|/metrics| PROM
    API -.->|traces| TEMPO
    API -.->|logs| LOKI

    PROM --> GRAF
    TEMPO --> GRAF
    LOKI --> GRAF
Loading

Request Flow — Authentication

sequenceDiagram
    participant C as Client
    participant S as Server
    participant DB as PostgreSQL

    C->>S: POST /auth/register {username, password}
    Note over S: Validate credentials
    S->>S: Hash password (bcrypt)
    S->>DB: INSERT INTO users
    S->>S: Generate access token (JWT)
    S->>S: Generate refresh token (random)
    S->>DB: INSERT INTO refresh_tokens (hash)
    S-->>C: {id, token, username} + Set-Cookie: refresh_token
Loading

Request Flow — WebRTC Signaling

sequenceDiagram
    participant A as Peer A
    participant S as Signaling Server
    participant B as Peer B

    A->>S: WebSocket connect /ws/{roomId}
    S-->>A: welcome {clientId}
    B->>S: WebSocket connect /ws/{roomId}
    S-->>B: welcome {clientId}
    S-->>A: presence {join, B}
    S-->>B: participants [A, B]

    Note over A,B: WebRTC negotiation via server relay

    A->>S: webrtc {offer, to: B, sdp}
    S-->>B: webrtc {offer, from: A, sdp}
    B->>S: webrtc {answer, to: A, sdp}
    S-->>A: webrtc {answer, from: B, sdp}
    A->>S: webrtc {ice, to: B, candidate}
    S-->>B: webrtc {ice, from: A, candidate}

    Note over A,B: Direct P2P media stream established
Loading

Clean Architecture Layers

graph LR
    subgraph Handler["Handler Layer"]
        H1["auth.go"]
        H2["signaling/handler.go"]
    end

    subgraph UseCase["Use Case Layer"]
        U1["auth.go"]
    end

    subgraph Repository["Repository Layer"]
        R1["auth.go"]
        R2["refresh_token.go"]
    end

    subgraph Domain["Domain Layer"]
        D1["user.go"]
        D2["token.go"]
    end

    subgraph Infra["Infrastructure"]
        I1["jwt_manager.go"]
        I2["postgres.go"]
    end

    H1 --> U1
    H2 --> U1
    U1 --> R1
    U1 --> R2
    U1 --> I1
    R1 --> D1
    R2 --> D2
    R1 --> I2
    R2 --> I2
Loading

Tech Stack

Layer Technology Purpose
Backend Go >1.25 API server, signaling, business logic
Frontend React 18 + Vite Single-page application
Real-time WebSocket + WebRTC Signaling and peer-to-peer media
Database PostgreSQL 18 Users, refresh tokens
Cache Redis 7.4 Reserved for future use
Auth JWT (HS256) + bcrypt Access/refresh token system
Migrations Goose Versioned database schema
Metrics Prometheus HTTP metrics collection
Tracing OpenTelemetry + Tempo Distributed request tracing
Logs Zap + Loki Structured JSON logging
Dashboards Grafana Unified observability UI
Proxy Nginx Reverse proxy, SSL termination
SSL Certbot (Let's Encrypt) Automated certificate management
Containers Docker + Docker Compose Dev and production orchestration
Task Runner Taskfile Development commands

Project Structure

chatter/
├── chatter/                          # Go backend
│   ├── cmd/
│   │   └── main.go                   # Entry point
│   ├── config/
│   │   └── config.yaml               # Application config
│   ├── internal/
│   │   ├── app/
│   │   │   └── app.go                # HTTP server setup & routing
│   │   ├── config/
│   │   │   └── config.go             # Config struct & loader
│   │   ├── domain/
│   │   │   ├── user.go               # User entity
│   │   │   └── token.go              # RefreshToken entity
│   │   ├── handler/
│   │   │   └── auth.go               # Auth HTTP handlers
│   │   ├── infra/
│   │   │   └── jwt_manager.go        # JWT generation & validation
│   │   ├── repository/
│   │   │   ├── auth.go               # User repository (PostgreSQL)
│   │   │   └── refresh_token.go      # Token repository (PostgreSQL)
│   │   ├── signaling/
│   │   │   ├── client.go             # WebSocket client
│   │   │   ├── handler.go            # Room creation & WS handler
│   │   │   ├── registry.go           # Thread-safe room registry
│   │   │   └── room.go               # Room logic & broadcasting
│   │   └── usecase/
│   │       └── auth.go               # Auth business logic
│   ├── migrations/
│   │   ├── 00001_create_users_table.sql
│   │   ├── 00002_create_refresh_tokens_table.sql
│   │   └── 00003_add_device_id.sql
│   ├── pkg/
│   │   ├── logger/                   # Zap logger setup
│   │   ├── middleware/
│   │   │   ├── auth.go               # JWT auth middleware
│   │   │   ├── cors.go               # CORS middleware
│   │   │   ├── logging.go            # Request logging
│   │   │   ├── metrics.go            # Prometheus metrics
│   │   │   ├── response_recorder.go  # ResponseWriter wrapper
│   │   │   └── tracing.go            # OpenTelemetry tracing
│   │   ├── postgres/                 # PostgreSQL connection pool
│   │   ├── redis/                    # Redis client (reserved)
│   │   └── telemetry/                # OpenTelemetry exporter
│   ├── Dockerfile
│   ├── go.mod
│   └── go.sum
├── web/                              # React frontend
│   ├── src/
│   │   ├── App.jsx                   # All pages & WebRTC logic
│   │   ├── main.jsx                  # React entry point
│   │   └── styles.css                # Application styles
│   ├── Dockerfile
│   ├── nginx.conf                    # SPA routing
│   ├── vite.config.js
│   ├── package.json
│   └── index.html
├── infra/
│   ├── monitoring/
│   │   ├── grafana/provisioning/     # Grafana datasources
│   │   ├── loki/                     # Loki retention config
│   │   ├── prometheus/               # Prometheus scrape config
│   │   └── tempo/                    # Tempo storage config
│   └── nginx/
│       ├── init.conf                 # Bootstrap nginx (pre-SSL)
│       └── prod.conf                 # Production nginx with SSL
├── docker-compose.yaml               # Development environment
├── docker-compose.prod.yaml          # Production environment
├── Dockerfile.migrate                # Goose migration runner
├── Taskfile.yml                      # Task runner commands
└── init-letsencrypt.sh               # SSL certificate bootstrap

Getting Started

Prerequisites

  • Docker and Docker Compose
  • Task (optional, for convenience commands)

Quick Start

1. Clone the repository

git clone https://github.com/your-username/chatter.git
cd chatter

2. Start the development environment

task dev:up

Or without Task:

docker compose up -d --build

3. Run database migrations

task migrate:up

Or without Task:

docker compose run --rm migrate up

4. Open the application

Service URL
Frontend http://localhost:5173
Backend API http://localhost:8080
Grafana http://localhost:3000
Prometheus http://localhost:9090

Stopping

task dev:down

Configuration

Configuration is loaded from chatter/config/config.yaml and can be overridden with environment variables.

Config File

server:
  addr: ":8080"
  cors_origins:
    - "http://localhost:5173"

auth:
  secret: "a-string-secret-at-least-256-bits-long"
  access_ttl: 24h
  refresh_ttl: 1440h

postgres:
  host: chatter-postgres
  port: 5432
  username: postgres
  password: password
  database: postgres
  min_conns: 1
  max_conns: 100

redis:
  host: redis
  port: 6379
  username: default
  password: password
  db: 0

Environment Variables

Variable Description Default
CONFIG_PATH Path to config file config/config.yaml
SERVER_ADDR Server listen address :8080
SERVER_CORS_ORIGINS Comma-separated allowed origins http://localhost:5173
AUTH_SECRET JWT signing secret (min 256 bits)
AUTH_ACCESS_TTL Access token lifetime 24h
AUTH_REFRESH_TTL Refresh token lifetime 1440h
POSTGRES_HOST PostgreSQL host chatter-postgres
POSTGRES_PORT PostgreSQL port 5432
POSTGRES_USER PostgreSQL username postgres
POSTGRES_PASSWORD PostgreSQL password
POSTGRES_DB PostgreSQL database name postgres
OTEL_SERVICE_NAME OpenTelemetry service name chatter
OTEL_EXPORTER_OTLP_ENDPOINT OTLP exporter endpoint http://tempo:4318

API Reference

Base URL: http://localhost:8080

Health & Metrics

Method Endpoint Description
GET /health Health check — returns 200 OK
GET /metrics Prometheus metrics endpoint

Authentication

Register

POST /auth/register

Headers:

Header Required Description
Content-Type Yes application/json
X-Device-ID Yes Unique device identifier

Body:

{
  "username": "alice",
  "password": "securepassword"
}

Response 200:

{
  "id": 1,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "username": "alice"
}

Sets refresh_token HttpOnly cookie.


Login

POST /auth/login

Headers: same as Register

Body:

{
  "username": "alice",
  "password": "securepassword"
}

Response 200:

{
  "id": 1,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "username": "alice"
}

Refresh Token

POST /auth/refresh

Cookies: refresh_token (set during login/register)

Response 200:

{
  "id": 1,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "username": "alice"
}

Logout

POST /auth/logout

Clears the refresh_token cookie.


List Active Sessions

GET /auth/sessions

Headers:

Header Required Description
Authorization Yes Bearer <access_token>

Response 200:

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "deviceId": "abc123def456",
    "expiresAt": "2026-04-07T12:00:00Z",
    "lastSeen": "2026-02-07T12:00:00Z"
  }
]

Rooms

Create Room

POST /rooms

Headers:

Header Required Description
Authorization Yes Bearer <access_token>

Response 200:

{
  "roomId": "a1b2c3d4",
  "wsUrl": "ws://localhost:8080/ws/a1b2c3d4"
}

Join Room (WebSocket)

GET /ws/{roomId}?token=<access_token>

Upgrades to WebSocket connection. The token query parameter is optional but enables authenticated participation.


WebSocket Protocol

All messages are JSON. The type field determines the message kind.

Server Messages

Type Description Payload
welcome Sent on connection {clientId}
participants Current room members {participants: [{id, displayName}]}
presence Join/leave events {action: "join"|"leave", clientId}
profile Display name update {clientId, displayName}

Client Messages

Type Description Payload
profile Set display name {type: "profile", displayName}
chat Send text message {type: "chat", text}
webrtc Signaling message {type: "webrtc", action, to, from, sdp|candidate}

WebRTC Actions

Action Direction Description
offer A → B SDP offer for peer connection
answer B → A SDP answer accepting the offer
ice Both ICE candidate exchange for connectivity

Database Schema

Entity Relationship Diagram

erDiagram
    USERS {
        bigserial id PK
        text username UK
        text email
        bytea password_hash
        timestamptz created_at
        timestamptz updated_at
    }

    REFRESH_TOKENS {
        uuid id PK
        integer user_id FK
        text token_hash
        timestamp expires_at
        boolean revoked
        timestamptz updated_at
        varchar device_id
    }

    USERS ||--o{ REFRESH_TOKENS : "has many"
Loading

Migrations

Version Description
00001 Create users table with username index
00002 Create refresh_tokens table with foreign key to users
00003 Add device_id column to refresh tokens

Observability

Architecture

graph LR
    APP["Chatter<br/>Backend"] -->|scrape /metrics| PROM["Prometheus<br/>:9090"]
    APP -->|OTLP HTTP| TEMPO["Tempo<br/>:4318"]
    APP -->|Docker logs| LOKI["Loki<br/>:3100"]

    PROM --> GRAF["Grafana<br/>:3000"]
    TEMPO --> GRAF
    LOKI --> GRAF
Loading

Metrics (Prometheus)

The backend exposes metrics at GET /metrics:

Metric Type Labels Description
http_requests_total Counter method, path, status Total HTTP requests
http_request_duration_ms_total Counter method, path, status Cumulative request duration
http_response_size_bytes_total Counter method, path, status Cumulative response size

Tracing (OpenTelemetry + Tempo)

Every HTTP request generates a span with attributes:

  • http.method — request method
  • http.route — matched route pattern
  • http.target — full request URL
  • http.status_code — response status

Traces are exported via OTLP HTTP to Tempo at http://tempo:4318.

Logging (Zap + Loki)

Structured JSON logs with fields:

  • method, path, status, bytes, duration
  • remote_addr, userID, username
  • Health and metrics endpoints are excluded from logging

Logs are collected by Docker's Loki logging driver and shipped to Loki automatically.

Grafana Datasources

Pre-configured datasources available out of the box:

Datasource URL Purpose
Prometheus http://prometheus:9090 Metrics queries
Loki http://loki:3100 Log queries
Tempo http://tempo:3200 Trace queries

Access Grafana at http://localhost:3000 (default credentials: admin / admin).


Production Deployment

1. Configure environment

Edit docker-compose.prod.yaml and set:

  • POSTGRES_PASSWORD — strong database password
  • SERVER_CORS_ORIGINS — your domain (e.g., https://example.com)
  • AUTH_SECRET — strong JWT signing secret

2. Set up SSL certificates

Update domains in init-letsencrypt.sh, then run:

chmod +x init-letsencrypt.sh
sudo ./init-letsencrypt.sh

3. Start production services

docker compose -f docker-compose.prod.yaml up -d --build

Production Stack

graph TB
    INET["Internet"] --> NGINX["Nginx<br/>:80 / :443"]
    NGINX -->|/api/*| BACKEND["Chatter<br/>:8080"]
    NGINX -->|/ws/*| BACKEND
    NGINX -->|/*| FRONTEND["React SPA<br/>(static)"]
    BACKEND --> PG[(PostgreSQL)]
    CERT["Certbot"] -.->|renew| NGINX
Loading

Services in production:

  • Nginx — reverse proxy with SSL termination
  • Certbot — automatic certificate renewal
  • Web — React SPA served by Nginx
  • Chatter — Go backend API + WebSocket server
  • PostgreSQL — primary database
  • Migrate — runs database migrations on startup

Task Runner Commands

Command Description
task dev:up Start development environment
task dev:down Stop and remove all containers and volumes
task migrate:up Run all pending migrations
task migrate:down Rollback the last migration
task migrate:status Show current migration status

Middleware Pipeline

Every HTTP request passes through the following middleware chain:

graph LR
    REQ["Request"] --> CORS["CORS"]
    CORS --> TRACE["Tracing"]
    TRACE --> LOG["Logging"]
    LOG --> METRIC["Metrics"]
    METRIC --> AUTH["Auth<br/><i>(protected routes)</i>"]
    AUTH --> HANDLER["Handler"]
    HANDLER --> RES["Response"]
Loading
Middleware Scope Description
CORS All routes Handles preflight and sets CORS headers
Tracing All routes Creates OpenTelemetry span per request
Logging All routes* Logs request details with Zap (*excludes /health, /metrics)
Metrics All routes Records Prometheus counters per request
Auth Protected routes Validates JWT and injects user context

Built with Go, React, and WebRTC

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors