From request to response.
WC26 Threads is a small but properly-shaped backend with a native Android client. Every layer is a choice I'd defend in an interview.
Request flow
An HTTPS request from a client to a JSON response, in one picture:
The stack, layer by layer
| Layer | Choice | Why |
|---|---|---|
| Language | Kotlin | One language across backend and Android. Modern, expressive, null-safe. |
| Backend framework | Ktor 3 | Coroutine-first, minimal, designed by JetBrains. Clean fit for Kotlin idioms. |
| Database | PostgreSQL 16 | Boring, battle-tested, free. CITEXT, JSON, generated columns when needed. |
| SQL framework | Exposed | Typed Kotlin DSL over JDBC. Repository pattern, no ORM magic. |
| Migrations | Flyway | Versioned, idempotent, ships with the app. Source of truth for schema. |
| Auth | JWT + BCrypt | Stateless tokens, server-side hashing at cost 12. Separate auth provider for admin routes. |
| Connection pool | HikariCP | The standard. Configured for the app's expected concurrency. |
| Pagination | Cursor + offset | Cursor for infinite-scroll feeds (posts), offset for bounded resources (matches). |
| Mobile client | Native Android | Jetpack Compose, Material 3, MVVM + Clean, Paging 3, Hilt. |
| Hosting | Oracle Cloud (Always Free) | Ampere A1 ARM, 2 OCPU / 12 GB RAM. Zero cost at this scale. |
| Reverse proxy | nginx | TLS termination, path-based routing, home for the static sites too. |
| TLS | Let's Encrypt | Auto-issued, auto-renewed via certbot's systemd timer. |
| CI/CD | GitHub Actions | Build, push to GHCR, SSH deploy on every push to main. |
| Container registry | GitHub Container Registry | Free for public images, lives next to the source. |
Android Client Architecture
The Android client is a native app engineered to represent Google's Modern Android Development (MAD) standards. It is built as a reactive, single-activity application that mirrors the backend's clean engineering principles.
Key Architectural Choices
Clean Architecture & MVVM:
The app is structured feature-by-feature (e.g., matches, posts, notifications, auth). Inside each feature, the presentation layer (Compose + ViewModels) interacts with pure-Kotlin use cases and domain entities, keeping business logic isolated from Android framework dependencies and database/network details.
Type-Safe Navigation (Compose 2.8+):
Instead of error-prone string routes like "match_detail/{id}", destinations are declared as compile-safe @Serializable data classes and objects. Arguments are checked at compile-time and parsed automatically, preventing runtime routing crashes.
Reactive Session & App-Control Interceptor: A centralized OkHttp interceptor manages security and application flow at the network layer. It automatically attaches auth headers and intercepts response status codes to manage:
- 401 Unauthorized: Reactively wipes the local DataStore session and redirects the user to the welcome screen.
- 426 Upgrade / 503 Maintenance: Buffers response bodies in-flight to check for app blocking states, notifying the app status manager to show system overlay blockers globally.
- 503 Maintenance: Buffers response bodies in-flight to check for maintenance states, letting the app reactively show maintenance screens.
Unified Cursor-Based Pagination:
Feed rendering relies on Jetpack Paging 3. To avoid duplicated logic, a generic PostPagingSource accepts a suspension lambda to perform network requests. This allows the exact same pagination implementation to drive the global feed, match threads, and user profile posts.
FCM Integration with Graceful Mock Fallback:
Real-time push notifications use Firebase Cloud Messaging to refresh feed feeds and update unread counts reactively. To ensure a smooth out-of-the-box build experience for other developers, a custom Gradle task copies a mock configuration template if the real google-services.json is missing, allowing the app to compile and run with silent notification degradation.