Files
monitoring-app/CLAUDE.md
bipproduction 08a1054e3c Initial commit: full-stack Bun + Elysia + React template
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>
2026-04-01 10:12:19 +08:00

3.8 KiB

Default to using Bun instead of Node.js.

  • Use bun <file> instead of node <file> or ts-node <file>
  • Use bun test instead of jest or vitest
  • Use bun install instead of npm install or yarn install or pnpm install
  • Use bun run <script> instead of npm run <script>
  • Use bunx <package> <command> instead of npx <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 via app.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 with Bun.password.hash bcrypt
  • 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 with Bun.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.tsuseSession(), useLogin(), useLogout()
  • UI: Mantine v8 (dark theme #242424), react-icons
  • Splash: index.html has inline dark CSS + spinner, removed on React mount

Dev Tools

  • Click-to-source: Ctrl+Shift+Cmd+C toggles inspector. Custom Vite plugin (inspectorPlugin in src/vite.ts) injects data-inspector-* attributes. Reads original file from disk for accurate line numbers.
  • HMR: Vite 8 with @vitejs/plugin-react v6. dedupeRefreshPlugin fixes double React Refresh injection.
  • Editor: REACT_EDITOR env var. zed and subl use file: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.tscreateTestApp(), 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 use host.docker.internal from container. Lightpanda executes JS but POST fetch returns 407 — use integration tests for mutations.

APIs

  • Bun.password.hash() / Bun.password.verify() for bcrypt
  • Bun.file() for static file serving in production
  • Bun.which() / Bun.spawn() for editor integration
  • crypto.randomUUID() for session tokens