Challenge
A social platform looks uniform from the outside — feed, posts, messaging, notifications — but the underlying requirements differ sharply. Authentication needs strict deadlines and rate limiting, messaging needs persistent WebSocket connections, the feed needs caching and recommendations, uploads work in orders of magnitude that text simply does not.
Pressing all of this into a single backend means processes compete for resources that really should scale independently. A spike on the upload endpoint can slow the login path; a slow search blocks notifications.
The goal for Crypton was therefore a backend federation with clear boundaries between services, a unified auth model at the edge and one shared deploy path via Docker Compose — while consolidating native clients on iOS, Android and web.
Approach
Five principles:
- Split services by domain — auth, users, posts, search, upload, notifications, WebSocket, recommendations and analytics each as their own service with their own data responsibility.
- One language in the backend — Go 1.23 across all services so patterns, libraries and operational know-how can be shared.
- Two data-store categories — PostgreSQL for durable data, Redis for cache, sessions and pub/sub.
- Security in the base — JWT with access and refresh tokens, rate limiting split between auth and API, security headers and CORS whitelist, all in code rather than only in nginx.
- Operations via Docker Compose — health checks, persistent volumes, explicit startup order, so local development and production share the same shape.
What we built
Backend services (Go)
A federation of Go 1.23 services under backend/ and microservices/: authentication, users, posts, notifications, analytics, search, upload, WebSocket, recommendations. Each service owns its endpoints and writes to its own schema.
Database & cache
PostgreSQL 16 with UUID support as the core database (database/, init-db.sql), Redis 7 for caching, sessions and pub/sub. Seed and migration scripts (seed-data.sql) make a fresh setup reproducible.
Android client (Kotlin)
android-app/ holds the native Android client together with shared Kotlin modules (shared-kotlin/). The client talks to the same backend APIs as the other surfaces.
iOS client (Swift)
ios-app/ delivers the native iOS client. Together with the Android client it shares data models through OpenAPI-generated SDKs (sdk/, sdks/).
Web surface and tooling
TypeScript parts under apps/, frontend-integration/, sdk/ and packages/ provide the web surface, shared types and tooling (Vite-based).
Security
security/, nginx.conf, nginx/, CSRF and 2FA guides plus scripts for token-blacklist tests (test_token_blacklist.sh, test_csrf_protection.sh) group the security work. JWT access tokens (15 min), refresh tokens (7 days), bcrypt for passwords, rate limiting per route family.
Operations & observability
docker-compose.production.yml, docker-compose.microservices.yml, monitoring/, k8s/, jaeger/, kafka/, kong/, consul/ — the federation can be started as a slim local Compose or with the full observability chain.
Tooling scripts
Numerous test-*.sh and docker-*.sh scripts at the repo root support developers on routine tasks (API testing, container start/stop, backup/restore).
Architecture
Crypton is a service federation in Go: several backend services (auth, users, posts, notifications, analytics, search, upload, WebSocket, recommendations) run as separate containers behind a common entry point. An API gateway (Kong in the more advanced setups) routes requests to the responsible service.
The data layer splits into PostgreSQL 16 for stateful domain data and Redis 7 for cache, sessions and pub/sub. Where events should flow between services (messaging, notifications), Kafka is available as an optional bus (kafka/).
On the client side, three native surfaces — Android (Kotlin), iOS (Swift) and web (TypeScript) — run against the same APIs. Shared data models are kept in sync through SDKs under sdk/, sdks/ and shared-kotlin/, so a change to the post schema does not have to be implemented three separate times.
The federation runs on Docker Compose in multiple profiles: from docker-compose.simple.yml for local development to docker-compose.production.yml with the full observability chain (Prometheus, Jaeger, Consul). Health checks and persistent volumes are part of the default setup.
Numbers & facts
| Metric | Value |
|---|---|
| Backend language | Go 1.23 |
| Backend services | auth, users, posts, notifications, analytics, search, upload, WebSocket, recommendations |
| Database / cache | PostgreSQL 16 · Redis 7 |
| Optional event bus | Kafka |
| Native clients | Android (Kotlin) · iOS (Swift) · web (TypeScript) |
| Auth | JWT (access 15 min, refresh 7 days) · bcrypt · rate limiting |
| Operations | Docker Compose (several profiles) · health checks · Prometheus · Jaeger |
What we learned
A service federation forces clear data ownership. As soon as every service owns its own schema, hidden SQL joins across domain boundaries stop being an option. That costs modelling time up front but makes every change explicit and locally deployable.
Native clients with generated SDKs scale better than a “universal” client. The cost of generating shared types from the backend and distributing them to Kotlin, Swift and TypeScript is lower than the cost of maintaining a single cross-platform framework and constantly working around its edges.
Security in code, not in the reverse proxy. Rate limiting, JWT checks and CSRF protection live in the services themselves. That makes them verifiable in tests and protects against regressions when the nginx configuration changes.
Next steps
- Consolidate the Docker Compose profiles: fewer variants, clearer roles (
dev,staging,production). - Extend the observability chain into standard deploys (Prometheus + Jaeger as default, not optional).
- Tie SDK generation strictly to OpenAPI so all three clients always share the same contract snapshot.
Related
- Context: Our ventures — Crypton shows how we design backend federations and clients together.