System Architecture Overview
Last updated: 2026-06-25
Production domains:
luckyplans.xyzfor the landing SPAdocs.luckyplans.xyzfor the documentation SPAbeta.luckyplans.xyzfor the product web appapi.luckyplans.xyzfor the gatewayadmin.luckyplans.xyzfor admin surfacesv0.api.luckyplans.xyzfor the legacy API proxy
Architecture Diagram
Browser
-> landing SPA (public)
-> docs SPA (public)
-> web app (authenticated)
-> GraphQL and auth routes
web app
-> api-gateway
api-gateway
-> Keycloak
-> Redis
-> PostgreSQL via Prisma
-> MinIO
-> edge internal APIs
edge-agent
<-> api-gateway
Service Map
| Service | Purpose | Communication |
|---|---|---|
| Keycloak | Identity provider and user management | Gateway to Keycloak server-side |
apps/landing | Public landing SPA | Static assets through ingress |
apps/docs | Public documentation and blog SPA | Static assets through ingress |
apps/web | Product web app | GraphQL to api-gateway |
apps/api-gateway | GraphQL API, auth endpoints, edge control plane, session management | GraphQL and internal REST |
apps/edge-agent | External edge runtime for onboarding, heartbeats, task execution, and upgrade flow | Internal REST to gateway |
Architectural Decisions
Services are split by functionality, not by domain:
api-gatewayowns client-facing orchestration and shared domain operations- edge runtimes execute distributed workloads and report state back to the gateway
- new microservices are added only when workload or operational isolation is justified
- shared contracts and utilities belong in
packages/shared
Auth Flow
- A user requests a protected route.
- Next.js middleware checks for the
session_idcookie. - If missing, the user is redirected to
/login. - The login form posts to
POST /auth/login. - The gateway authenticates against Keycloak with ROPC, creates a Redis session, and sets an HttpOnly cookie.
- Future GraphQL requests include the cookie automatically.
SessionGuardloads the session, refreshes server-side credentials when needed, and injects user context.
Registration follows the same pattern through POST /auth/register, with user creation handled through the Keycloak Admin API.
Request Lifecycle
- The frontend sends a GraphQL operation through Apollo Client.
- The gateway validates the session and populates request user context.
- Resolver and service logic run in the gateway.
- Domain data is read from or written to PostgreSQL through Prisma.
- The gateway returns the GraphQL response to the frontend.
Edge Runtime Lifecycle
- The edge starts and loads local config.
- If config is missing and the runtime is interactive, onboarding collects display name, server URL, and registration token.
- The gateway registers or updates the worker and issues credentials.
- The edge sends connectivity heartbeats and reports version state.
- The edge polls tasks and reports results.
- Upgrades run only when the edge is idle.
Shared Packages
| Package | Purpose |
|---|---|
packages/shared | Entity interfaces, DTOs, message pattern enums, and utility functions such as getEnvVar, getRedisConfig, and generateId |
packages/config | Shared ESLint and TypeScript configuration |
Infrastructure
- Build: Turborepo with pnpm workspaces
- Local development: Docker Compose for Redis, PostgreSQL, Keycloak, and MinIO
- Public surface:
apps/landingandapps/docsare isolated from the authenticated product app - Deployment: Helm on Kubernetes, with ArgoCD for production sync
- Auth: Keycloak plus gateway-managed Redis-backed sessions
- Docs: standalone
apps/docsDocusaurus static deployment ondocs.luckyplans.xyz
See also:
Current State and Known Limitations
- Worker upgrades are policy-driven and run only when idle.
- Connectivity is heartbeat-based and does not explain root cause by itself.
- Local development still depends on Docker Compose infrastructure before
pnpm dev. - The gateway is stateful because sessions live in Redis.
- Blog pages are static placeholders.
- Portfolio image uploads still rely on external URLs rather than managed uploads.
- Public profile discovery is direct-link based and has no search or directory yet.