A modern full-stack monorepo template with Vite + React frontend, Hono backend, and a shared TypeScript lib package.
Create a new app in the current directory (you'll be prompted for a name):
bun create thunder-app@latestOr specify the name directly:
bun create thunder-app@latest my-appThe generator will:
- Copy the template into
./my-app(or the name you provide) - Rename all package scopes to
@<your-app>/... - Seed env files with sensible defaults
- Ask to run
bun install(workspaces) and optionally buildlib
frontend/- Vite React application with Auth.js integrationbackend/- HonoJS API server with Drizzle ORM and Auth.jslib/- Shared TypeScript package used by both frontend and backendbackend/src/db/schema/- Database schema definitionsusers.ts- User table (merged with auth requirements)auth.ts- Auth.js tables (accounts, sessions, verification tokens)
| Command | Description |
|---|---|
bun run dev |
Start both frontend, lib, and backend |
bun run dev:frontend |
Start only the frontend (Vite, port 5173) |
bun run dev:backend |
Start only the backend (Hono, port 3000) |
bun run dev:lib |
Watch mode for lib package (rebuilds on changes) |
bun run build |
Build all packages (includes typecheck & lint) |
bun run build:all |
Build all packages without typecheck/lint |
bun run build:frontend |
Build only the frontend package |
bun run build:backend |
Build only the backend package |
bun run build:lib |
Build only the lib package |
bun run typecheck |
Type check all packages |
bun run lint |
Lint all packages (format check + ESLint) |
bun run lint:fix |
Auto-fix linting issues |
bun run format |
Format all files with Prettier |
bun run format:check |
Check formatting without fixing |
| Command | Description |
|---|---|
bun run --filter @thunder-app/backend db:generate |
Generate database migrations |
bun run --filter @thunder-app/backend db:migrate |
Run database migrations |
bun run --filter @thunder-app/backend db:push |
Push schema changes to database |
bun run --filter @thunder-app/backend db:studio |
Open Drizzle Studio |
The lib package contains shared TypeScript code that can be imported in both the frontend and backend:
import { greet, User } from "@your-project/lib";Important: If you make changes to the lib package, you must rebuild it:
bun run build:libOr run it in watch mode during development:
bun run dev:libThis template uses Drizzle ORM with PostgreSQL. The database schema is defined in backend/src/db/schema/.
-
Set up your database (local PostgreSQL or cloud provider)
-
Configure environment variables in
backend/.env(see Environment Variables section) -
Create your schema in
backend/src/db/schema/(e.g.,users.ts,posts.ts) -
Push schema to database:
bun run --filter @thunder-app/backend db:push
Or generate and run migrations:
bun run --filter @thunder-app/backend db:generate bun run --filter @thunder-app/backend db:migrate
-
Open Drizzle Studio to view/edit data:
bun run --filter @thunder-app/backend db:studio
This template includes Auth.js (formerly NextAuth) integration with Google OAuth as the default provider. Authentication uses database sessions for secure, server-side session management.
-
Set up Google OAuth credentials:
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google+ API or People API
- Go to APIs & Services > Credentials
- Create OAuth 2.0 Client ID (Web application)
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google - Copy the Client ID and Client Secret
-
Configure backend environment variables in
backend/.env:AUTH_SECRET=your-auth-secret-here # Generate with: openssl rand -base64 32 DATABASE_URL=postgresql://user:password@localhost:5432/dbname FRONTEND_URL=http://localhost:5173 # Frontend URL for redirects GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret PORT=3000
-
Database schema is already set up - The auth tables (
auth_accounts,auth_sessions, etc.) are defined inbackend/src/db/schema/auth.tsand merged with youruserstable. -
Push the auth schema to your database:
bun run --filter @thunder-app/backend db:push
-
Configure frontend environment variables in
frontend/.env:VITE_BACKEND_URL=http://localhost:3000 VITE_PORT=5173
-
The frontend is already configured with
SessionProviderand auth hooks. The sign-in button and session management are ready to use. -
Vite proxy is configured - Auth API requests (
/api/auth/*) are automatically proxied to the backend viavite.config.ts. This allows the frontend to use relative URLs for auth endpoints.
To add additional OAuth providers (GitHub, Discord, etc.):
-
Install the provider (if needed):
cd backend bun add @auth/core/providers/github # Example for GitHub
-
Add provider to backend in
backend/src/index.ts:import GitHub from "@auth/core/providers/github"; providers: [ Google({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET }), GitHub({ clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET }), ],
-
Add environment variables to
backend/.env:GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret
-
Update Google Cloud Console with additional redirect URIs if needed
For production:
-
Update Google OAuth redirect URIs in Google Cloud Console:
- Add:
https://your-api-domain.com/api/auth/callback/google
- Add:
-
Update environment variables:
- Set
FRONTEND_URLto your production frontend URL - Set
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETto production credentials - Ensure
AUTH_SECRETis a strong, random secret
- Set
-
Update CORS settings in
backend/src/index.tsto allow your production frontend domain
- React 19 - UI library
- Vite 5 - Build tool and dev server
- TypeScript 5.9 - Type safety
- Tailwind CSS v4 - Styling
- React Query (@tanstack/react-query) - Data fetching and caching
- Zod v4 - Runtime type validation
- @hono/auth-js/react - Auth.js React client hooks
- Hono v4 - Fast web framework
- TypeScript 5.9 - Type safety
- Drizzle ORM - Type-safe SQL ORM
- PostgreSQL - Database (via
postgresdriver) - Zod v4 - Runtime type validation
- Auth.js (@hono/auth-js) - Authentication framework
- @auth/drizzle-adapter - Drizzle adapter for Auth.js
- Bun - Runtime and package manager
- ESLint - Code linting
- Prettier - Code formatting
Out of the box, the backend enables several defenses:
- Secure headers via
hono/secure-headers - CSRF protection via
hono/csrf; the frontend sendsX-CSRF-Tokenon mutating requests - CORS restricted to
FRONTEND_URLwith credentials andX-CSRF-Tokenallowed - Rate limiting on
/api/auth/*(10 requests / 60s per IP+path) to mitigate brute force and callback abuse - Sessions and verification tokens are stored as SHAโ256 hashes (no plaintext)
- OAuth tokens (access/refresh/id) can be encrypted at rest (AESโ256โGCM) when you set
OAUTH_TOKEN_ENCRYPTION_KEY
Recommended hardening for production (left to end users):
- Add a Content Security Policy (CSP) with nonces for scripts
- Consider an Origin/Referer check for POST/PUT/PATCH/DELETE as defenseโinโdepth
- Ensure cookies are
Secure,HttpOnly, andSameSite=Lax/Strictbehind HTTPS - If deploying multiple instances, replace inโmemory rate limiting with a shared store (e.g., Redis)
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# Server
PORT=3000
# Authentication
AUTH_SECRET=your-auth-secret-here # Generate with: openssl rand -base64 32
FRONTEND_URL=http://localhost:5173 # Frontend URL for redirects after auth
# Google OAuth (required)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# Additional providers (optional)
# GITHUB_CLIENT_ID=your-github-client-id
# GITHUB_CLIENT_SECRET=your-github-client-secret
# Token encryption (optional)
# When set, OAuth tokens are encrypted at rest (AES-256-GCM). Generate with: openssl rand -base64 32
OAUTH_TOKEN_ENCRYPTION_KEY=Required variables:
DATABASE_URL- PostgreSQL connection stringAUTH_SECRET- Secret for signing sessions (generate withopenssl rand -base64 32)GOOGLE_CLIENT_ID- Google OAuth client IDGOOGLE_CLIENT_SECRET- Google OAuth client secret
Optional variables:
PORT- Backend server port (default:3000)FRONTEND_URL- Frontend URL for redirects (default:http://localhost:5173)- Additional provider credentials for other OAuth providers
VITE_BACKEND_URL=http://localhost:3000
VITE_PORT=5173VITE_BACKEND_URL- Backend API URL (default:http://localhost:3000)VITE_PORT- Frontend dev server port (default:5173)
Note: Vite reads environment variables at config time; restart the dev server after changing .env files.
- TypeScript - Strict mode enabled across all packages
- ESLint - Typeโaware linting across workspaces (TS project aware):
- Prefer
??over||for defaulting - Flag impossible conditions (
@typescript-eslint/no-unnecessary-condition) - Enforce
import typewhen symbols are used as types only - JS configs like
postcss.config.js,tailwind.config.jsare ignored in typed linting
- Prefer
- Prettier - Automatic code formatting
- Type checking - Run
bun run typecheckto verify all packages
To publish this template to npm so others can use it:
-
Make sure you're logged into npm:
npm login
-
Update the version in package.json (if needed)
-
Bump a minor version and publish to npm:
npm version minor -m "chore: release %s" npm publish --access public git push --follow-tags -
Users can then create projects with:
bun create thunder-app@latest
Note: The package name is create-thunder-app, but users call it with bun create thunder-app (Bun automatically prepends create-).
