5.2 KiB
Messenger
Self-hosted messenger for family and friends. Built from scratch: Go backend, React PWA frontend, PostgreSQL. Deployed on a home-lab mini PC.
Status: v1.0.0 — production-ready. Auth, direct chats and groups, real-time delivery, media, reactions, search, Web Push, PWA offline, metrics and hardening are all implemented and tested.
Repository layout
server/ Go API
cmd/api/ entrypoint (wiring, migrations, graceful shutdown)
cmd/keygen/ generate VAPID keys for Web Push
cmd/seed/ create demo users and a family group
internal/api/ HTTP handlers: auth, users, contacts, conversations, messages, media, search, push
internal/auth/ argon2id hashing, JWT access tokens, refresh tokens
internal/realtime/ WebSocket hub: events, presence, typing, read receipts
internal/media/ S3-like storage + image thumbnails
internal/store/ PostgreSQL access (pgx), embedded migrations
internal/platform/ config, logging, metrics, rate limiting, version
web/ React + TypeScript + Vite PWA (offline, Web Push)
deploy/ docker-compose (api, postgres, victoriametrics, grafana)
docs/ architecture (mermaid), ADR
.github/ CI workflows
API
All endpoints under /api/v1. Auth via httpOnly cookies (messenger_access
= JWT 15 min, messenger_refresh = opaque token 30 days, rotated on refresh).
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Create account (sets cookies) |
| POST | /api/v1/auth/login |
Sign in (sets cookies) |
| POST | /api/v1/auth/refresh |
Rotate refresh token |
| POST | /api/v1/auth/logout |
Revoke session, clear cookies |
| GET | /api/v1/users/me |
Current user |
| GET | /api/v1/users?q= |
Search users by username/name |
| GET | /api/v1/contacts |
Contact list |
| POST | /api/v1/contacts |
{user_id} — add contact |
| DELETE | /api/v1/contacts/{user_id} |
Remove contact |
| GET | /api/v1/conversations |
List chats + unread counts |
| POST | /api/v1/conversations |
{user_id} DM or {type:"group", name, member_ids} |
| GET | /api/v1/conversations/{id}/members |
List participants + roles |
| POST | /api/v1/conversations/{id}/members |
{user_id} — add to group |
| DELETE | /api/v1/conversations/{id}/members/{user_id} |
Remove/leave (owner or self) |
| GET | /api/v1/conversations/{id}/messages |
History, keyset ?before= |
| POST | /api/v1/conversations/{id}/messages |
{body, temp_id, media_id?} — send |
| PUT | /api/v1/conversations/{id}/messages/{msg_id}/reactions |
{emoji} — react |
| DELETE | /api/v1/conversations/{id}/messages/{msg_id}/reactions/{emoji} |
Remove reaction |
| POST | /api/v1/media |
Upload file (multipart file) |
| GET | /api/v1/media/{id} |
Download file |
| GET | /api/v1/media/{id}/thumb |
Image thumbnail |
| GET | /api/v1/search/messages?q= |
Search user's messages |
| POST | /api/v1/push/subscribe |
Register Web Push subscription |
| DELETE | /api/v1/push/subscribe |
Unregister Web Push subscription |
| GET | /ws |
WebSocket (auth via cookie) |
WebSocket event types: message.created, typing, presence, read,
reaction.added/removed, member.added/removed, conversation.added/removed.
Prerequisites
- Go 1.27+, Node.js 22+, PostgreSQL
- Docker + Docker Compose (for deployment)
Local development
# create the role once (as postgres superuser)
sudo -u postgres psql -c "CREATE ROLE messenger LOGIN PASSWORD 'messenger';"
sudo -u postgres psql -c "CREATE DATABASE messenger OWNER messenger;"
# backend (migrations run automatically on start)
cd server
go run ./cmd/api # :8080
# frontend (separate terminal)
cd web
npm install
npm run dev # :5173, proxies /api and /ws -> :8080
Optional: demo data → cd server && go run ./cmd/seed
(users papa/mama/syn/doch, password password123, group «Семья»).
Deployment (Docker Compose)
cp deploy/.env.example deploy/.env # adjust secrets
cd deploy
docker compose up -d --build
Services: api (single static binary serving API + frontend), postgres,
victoriametrics (metrics storage), grafana (dashboard «Messenger»
auto-provisioned at http://localhost:3000).
Metrics
GET /metrics exposes Prometheus metrics: HTTP requests by status/path,
request latency, WebSocket connections, Go runtime. Scraped by VictoriaMetrics.
Web Push (optional)
cd server && go run ./cmd/keygen # prints the three VAPID variables
Put them in deploy/.env (MESSENGER_VAPID_*) and in web/.env
(VITE_VAPID_PUBLIC_KEY). Without keys everything works except notifications.
Architecture & decisions
See docs/architecture.md (component and sequence diagrams) and
docs/adr/ for the rationale behind: monolith, in-memory WS hub, ILIKE
search, S3-like media storage, E2EE roadmap.
Roadmap
- Phase 0-3 — skeleton → auth/chats → frontend → groups/reactions/media → search/push/PWA ✅
- Phase 4 — P2P WebRTC calls via our WebSocket signalling + coturn TURN fallback (planned, major feature)
- Phase 5 — metrics, docs, hardening ✅
License
GPLv3