This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A starter template for building mini apps for you and your friends. Uses Local First Auth spec for user signup and authentication, SQLite database, WebSocket for real-time updates, REST API for backend endpoints.
This is a pnpm workspace monorepo with three packages:
| Package | Description |
|---|---|
client/ |
React frontend |
server/ |
Cloudflare Workers, D1 (SQLite), Durable Objects (WebSocket) |
shared/ |
Shared utilities (JWT verification) |
/client/src/components/- React componentsQRCodePanel.tsx- QR code for app (hidden on mobile, visible on desktop)Avatar.tsx- User avatar or placeholderAdminSection.tsx- Admin-only controls for resetting the eventFooter.tsx- Footer with attribution link
/client/src/hooks/- React hooksuseLocalFirstAuth.tsx- Authentication state management, exportsAuthProvideranduseLocalFirstAuth()hookuseWebSockets.ts- WebSocket connection hook for real-time updates
/client/src/routes/- Route componentsindex.tsx- React Router root routehome.tsx- Home pagenot-found.tsx- 404 page
/client/src/app.tsx- Main component with React Router and Local First Auth integration/client/src/main.tsx- Entry point (initializes Local First Auth Simulator whenVITE_ENABLE_LOCAL_FIRST_AUTH_SIMULATOR=true)/client/public/- Public fileslocal-first-auth-manifest.json- Mini app manifest with metadata and requested permissionsicon.webp- Mini app icon
/client/vite.config.ts- Vite configuration with proxy to backend
/server/src/index.ts- Cloudflare Workers entry point with Hono router, API endpoints, and WebSocket handling/server/src/durable-object.ts- Durable Object class for real-time WebSocket connections (WebSocket message types defined inline)/server/src/db/client.ts- Database client factory for Cloudflare D1/server/src/db/schema.ts- Database schema (used by Drizzle Kit to generate migrations)/server/src/db/models/index.ts- Export file for all models/server/src/db/models/users.ts- User database model/server/src/db/migrations/- D1 SQL migration files (auto-generated)/server/drizzle.config.js- Drizzle Kit configuration for migrations/server/src/types.ts- Type definitions for Cloudflare Workers environment bindings
/shared/src/index.ts- Main export file for shared utilities/shared/src/jwt.ts- JWT decoding and verification utilities (decodeAndVerifyJWT,decodeJWT)
/docs/- Documentationlocal-first-auth-spec.md- Local First Auth Specificationmini-app-examples.md- Reference examples and links to other mini appsadmin-setup.md- Admin setup instructionsport-troubleshooting.md- Port troubleshooting instructions
/scripts/- Helper scriptssetup.ts- Project setup script (renames template to your app name)build-client-if-missing.ts- Builds client if dist doesn't exist (runs before dev via predev hook)run-dev-migrations.ts- Database migration script for local development
alchemy.run.ts- Alchemy deployment configuration for Cloudflare Workerspnpm-workspace.yaml- Workspace configuration.alchemy/state.json- Tracks your infrastructure (created after first deployment)wrangler.toml- Cloudflare configuration (used in development only)
All commands run from the workspace root:
pnpm install # Install all workspace dependencies
pnpm run dev # Start dev server (no simulator)
pnpm run dev:simulator # Start dev server with Local First Auth Simulator
pnpm run dev:client # Start only client dev server (no simulator)
pnpm run dev:server # Start only Wrangler dev server (Cloudflare Workers local mode)
pnpm run build # Build shared package, then client
pnpm run build:client # Build only client packagePre-Dev Hook: The pnpm run dev command automatically runs a predev hook that executes build-client-if-missing.ts to build the client if the dist folder doesn't exist.
Note: This is a pnpm workspace. All dependencies are installed at the root level. Shared dependencies (@noble/curves, base58-universal, jwt-decode, drizzle-orm) are hoisted to the workspace root.
When to run these steps: When the user asks to "set up", "initialize", or "rename" this project.
Run: pnpm setup-project {app-name} (defaults to current directory name). See Project Setup docs for details.
We use the library local-first-auth to easily add auth and a simple onboarding flow to the mini app. It uses the Local First Auth spec to simplify the signing up and onboarding process.
How it works:
- User creates a one-time account (no passwords, no email, no signup)
- Profile details are stored client-side in the browser's local storage
- The
window.localFirstAuthAPI is injected into the page - Mini app calls
getProfileDetails()to access the user's profile details
See /docs/local-first-auth-spec.md for the full specification.
To use a test user account without having to go through the onboarding flow, you can use the Local First Auth Simulator. The simulator injects the window.localFirstAuth API into a regular browser, allowing you to test your mini app locally without having to scan a QR code.
pnpm dev:simulator # Start dev server with simulator enabled (floating debug panel visible)
pnpm dev # Start dev server without simulatorNote: The simulator is a development-only tool and should never be used in production.
Auth States:
| State | Condition | What to Show |
|---|---|---|
| Loading | loading === true |
Loading spinner |
| Logged Out | user === null |
Onboarding trigger |
| Logged In | user !== null |
User content |
Pattern:
import { useLocalFirstAuth } from '../hooks/useLocalFirstAuth'
function MyComponent() {
const { user, setIsOnboardingModalOpen, getProfileJwt } = useLocalFirstAuth()
// For UI that requires login
if (!user) {
return <button onClick={() => setIsOnboardingModalOpen(true)}>Add a thing</button>
}
// For actions that require auth
const handleAction = async () => {
const profileJwt = await getProfileJwt()
if (!profileJwt) {
setIsOnboardingModalOpen(true)
return
}
// Make authenticated API call with profileJwt
}
return <div>Welcome, {user.name}!</div>
}Auth checks:
!user- Quick sync check for UI rendering!profileJwt- Use when making API calls (async)
Server endpoints verify JWTs using decodeAndVerifyJWT from @starter/shared. The function validates the signature, expiration, and returns the typed payload. See /shared/src/jwt.ts for implementation.
The app uses a single Durable Object instance (idFromName: 'default') for WebSocket broadcasting:
- Client connects: WebSocket upgrade request → Worker → Durable Object
- User action: POST endpoint → Worker verifies JWT → Saves to D1 → Notifies DO
- Broadcast: Durable Object sends WebSocket message to all connected clients
- Auto-eviction: Cloudflare automatically evicts DO when all connections close
Defined inline in /server/src/durable-object.ts and /server/src/index.ts.
Client ← Server:
| Type | Description |
|---|---|
connected |
Initial connection confirmation |
user-joined |
New user or updated user profile |
user-left |
User removed |
- Mobile: Single column, QR code hidden
- Desktop: Two columns with QR code panel on left
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/add-user |
Add or update user profile | JWT required |
POST |
/api/add-avatar |
Add or update user avatar | JWT required |
DELETE |
/api/remove-user |
Remove user | JWT required |
GET |
/api/users |
Get all users from database | Public |
GET |
/api |
Health check | Public |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/ws |
Establish WebSocket connection for real-time updates |
Run from workspace root unless noted:
pnpm db:generate-migrations # Generate D1 migration files from schema (from /server/)
pnpm db:run-migrations # Run all pending migrations on local D1 database
pnpm db:push # Push schema changes directly without migrations (from /server/)
pnpm db:studio # Open Drizzle Studio for database inspection (from /server/)- Edit schema in
/server/src/db/schema.ts - Generate migration:
pnpm db:generate-migrations(usesdrizzle.config.jsto read schema) - Apply locally:
pnpm db:run-migrations(runsrun-dev-migrations.ts) - Deploy to production:
pnpm run deploy:cloudflare(Alchemy automatically applies migrations viamigrationsDir)
Use the Wrangler CLI to run SQL queries against D1 databases.
Development (Local D1):
pnpm wrangler d1 execute meetup-cloudflare-dev-db --local --command "SELECT * FROM users;"Production (Remote D1):
# Find the database name (format: meetup-irl-<stage>-db)
pnpm wrangler d1 list
# Run a query
pnpm wrangler d1 execute meetup-irl-prod-db --remote --command "SELECT * FROM users;"Or log in to the Cloudflare dashboard, go to the D1 database, and run SQL queries directly.
This project uses Alchemy for deployment to Cloudflare Workers.
alchemy.run.ts- Alchemy configuration file defining the Cloudflare Worker, D1 database, and Durable Object bindings.alchemy/state.json- Created after first deployment, tracks infrastructure state
pnpm run deploy:cloudflare # Deploy to Cloudflare
pnpm run destroy:cloudflare # Destroy Alchemy deploymentNo manual migration steps needed - everything is handled by alchemy.run.ts configuration.
- React - UI framework
- Tailwind CSS - Utility-first CSS framework
- React Router - Routing for the app
- qrcode.react - QR code generation
- local-first-auth - Authentication library using the Local First Auth spec
- local-first-auth-simulator - Simulates different test users (dev only)
- Vite - Build tool and dev server
- Hono - Lightweight REST API framework for Cloudflare Workers
- Drizzle ORM - TypeScript ORM for D1 database operations
- Drizzle Kit - Migration generator and database studio
- Cloudflare Workers - Serverless runtime environment
- Cloudflare D1 - Serverless SQLite database
- Cloudflare Durable Objects - Stateful WebSocket coordination
- @noble/curves - Ed25519 signature verification
- base58-universal - Base58 encoding/decoding for DIDs
- jwt-decode - JWT decoding
- drizzle-orm - Database ORM
- Alchemy - Infrastructure as Code tool for deploying to Cloudflare
JWT Verification Failures:
- Expired JWT (
expclaim) - Invalid signature
- Malformed DID (must start with
did:key:z) - Audience claim mismatch (must match production URL)
Profile Not Loading:
- Check if API exists:
console.log(window.localFirstAuth)
Build Errors:
- Run
pnpm install - Check TypeScript errors:
pnpm run build
Port Already in Use (8787): See Port Troubleshooting