Data Fetching & Auth
How the web app talks to the backend, and how sessions work. Understanding this page saves the most onboarding time — it is the least obvious part of the codebase.
The proxy layer
There is no Vite dev proxy. Instead, the app ships its own same-origin proxy built from TanStack Start server routes under src/routes/api/**:
Browser ──▶ /api/os-tickets/… (same origin, TanStack Start server route)
│
▼ forwardBackendRequest()
VITE_API_BASE_URL + /api/v1/tickets/… (real backend via gateway)
- Each backend domain has a
-proxy.tshelper (e.g.src/routes/api/os-tickets/-proxy.ts,src/routes/api/os-auth/-proxy.ts). - All of them delegate to
forwardBackendRequestinsrc/routes/api/-backend-proxy.ts, which rewrites the URL ontoVITE_API_BASE_URLplus the versioned backend base path (tickets →/api/v1/tickets, auth →/api/v1/auth/), preserving the query string. src/routes/api/-forward-headers.tsattaches auth: it forwards an incomingAuthorizationheader, otherwise injectsBearer <accessToken>from the stored session. Auth endpoints opt out withincludeAuth: false.
Making requests from components
Use requestJson from src/libs/http-client/request.ts:
target: "app"— call the same-origin/api/...proxy routes (the normal path; works in SSR too).target: "backend"— hit the backend directly via the axios client (src/libs/http-client/client.ts).
requestJson understands the backend's response envelope { data, message, path, status, timestamp } and unwraps data automatically. Pass a Zod schema to validate responses. A 401 is surfaced as a "session expired" error.
Wrap calls in TanStack Query queryOptions inside the feature's -api/ or -resources/ folder — see any existing feature (e.g. src/routes/dashboard/ticketing/tickets/-api/) for the pattern to copy.
Auth and sessions
Auth is a custom JWT flow implemented in src/libs/auth/:
- Login methods: credentials (
/api/os-auth/admin-login) and Google OAuth (google-start/google-complete, callback route atlogin/google/callback.tsx). - Session storage: a cookie (name from
AUTH_SESSION_COOKIE_NAME,samesite=lax) holding URL-encoded JSON with access/refresh tokens, role, and permissions decoded from the JWT. Legacy localStorage sessions are auto-migrated to the cookie. - SSR: the cookie is readable server-side, so
resolveAuthUser()renders the same authenticated UI on the server — no hydration mismatch. - Refresh & logout:
refreshStoredAuthSession()refreshes an expired access token;logoutCurrentSession()calls/logoutand clears the cookie. - Route protection:
src/routes/dashboard/route.tsxredirects to/logininbeforeLoadwhen there is no auth user, and wraps the dashboard inAccessControlProvider(permission checks come fromr3-utils/access-control).
better-auth appears in package.json and the README, but the live flow is the custom JWT/cookie implementation described above. Don't assume Better Auth patterns apply.
Environment variables
Validated with @t3-oss/env-core in src/env.ts — the app fails at startup if required values are missing.
| Variable | Scope | Notes |
|---|---|---|
VITE_API_BASE_URL | client | Required URL of the backend gateway (e.g. http://localhost:8080 for local backend) |
VITE_LOG_CLIENT | client | Client-side logging, default true |
BETTER_AUTH_SECRET | server | Required — generate with npx @better-auth/cli secret |
SESSION_TIMEOUT | server | Default 300000 ms |
LOG_API_CALLS | server | Log proxied API calls, default true |
LOG_DB | server | Default true |
Copy .env.example to .env and fill these in before first run.