Screenshot 2026-06-29 at 5.44.36 PM.png

Screenshot 2026-06-29 at 5.55.07 PM.png

The vertical spine is the request and data path. The Web client (served as static assets from Vercel, running in the browser) talks only to the Express API over HTTPS, attaching a JWT access token held in memory. Everything heavy is pushed off the request path: when you add a repo, the API upserts the Repository record, enqueues a job, and returns immediately, while the actual GitHub pagination happens later in the Sync worker. That is why the worker is a separate Railway process, not part of the API.

There are two distinct write paths into MongoDB. The API writes directly for lightweight, synchronous data (User on OAuth login, Repository on add), shown by the looping arrow on the left. The worker writes the bulk analytics data (Commit, PullRequest) after pulling from GitHub. Reads for the dashboard run the other direction: /api/metrics/commits and /api/metrics/cycle-time are Mongo aggregation pipelines that group commits by day and average PR cycle time.

GitHub connects in three ways, which is the most important external boundary: OAuth (the API initiates the handshake and exchanges the code for a token), webhooks (GitHub POSTs push and pull_request events to the API, verified with an HMAC signature over the raw request body — which is why that route is mounted before express.json()), and the REST API (the worker pulls commits and PRs, pausing when X-RateLimit-Remaining gets low). The queue uses a TLS rediss:// connection to Upstash in production.

The webhook ingestion path exists end to end at the queue level, but processPushEvent and processPREvent in services/sync.js are currently stubs that only log — the incremental update logic isn't implemented yet, so in practice only processFullSync does real work. And the client's axios interceptor calls POST /api/auth/refresh (and the silent-refresh-then-logout pattern assumes a logout route), but routes/auth.js only implements /github, /github/callback, and /me. So the "401 to silent refresh" arrow your auth notes describe doesn't have a server endpoint behind it yet.

Not shown in either diagram, since it sits outside the request path: GitHub Actions runs lint and build on every push to main, then fans out to two parallel deploy jobs — the client to Vercel and the server to Railway via the Railway CLI container.

System context

flowchart LR
  user(["User browser"])
  subgraph Vercel
    spa["Web client - React + Vite"]
  end
  subgraph Railway
    api["Express API"]
    worker["Sync worker"]
  end
  subgraph Upstash
    redis[("Redis / BullMQ")]
  end
  subgraph Atlas
    mongo[("MongoDB")]
  end
  gh["GitHub - OAuth, REST, Webhooks"]
  user --> spa
  spa -->|"HTTPS / JWT"| api
  api -->|"enqueue"| redis
  redis -->|"dequeue"| worker
  api -->|"read / write"| mongo
  worker -->|"upsert"| mongo
  api <-->|"OAuth / webhooks"| gh
  worker -->|"REST sync"| gh

Frontend architecture

flowchart TD
  main["main.jsx"] --> app["App.jsx - BrowserRouter + QueryClient"]
  app --> login["Login (/login)"]
  app --> cb["AuthCallback (/auth/callback)"]
  app --> prot{"ProtectedRoute - useAuth"}
  prot -->|"authed"| shell["AppShell"]
  prot -->|"no user"| login
  shell --> dash["Dashboard"]
  shell --> repo["RepoDetail"]
  shell --> comp["Compare"]
  shell --> settings["RepoSettings"]
  dash --> rq["TanStack Query"]
  settings --> client["axios client - token in memory"]
  rq --> client
  client -->|"Bearer JWT"| api["Express API"]

Auth notes


Backend architecture

flowchart TD
  idx["index.js - Express app"]
  idx --> mw["helmet / cors / morgan"]
  idx --> wh["/api/webhooks (raw body)"]
  idx --> auth["/api/auth"]
  idx --> repos["/api/repos"]
  idx --> metrics["/api/metrics"]
  auth --> authn["authenticate (JWT)"]
  repos --> authn
  metrics --> authn
  repos --> queue["syncQueue (BullMQ)"]
  wh --> queue
  metrics --> msvc["metrics service - aggregations"]
  auth --> models[("Mongoose models")]
  msvc --> models
  queue --> redis[("Redis")]
  idx --> err["errorHandler"]

Data model