XStack is a pnpm + Turborepo monorepo with a React workspace app and an Elysia API.
It ships a complete authenticated task workflow:
- Supabase auth in the frontend
- JWT verification in the API
- Postgres persistence via Prisma
- Typed client calls from app -> API
- Unit/integration/contract tests and Playwright e2e coverage
| Path | Purpose |
|---|---|
apps/app |
React 19 + Vite frontend (Mantine UI, TanStack Router, React Query, i18n) |
packages/api |
Elysia API (modular monolith), Prisma persistence, OpenAPI exposure |
packages/api-client |
Shared typed API client + error normalization |
packages/config |
Shared TypeScript base config |
supabase |
Local Supabase CLI config |
playwright.config.ts |
End-to-end test config (starts API + app web servers) |
- React + Vite + TypeScript
- Route-level auth guards with TanStack Router
- Supabase auth session management
- React Query with centralized error pipeline and toast deduplication
- Workspace shell with command palette, responsive sidebar, language/theme toggles
- Task page supports create/list/update/delete, pagination, sorting, and status filters
- Elysia app composition rooted at
packages/api/src/app.ts - Modular monolith slice pattern:
core/*: runtime config, request context, error and envelope model, pluginsmodules/auth/*: auth provider port + Supabase/JWT adaptermodules/tasks/*: domain, use-cases, repository port + Prisma adapter, HTTP routesmodules/system/*: status/health endpoints
- Dependency wiring in
packages/api/src/bootstrap/create-container.ts - Consistent response envelopes and
x-request-idpropagation - OpenAPI served at
/openapiand/openapi/json
Prisma schema currently includes a Todo model (todos table):
id(UUID, PK)user_id(tenant/user scope)titleis_donecreated_at,updated_at- index on
user_id
- Node.js (Node 20+ recommended; local dev currently uses Node
v22.15.1) pnpm(workspace usespnpm@10.6.5)- Postgres database
- Supabase project (local CLI stack or hosted project) for auth/JWT
pnpm installCreate apps/app/.env.local:
VITE_SUPABASE_URL=...
VITE_SUPABASE_ANON_KEY=...Create packages/api/.env.local:
DATABASE_URL=postgresql://...
DATABASE_URL_TEST=postgresql://...
SUPABASE_JWT_SECRET=...
SUPABASE_JWT_ISS=...
# optional
SUPABASE_JWKS_URL=
PORT=54545Notes:
- API runtime requires
DATABASE_URLandSUPABASE_JWT_SECRET. DATABASE_URL_TESTis used by API tests (test/setup.tsoverridesDATABASE_URLwith it).SUPABASE_JWT_ISSis optional but recommended for stricter token validation.SUPABASE_JWKS_URLis optional and used for non-HS JWT algorithms.
pnpm -C packages/api prisma migrate deploypnpm devDefault local URLs:
- Frontend:
http://127.0.0.1:8878 - API health:
http://127.0.0.1:54545/health - API OpenAPI JSON:
http://127.0.0.1:54545/openapi/json
A Supabase CLI config exists under supabase/config.toml.
Start local services:
pnpm supabase startUseful defaults from config:
- API:
http://127.0.0.1:54321 - DB:
postgresql://postgres:postgres@127.0.0.1:54322/postgres - Studio:
http://127.0.0.1:54323
Use local Supabase output (for URL/keys/JWT settings) to populate .env.local files.
| Command | What it does |
|---|---|
pnpm dev |
Run workspace dev tasks via Turbo |
pnpm build |
Build all packages/apps with build scripts |
pnpm test |
Run workspace test tasks via Turbo |
pnpm test:e2e |
Run Playwright e2e tests |
pnpm test:e2e:chromium |
Run Playwright only on Chromium |
pnpm test:e2e:ui |
Open Playwright UI mode |
pnpm lint |
Run Oxlint across workspace |
pnpm lint:fix |
Auto-fix lint issues where possible |
pnpm fmt |
Format with OXC formatter |
pnpm fmt:check |
Check formatting without writing |
pnpm typecheck |
Run workspace type checks |
pnpm -C packages/api dev
pnpm -C packages/api test
pnpm -C packages/api build
pnpm -C apps/app dev
pnpm -C apps/app buildGET /-> service statusGET /health-> liveness
All routes below require Authorization: Bearer <access_token>:
GET /api/v1/todosPOST /api/v1/todosPATCH /api/v1/todos/:idDELETE /api/v1/todos/:id
Query options for GET /api/v1/todos:
page(>= 1)pageSize(1-100)sortBy(createdAt|updatedAt|title)sortOrder(asc|desc)status(all|todo|done)
Success envelope:
{
"data": {},
"meta": {
"requestId": "uuid"
}
}Error envelope:
{
"error": {
"code": "ERROR_CODE",
"message": "Message",
"requestId": "uuid",
"details": {}
}
}Every response includes x-request-id header.
Covers:
- health/system endpoints
- OpenAPI contract exposure
- auth guard behavior
- full todo CRUD and envelope contracts
- pagination/sorting/filtering
- tenant boundary behavior
- task use-case unit tests
Run:
pnpm -C packages/api testRequirements:
- reachable test database (
DATABASE_URL_TEST) SUPABASE_JWT_SECRETconfigured for signed test JWT creation
Covers:
- auth entry flow and redirects
- sign-up/sign-in flow
- task CRUD interactions in UI
- language/theme persistence
- command palette behavior
- mobile navigation drawer
- global toast handling for API/auth failures
Run:
pnpm test:e2eNotes:
- Playwright config auto-starts API (
packages/api) and app (apps/app) dev servers. - You still need working env configuration (Supabase + DB) before e2e tests.
- API maps framework/domain/db failures to normalized error envelopes.
- API error codes include:
AUTH_MISSING_TOKENAUTH_INVALID_TOKENTASK_NOT_FOUNDVALIDATION_ERRORPARSE_ERRORROUTE_NOT_FOUNDDATABASE_UNAVAILABLEDATABASE_ERRORINTERNAL_ERROR
- Frontend maps API error codes to localized user-facing toasts.
- Unauthorized responses trigger coordinated sign-out + redirect to
/auth.
Use packages/api/templates/module as the starting pattern.
Checklist:
- Define domain model and repository port.
- Implement use-cases against the port.
- Add infrastructure adapter.
- Add HTTP schemas/routes.
- Wire dependencies in
create-container.tsand route registration inapp.ts. - Add unit + integration tests.
The API loads .env.local then .env in packages/api and fails fast when required values are missing.
Usually means JWT settings are mismatched:
- check
SUPABASE_JWT_SECRET - check
SUPABASE_JWT_ISS - if using asymmetric tokens, set
SUPABASE_JWKS_URL(or ensure issuer JWKS endpoint is reachable)
Confirm your Postgres instance is running and DATABASE_URL is reachable from packages/api.
The app proxies /api and /health to http://localhost:54545 via Vite config. Ensure API dev server is up on port 54545.
- Existing package-specific docs:
packages/api/README.mdapps/app/README.md(currently default Vite template text)
- Secrets are intentionally gitignored (
.env,.env.*).