A production-ready authentication starter kit built with Better Auth, featuring multiple authentication methods, account linking, and a modern tech stack. Deploy to Cloudflare Workers or any Node.js / Bun host.
- Email & Password - Sign up/sign in with email verification
- Password Reset - Secure reset via email link
- Email OTP - 6-digit one-time password verification
- Magic Link - Passwordless email sign-in
- Social OAuth - Twitter/X and Google integration
- Web3 (SIWE) - Sign-In With Ethereum wallet authentication
- Passkey (WebAuthn) - Passwordless auth with biometrics or device PIN
- Account Linking - Connect multiple auth methods to one account
- Cloudflare Workers + D1 - Serverless, globally distributed (default)
- Node.js / Bun + SQLite - Self-hosted on any VPS or Docker
- Same codebase, same auth logic — just a different entry point
Choose quickly, then open the detailed guide:
| Option | Best For | Ops Effort | Read More |
|---|---|---|---|
| Cloudflare Workers + D1 | Fastest production setup, lowest maintenance | Low | Cloudflare Deploy Workflow |
| Bun + SQLite | Full control on your own server/VPS | Medium | Bun + SQLite Deploy Workflow |
| Docker (Bun + SQLite) | Reproducible self-hosted environment | Medium | Docker Setup |
Need a fuller comparison first: Deployment Decision Table
Need all deployment details: Deployment Guide
- Bun runtime
- Wrangler CLI (for Cloudflare deployment)
# Clone the repository
git clone https://github.com/whereissam/better-auth-template.git
cd better-auth-template
# Install dependencies
cd backend && bun install && cd ../frontend && bun install && cd ..
# Set up local D1 database
cd backend
cp .dev.vars.example .dev.vars # edit with your secrets
bun run db:migrate:local
# Start backend (Wrangler dev server)
bun run devIn a new terminal:
cd frontend && bun run devgit clone https://github.com/whereissam/better-auth-template.git
cd better-auth-template
cd backend && bun install && cd ../frontend && bun install && cd ..
# Configure environment
cd backend
cp .env.example .env # edit with your secrets
# Start backend (auto-creates SQLite DB and runs migrations)
bun run dev:nodeIn a new terminal:
cd frontend && bun run devgit clone https://github.com/whereissam/better-auth-template.git
cd better-auth-template
cp backend/.env.example backend/.env # edit with your secrets
docker compose up -dOpen http://localhost:4000 in your browser.
| Service | URL | Description |
|---|---|---|
| Frontend | http://localhost:4000 | React application |
| Backend (Wrangler) | http://localhost:8787 | Cloudflare Workers dev |
| Backend (Node.js) | http://localhost:4200 | Node.js / Bun server |
| Command | Description |
|---|---|
bun run dev:local |
Start backend + frontend concurrently |
docker compose up -d |
Start all services (Docker) |
docker compose down |
Stop all services |
bun run test |
Run all tests |
| Command | Description |
|---|---|
bun run dev |
Start Wrangler dev server (D1) |
bun run dev:node |
Start Node.js/Bun server (SQLite) |
bun run deploy |
Deploy to Cloudflare Workers |
bun run db:migrate:local |
Apply D1 migrations locally |
bun run db:migrate |
Apply D1 migrations (remote) |
bun run db:create |
Create D1 database |
bun run test |
Run tests |
| Command | Description |
|---|---|
bun run dev |
Start Vite dev server |
bun run build |
Build for production |
bun run test |
Run tests |
Cloudflare Workers — copy backend/.dev.vars.example to backend/.dev.vars:
BETTER_AUTH_SECRET=your_random_secret_key_here_min_32_chars
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
RESEND_API_KEY=
RESEND_FROM_EMAIL=noreply@yourdomain.com
SIWE_DOMAIN=localhost:4000
PASSKEY_RP_ID=localhost
PASSKEY_RP_NAME=Better Auth Template
PASSKEY_ORIGIN=http://localhost:4000For deployed Cloudflare Workers, set URL vars in backend/wrangler.toml:
BETTER_AUTH_URL: backend public URL (e.g.https://api.yourdomain.com)APP_URL: frontend public URL (e.g.https://app.yourdomain.com)TRUSTED_ORIGINS: allowed frontend origins (comma-separated)
Node.js / Bun — copy backend/.env.example to backend/.env:
PORT=4200
DB_PATH=./data/local.db
BETTER_AUTH_SECRET=your_random_secret_key_here_min_32_chars
BETTER_AUTH_URL=http://localhost:4200
TRUSTED_ORIGINS=http://localhost:4000
APP_URL=http://localhost:4000
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
RESEND_API_KEY=re_your_api_key_here
RESEND_FROM_EMAIL=noreply@yourdomain.com
SIWE_DOMAIN=localhost:4000
SIWE_EMAIL_DOMAIN=localhost
PASSKEY_RP_ID=localhost
PASSKEY_RP_NAME=Better Auth Template
PASSKEY_ORIGIN=http://localhost:4000- Create an account at resend.com
- Generate an API key from resend.com/api-keys
- Add
RESEND_API_KEYandRESEND_FROM_EMAILto your environment
Note: In development, emails are logged to console when RESEND_API_KEY is not set.
- Go to Twitter Developer Portal
- Create an app with OAuth 2.0
- Set callback URL:
http://localhost:8787/api/auth/callback/twitter(Wrangler) orhttp://localhost:4200/api/auth/callback/twitter(Node.js) - Add credentials to your environment
- Go to Google Cloud Console
- Create OAuth 2.0 credentials
- Set callback URL:
http://localhost:8787/api/auth/callback/google(Wrangler) orhttp://localhost:4200/api/auth/callback/google(Node.js) - Add credentials to your environment
better-auth-template/
├── frontend/ # React + Vite + TypeScript
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utilities and auth client
│ │ └── pages/ # Page components
│ └── package.json
├── backend/ # Hono + Better Auth
│ ├── src/
│ │ ├── index.ts # Cloudflare Workers entry point
│ │ ├── node.ts # Node.js / Bun entry point
│ │ └── lib/
│ │ ├── auth.ts # Auth factory (portable)
│ │ └── email.ts # Email via Resend API
│ ├── migrations/ # D1 / SQLite migrations
│ ├── wrangler.toml # Cloudflare Workers config
│ └── package.json
├── docs/ # Documentation
├── docker-compose.yml # Docker (Node.js + SQLite)
└── package.json # Root scripts
| Technology | Description |
|---|---|
| React 19 | UI library |
| TypeScript 5.8 | Type-safe JavaScript |
| Vite 7 | Build tool & dev server |
| Tailwind CSS 4 | Utility-first CSS |
| React Router 7 | Client-side routing |
| TanStack Query 5 | Server state management |
| Wagmi 2 | React hooks for Ethereum |
| Viem 2 | TypeScript Ethereum library |
| RainbowKit 2 | Wallet connection UI |
| Technology | Description |
|---|---|
| Hono 4 | Lightweight web framework (Workers, Node.js, Bun) |
| Better Auth 1.4 | Authentication framework |
| Kysely + D1Dialect | Query builder for Cloudflare D1 |
bun:sqlite (Bun runtime) |
Local SQLite driver for src/node.ts |
| Resend | Email delivery (raw fetch) |
| Technology | Description |
|---|---|
| SIWE | Sign-In With Ethereum (EIP-4361) |
| WebAuthn | Passkey / biometric authentication |
| Technology | Description |
|---|---|
| Cloudflare Workers | Serverless edge deployment |
| Cloudflare D1 | Serverless SQLite database |
| Docker | Container platform (for self-hosted) |
# Wrangler dev logs (shown in terminal)
bun run dev
# Docker logs
docker logs better-auth-backend -f
docker compose logs -fPort already in use:
lsof -i :8787 # or :4200
kill -9 <PID>D1 migration errors:
# Re-run migrations locally
bun run db:migrate:localEmail verification not working:
- Check backend console for email logs
- In development without
RESEND_API_KEY, verification links are printed to console
SIWE not working:
- Ensure
SIWE_DOMAINmatches your frontend origin - Check that the
walletAddresstable exists in your database
OAuth Invalid callbackURL (Cloudflare Tunnel):
- If you use quick tunnels (
*.trycloudflare.com), the domain changes each run - Update
APP_URLandTRUSTED_ORIGINSinbackend/.envto your current tunnel URL - Keep
BETTER_AUTH_URLas your backend URL (local Node.js example:http://localhost:4200) - Restart backend after env changes:
docker compose restart backend
Docker backend missing module after rebuild:
- If startup fails with errors like
Cannot find module '@hono/node-server', renew anonymous volumes:
docker compose up -d --build --force-recreate --renew-anon-volumes backend- Quick Start
- Setup Guide
- Architecture Overview
- Deployment Guide
- Deployment Decision Table
- Cloudflare Deploy Workflow
- Bun + SQLite Deploy Workflow
- Database Setup
- Docker Setup
- Auth Usage
- Email & Password Auth
- Forgot Password
- Google OAuth Setup
MIT
Built with Better Auth