A CLI tool to scaffold a production-ready full-stack turborepo monorepo with authentication, database, and multi-platform support — all wired up and ready to go.
npx @danoweibo/create-fastnext my-app
# or
pnpm dlx @danoweibo/create-fastnext my-appA turborepo monorepo with 5 apps and 5 shared packages, fully configured with auth, database, linting, and typed environment variables out of the box.
| App | Framework | Port | Purpose |
|---|---|---|---|
apps/web |
Next.js 16 (Pages Router) | 5000 | Consumer web app — sign in, sign up, dashboard |
apps/admin |
Next.js 16 (App Router) | 5100 | Admin dashboard — role-gated, admin-only access |
apps/app |
Vite + React | 5200 | Lightweight generic app |
apps/mobile |
Expo 55 + React Native | 8081 | Mobile app — iOS and Android |
apps/server |
Fastify 5 | 8080 | API server — auth handler + protected routes |
| Package | Purpose |
|---|---|
@{name}/auth |
Shared Better Auth configuration with Drizzle adapter |
@{name}/db |
Drizzle ORM schema + Supabase-ready migrations |
@{name}/env |
Type-safe environment variables per app (t3-env) |
@{name}/ui |
Shared shadcn/ui component library (Tailwind 4) |
@{name}/config |
Shared TypeScript base configuration |
- Monorepo — Turborepo + pnpm workspaces
- Language — TypeScript throughout
- Linting & Formatting — Biome (replaces ESLint + Prettier)
- Server — Fastify 5
- Auth — Better Auth with email/password, username, admin plugin, bearer token, and Expo support
- Database — Drizzle ORM + PostgreSQL (Supabase)
- Env validation — t3-env with Zod
- Framework — Next.js 16
- Styling — Tailwind CSS 4 + shadcn/ui
- Forms — TanStack Form + Zod validation
- Themes — next-themes (dark/light mode)
- Framework — Expo 55 + React Native
- Styling — Tailwind CSS 4 via Uniwind
- Components — heroui-native
- Variants — tailwind-variants
- Auth storage — expo-secure-store
Better Auth is pre-configured across all platforms with the following out of the box:
- Email + password sign in and sign up
- Username support with normalization, min/max length, reserved name list, and display name validation
- Admin plugin — role-based access, user banning with custom message
- Bearer token — for mobile API requests (React Native doesn't use browser cookies)
- Expo plugin — deep link auth for React Native with secure token storage
- Trusted origins — all app URLs pre-configured for local and production
- Cross-domain cookies —
sameSite: none,secure: truefor Vercel → Render setup
Two roles are supported out of the box: user (default) and admin.
apps/admin— only allows sign in for users withrole: "admin". Non-admin accounts are detected after sign-in and redirected to the consumer app.apps/web+apps/mobile— accessible by all authenticated users regardless of role.
To promote a user to admin, run directly on the database:
UPDATE "user" SET "role" = 'admin' WHERE "email" = 'your@email.com';The server ships with three tiers of test routes that demonstrate the auth-gating pattern:
| Route | Auth | Role | Used by |
|---|---|---|---|
GET /api/test/items |
None | Public | apps/app, apps/mobile |
POST /api/testuser/create |
Required | Any user | apps/web, apps/mobile |
GET /api/testuser/read |
Required | Any user | apps/web, apps/mobile |
PUT /api/testuser/update |
Required | Any user | apps/web, apps/mobile |
DELETE /api/testuser/delete |
Required | Any user | apps/web, apps/mobile |
POST /api/testadmin/create |
Required | Admin only | apps/admin |
GET /api/testadmin/read |
Required | Admin only | apps/admin |
PUT /api/testadmin/update |
Required | Admin only | apps/admin |
DELETE /api/testadmin/delete |
Required | Admin only | apps/admin |
These are wired up with working UI in each app — create, read, update, delete with live responses displayed. Replace them with your real business logic.
- Node.js 18+
- pnpm 11+
- A Supabase project
pnpm create fastnext my-app
cd my-appThe CLI will ask:
- Initialize a git repository? (default: Yes)
- Install dependencies with pnpm? (default: Yes)
- Open your Supabase project → SQL Editor
- Run the schema from
packages/db/src/schema/auth.ts(or use the generated SQL in Supabase) - Copy your Supabase connection string (Transaction pooler recommended) and update
apps/server/.env:
DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgresEach app has a pre-populated .env for local development and .env.production for deployment. The only values you need to fill in locally are:
apps/server/.env
BETTER_AUTH_SECRET=<auto-generated by CLI>
BETTER_AUTH_URL=http://localhost:8080
CORS_ORIGIN=http://localhost:5000,http://localhost:5100,http://localhost:5200
DATABASE_URL=your-supabase-connection-stringapps/admin/.env
NEXT_PUBLIC_SERVER_URL=http://localhost:8080
NEXT_PUBLIC_ENTRY_URL=http://localhost:5000apps/web/.env
NEXT_PUBLIC_SERVER_URL=http://localhost:8080apps/mobile/.env
EXPO_PUBLIC_SERVER_URL=http://<your-local-ip>:8080Note: Mobile can't use
localhost— use your machine's local IP address (ipconfigon Windows,ifconfigon Mac/Linux).
apps/app/.env
VITE_SERVER_URL=http://localhost:8080pnpm dev:admin # admin + server
pnpm dev:web # web + server
pnpm dev:mobile # mobile + server (Expo)
pnpm dev:api # server only
pnpm dev:admin+core # admin + web + server
pnpm dev:core+ticket # web + app + server
pnpm dev:app # mobile only (no server)| App | Platform |
|---|---|
apps/server |
Render |
apps/admin |
Vercel |
apps/web |
Vercel |
apps/app |
Vercel |
| Database | Supabase |
| Mobile | Expo EAS |
Important: Vercel and Render are on different domains. The template uses Next.js rewrites (
/api/*→ Render) to avoid cross-domain cookie issues. This is pre-configured inapps/admin/next.config.tsandapps/web/next.config.ts.
Set these environment variables in your Render service:
BETTER_AUTH_SECRET=<your-secret>
BETTER_AUTH_URL=https://<your-render-service>.onrender.com
CORS_ORIGIN=https://<your-web>.vercel.app,https://<your-admin>.vercel.app,https://<your-app>.vercel.app
DATABASE_URL=<your-supabase-connection-string>
NODE_ENV=productionSet these in each Vercel project's environment variables:
Admin:
NEXT_PUBLIC_SERVER_URL=https://<your-render-service>.onrender.com
NEXT_PUBLIC_ENTRY_URL=https://<your-web>.vercel.appWeb:
NEXT_PUBLIC_SERVER_URL=https://<your-render-service>.onrender.comApp:
VITE_SERVER_URL=https://<your-render-service>.onrender.comCustom domain tip: If you own a domain (e.g.
myapp.com), point all services at subdomains (api.myapp.com,app.myapp.com,admin.myapp.com). Setdomain: ".myapp.com"in the Better Auth cookie config and the rewrite workaround becomes unnecessary.
| Script | What it runs |
|---|---|
pnpm dev |
All apps (via Turborepo) |
pnpm dev:admin |
apps/admin + apps/server |
pnpm dev:web |
apps/web + apps/server |
pnpm dev:ticket |
apps/app + apps/server |
pnpm dev:mobile |
apps/mobile + apps/server |
pnpm dev:admin+core |
apps/admin + apps/web + apps/server |
pnpm dev:core+ticket |
apps/web + apps/app + apps/server |
pnpm dev:api |
apps/server only |
pnpm dev:app |
apps/mobile only |
pnpm build |
Build all apps |
pnpm check-types |
Type check all packages |
pnpm check |
Biome check + auto-fix (safe) |
pnpm lint:fix |
Biome check + auto-fix (including unsafe — sorts Tailwind classes) |
pnpm db:push |
Push schema changes to database |
pnpm db:studio |
Open Drizzle Studio |
pnpm db:generate |
Generate migrations |
pnpm db:migrate |
Run migrations |
my-app/
├── apps/
│ ├── admin/ # Next.js App Router — admin portal
│ ├── web/ # Next.js Pages Router — consumer web
│ ├── mobile/ # Expo — iOS + Android
│ ├── app/ # Vite — ticket/public app
│ └── server/ # Fastify — API + auth
├── packages/
│ ├── auth/ # Better Auth config (shared)
│ ├── db/ # Drizzle schema + migrations
│ ├── env/ # t3-env per app
│ ├── ui/ # shadcn/ui component library
│ └── config/ # Shared tsconfig
├── biome.json # Linting + formatting (root)
├── turbo.json # Turborepo pipeline
└── pnpm-workspace.yaml
Biome is configured at the root and covers all apps and packages. It replaces both ESLint and Prettier.
pnpm check # fix safe issues
pnpm lint:fix # fix everything including Tailwind class sortingTailwind class sorting uses Biome's useSortedClasses rule (nursery) — it won't auto-apply on save, only via lint:fix.
MIT