

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.
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
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
localStorage)sessionStorage/api/auth/refresh → retry or redirect to /loginflowchart 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"]