A production-ready, type-safe full-stack SaaS monorepo template with end-to-end type safety, built-in monetization, monitoring, and i18n.
- End-to-End Type Safety: Hono
AppType+hono/client— zero manual type definitions across API boundary - Monorepo: pnpm workspaces + Turborepo for efficient builds and caching
- Authentication: Better Auth with email/password (dev) + Google & GitHub OAuth (production)
- Three-Pool Credits: Daily/subscription/bonus credits with full audit trail
- Stripe Payments: Subscription plans + one-time credit packages + webhook handling
- Task Queue: pg-boss (PostgreSQL-based) — background jobs, retries, cron scheduling, no Redis
- AI Integration: OpenRouter SDK — unified access to 300+ LLMs (streaming + image generation)
- File Upload: Cloudflare R2 with presigned URLs for direct frontend upload
- i18n: i18next with Chinese/English support, browser language detection, localStorage persistence
- Production Monitoring: Sentry error tracking, pino structured logging, business alerting webhooks
- Admin System: Role-based admin routes for user search, credit grants, feedback management
- Referral System: Viral growth with anti-fraud (IP limits, monthly caps, self-referral prevention)
- Feedback Collection: User bug reports and feature requests with rate limiting
- Bot Protection: Cloudflare Turnstile (optional in dev)
- Email: Resend transactional emails (optional in dev)
- UI: shadcn/ui (16+ components) + Tailwind CSS + collapsible sidebar + modal system
morph-template/
├── apps/
│ ├── api/ # Backend (Hono + Drizzle + Better Auth + pg-boss)
│ │ ├── src/
│ │ │ ├── routes/ # API routes (checkout, orders, user, chat, admin, ...)
│ │ │ ├── jobs/ # pg-boss job handlers (AI generation, cleanup)
│ │ │ ├── db/ # Drizzle schema + connection
│ │ │ ├── lib/ # Service singletons (stripe, r2, ai, queue, sentry, logger, ...)
│ │ │ ├── auth.ts # Better Auth configuration
│ │ │ ├── env.ts # Environment variable validation
│ │ │ └── index.ts # Main app (exports AppType)
│ │ └── drizzle.config.ts
│ └── web/ # Frontend (React + Vite + TanStack Query)
│ ├── src/
│ │ ├── components/ # UI (shadcn/ui), modals, layout, landing
│ │ ├── providers/ # Paywall, Pricing, Settings, Sidebar, Feedback, Referral
│ │ ├── i18n/ # i18next initialization
│ │ ├── pages/ # Dashboard, pricing, login, etc.
│ │ ├── lib/ # API client, auth, upload utils
│ │ └── main.tsx # Entry point + Sentry init
│ └── vite.config.ts
├── packages/
│ └── shared/ # Shared Zod schemas, configs, i18n, locales
└── scripts/ # Build utilities
- Node.js >= 20.0.0
- pnpm >= 9.0.0
- Docker (for PostgreSQL)
git clone git@github.com:Howell5/morph-template.git
cd morph-template
pnpm installcp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.envBackend (apps/api/.env):
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
BETTER_AUTH_SECRET |
Yes | Secure random string (min 32 chars) |
BETTER_AUTH_URL |
Yes | API base URL (e.g. http://localhost:3000) |
FRONTEND_URL |
No | Frontend URL for CORS (default: http://localhost:5173) |
GOOGLE_CLIENT_ID / SECRET |
No | Google OAuth (optional in dev) |
GITHUB_CLIENT_ID / SECRET |
No | GitHub OAuth (optional in dev) |
STRIPE_SECRET_KEY |
No | Stripe API key |
STRIPE_WEBHOOK_SECRET |
No | Stripe webhook signing secret |
OPENROUTER_API_KEY |
No | OpenRouter AI API key |
R2_ACCOUNT_ID |
No | Cloudflare R2 (4 vars needed for file uploads) |
TURNSTILE_SECRET_KEY |
No | Cloudflare Turnstile bot protection |
RESEND_API_KEY |
No | Resend email service |
SENTRY_DSN |
No | Sentry error tracking |
ALERT_WEBHOOK_URL |
No | Slack/Lark webhook for business alerts |
Frontend (apps/web/.env):
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
No | Backend URL (default: http://localhost:3000) |
VITE_SENTRY_DSN |
No | Sentry frontend error tracking |
Generate a secret:
openssl rand -base64 32pnpm docker:up # Start PostgreSQL
pnpm db:push # Create tablespnpm dev # API on :3000, Web on :5173A test account (test@test.com / password123) is auto-seeded in development mode.
pnpm dev # Start all apps
pnpm build # Build for production
pnpm check # Lint + format (Biome)
pnpm check:lines # Verify all files < 500 lines
pnpm db:push # Push schema to database
pnpm db:studio # Open Drizzle Studio
pnpm docker:up # Start PostgreSQL
pnpm docker:down # Stop PostgreSQL// Backend exports AppType
export type AppType = typeof app;
// Frontend automatically knows all request/response types
const response = await api.api.posts.$get();
const json = await response.json(); // Fully typed!Zod schemas in packages/shared are the single source of truth:
- Define in
packages/shared/src/schemas/ - Backend validates with
zValidator('json', schema) - Frontend validates with
zodResolver(schema)
Three-pool system (consumption priority: daily → subscription → bonus):
- Daily: 50/day, expires at UTC midnight
- Subscription: Based on tier, resets on renewal
- Bonus: From purchases/referrals/admin grants, never expire
- Subscriptions: Free, Starter ($12/mo), Pro ($24/mo), Max ($240/mo)
- Credit Packages: Small (100/$5), Medium (250/$10), Large (600/$20)
- Stripe Checkout + Webhooks + Billing Portal
Provider-based architecture with paywall interception:
PaywallProvider— intercepts API paywall errors → opensPricingModalSettingsModal— 4 tabs: Account, Preferences, Billing, UsageFeedbackModal/ReferralModal— accessible from sidebar and header
- Sentry: Frontend + backend error tracking
- pino: Structured JSON logging with child loggers per module
- Health check:
GET /healthwith DB latency, R2/AI status - Business alerts: Webhook-based (Slack/Lark) for AI quota, payment failures
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Service health with dependency status |
/api/auth/* |
* | Better Auth (login, register, OAuth) |
/api/posts |
GET/POST | Posts CRUD |
/api/checkout |
POST | Credit package checkout |
/api/checkout/subscription |
POST | Subscription checkout |
/api/checkout/manage |
POST | Stripe billing portal |
/api/orders |
GET | Order history (paginated) |
/api/webhooks/stripe |
POST | Stripe webhook handler |
/api/user/me |
GET/PATCH | User profile + credits |
/api/user/usage-history |
GET | Credit usage records |
/api/upload/presign |
POST | R2 presigned upload URL |
/api/chat |
POST | AI chat (streaming SSE + image gen) |
/api/tasks |
POST/GET | Task submission + status |
/api/admin/* |
* | Admin: users, credits, feedback |
/api/referral/* |
* | Referral code, stats, history |
/api/feedback |
POST | User feedback submission |
- PostgreSQL: Add from Zeabur Marketplace
- API: Deploy
apps/api, set env vars (use${postgres.DATABASE_URL}) - Web: Deploy
apps/web, setVITE_API_URL - Migrations: Run
pnpm db:pushfrom API console
Estimated cost: ~$10/month (PostgreSQL + API container, Web is static)
- Sentry (Free tier): Set
SENTRY_DSN+VITE_SENTRY_DSN - Uptime Kuma (~$5/mo on Zeabur): External health monitoring + status page
- Slack/Lark webhook: Set
ALERT_WEBHOOK_URLfor business alerts
| Layer | Technology |
|---|---|
| Web Framework | Hono |
| Database | PostgreSQL + Drizzle ORM |
| Auth | Better Auth |
| Payments | Stripe |
| Task Queue | pg-boss |
| AI | OpenRouter SDK |
| File Storage | Cloudflare R2 |
| Frontend | React + Vite |
| Data Fetching | TanStack Query |
| UI Components | shadcn/ui + Tailwind CSS |
| i18n | i18next + react-i18next |
| Error Tracking | Sentry |
| Logging | pino |
| Resend | |
| Linting | Biome |
| Monorepo | Turborepo |
MIT