Elysia.js API with session-based auth (email/password + Google OAuth), role system (USER/ADMIN/SUPER_ADMIN), Prisma + PostgreSQL, React 19 with Mantine UI, TanStack Router, dark theme, and comprehensive test suite (unit, integration, E2E with Lightpanda). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.8 KiB
3.8 KiB
Default to using Bun instead of Node.js.
- Use
bun <file>instead ofnode <file>orts-node <file> - Use
bun testinstead ofjestorvitest - Use
bun installinstead ofnpm installoryarn installorpnpm install - Use
bun run <script>instead ofnpm run <script> - Use
bunx <package> <command>instead ofnpx <package> <command> - Bun automatically loads .env, so don't use dotenv.
Server
Elysia.js as the HTTP framework, running on Bun. API routes are in src/app.ts (exported as createApp()), frontend serving and dev tools are in src/index.tsx.
src/app.ts— Elysia app factory with all API routes (auth, hello, health, Google OAuth). Testable viaapp.handle(request).src/index.tsx— Server entry. Adds Vite middleware (dev) or static file serving (prod), click-to-source editor integration, and.listen().src/serve.ts— Dev entry (bun --watch src/serve.ts). Dynamic import workaround for Bun EADDRINUSE race.
Database
PostgreSQL via Prisma v6. Client generated to ./generated/prisma (gitignored).
- Schema:
prisma/schema.prisma— User (id, name, email, password, timestamps) + Session (id, token, userId, expiresAt) - Client singleton:
src/lib/db.ts— import{ prisma }from here - Seed:
prisma/seed.ts— demo users withBun.password.hashbcrypt - Commands:
bun run db:migrate,bun run db:seed,bun run db:generate
Auth
Session-based auth with HttpOnly cookies stored in DB.
- Login:
POST /api/auth/login— finds user by email, verifies password withBun.password.verify, creates Session record - Google OAuth:
GET /api/auth/google→ Google →GET /api/auth/callback/google— upserts user, creates session - Session:
GET /api/auth/session— looks up session by cookie token, returns user or 401, auto-deletes expired - Logout:
POST /api/auth/logout— deletes session from DB, clears cookie
Frontend
React 19 + Vite 8 (middleware mode in dev). File-based routing with TanStack Router.
- Entry:
src/frontend.tsx— renders App, removes splash screen, DevInspector in dev - App:
src/frontend/App.tsx— MantineProvider (dark, forced), QueryClientProvider, RouterProvider - Routes:
src/frontend/routes/—__root.tsx,index.tsx,login.tsx,dashboard.tsx - Auth hooks:
src/frontend/hooks/useAuth.ts—useSession(),useLogin(),useLogout() - UI: Mantine v8 (dark theme
#242424), react-icons - Splash:
index.htmlhas inline dark CSS + spinner, removed on React mount
Dev Tools
- Click-to-source:
Ctrl+Shift+Cmd+Ctoggles inspector. Custom Vite plugin (inspectorPlugininsrc/vite.ts) injectsdata-inspector-*attributes. Reads original file from disk for accurate line numbers. - HMR: Vite 8 with
@vitejs/plugin-reactv6.dedupeRefreshPluginfixes double React Refresh injection. - Editor:
REACT_EDITORenv var.zedandsublusefile:line:col, others use--goto file:line:col.
Testing
Tests use bun:test. Three levels:
bun run test # All tests
bun run test:unit # tests/unit/ — env, db connection, bcrypt
bun run test:integration # tests/integration/ — API endpoints via app.handle()
bun run test:e2e # tests/e2e/ — browser tests via Lightpanda CDP
tests/helpers.ts—createTestApp(),seedTestUser(),createTestSession(),cleanupTestData()- Integration tests use
createApp().handle(new Request(...))— no server needed - E2E tests use Lightpanda browser (Docker,
ws://127.0.0.1:9222). App URLs usehost.docker.internalfrom container. Lightpanda executes JS but POST fetch returns 407 — use integration tests for mutations.
APIs
Bun.password.hash()/Bun.password.verify()for bcryptBun.file()for static file serving in productionBun.which()/Bun.spawn()for editor integrationcrypto.randomUUID()for session tokens