A concise, production-ready Nuxt 4 boilerplate for SaaS/internal apps that need authentication, multi-organization tenancy, MySQL with Drizzle ORM, email templates, and local file uploads. Opinionated, minimal, and reusable.
- Nuxt 4 + Nitro (SSR/ISR) with Nuxt UI v4 components
- Better Auth (email/password, 2FA, admin, organizations) and global auth middleware
- Drizzle ORM (MySQL) with typed schema and drizzle-kit migrations
- Request validation using h3-zod
- Email rendering with Vue Email; Resend provider included (SMTP stub present)
- Local file uploads via
nuxt-file-storage - TypeScript (strict) and ESLint configured
- Nuxt 4, Nitro, Vite
- Nuxt UI v4, @vueuse/nuxt
- Better Auth (+ plugins: twoFactor, admin, organization)
- Drizzle ORM + drizzle-kit (MySQL, mysql2)
- h3-zod, Vue Email, Resend API
app/ # UI (pages, components, layouts)
modules/ # Modules: auth/, email/
server/ # API (Nitro), database schema, utils, tasks
shared/ # Shared types & enums
public/storage/ # File uploads (mounted by nuxt-file-storage)
Modules of note:
modules/auth→ exposes/api/auth/**,useAuth()composable, global middlewaremodules/email→sendEmail()util withresend|smtpproviders
- Node.js 20+ and pnpm 10+
- MySQL 8+ (or compatible) and a working DSN
- Install dependencies
pnpm install- Copy environment file
cp .env.example .env- Push database schema (Drizzle)
pnpm db:push- Start development server
pnpm devDATABASE_URL— MySQL DSN (e.g.mysql://user:pass@host:3306/dbname)NUXT_SITE_URL— public site URL (default:http://localhost:3000)APP_SIGNUP_ENABLED—true|falseto enable signups
Email (Resend default):
MAIL_PROVIDER=resendMAIL_RESEND_KEY=<key>MAIL_FROM="Acme <no-reply@acme.test>"
SMTP (optional, stub):
MAIL_PROVIDER=smtp,MAIL_SMTP_HOST,MAIL_SMTP_PORT,MAIL_SMTP_USERNAME,MAIL_SMTP_PASSWORD,MAIL_SMTP_SECURE=true|false
- Schema:
server/database/schema/*(auth, core, relations) - Migrate:
pnpm db:push - Studio:
pnpm db:studio - Regenerate auth schema (when upgrading Better Auth):
pnpm auth:db
- Auth endpoints are available under
/api/auth/**(handled by Better Auth module) - Use
useAuth()composable to access session, organizations, andsetActiveOrganization() - Global middleware redirects guests to sign-in or to organization creation when none exists
- In production, email verification is enabled by default (see
modules/auth/runtime/server/utils/auth.ts)
- Example template:
server/emails/WelcomeMail.vue - Use
sendEmail({ to, subject, html })— default provider: Resend - SMTP provider is scaffolded but not implemented (stub)
- Upload endpoint:
POST /api/uploadwith body{ files: File[] } - Returns public URLs under
/storage/... - Storage folder:
public/storage(configured viafileStorage.mount)
- Always derive
organizationIdfrom session server-side (useAuthSession(event)) — never trust client-provided org IDs - Use
useValidatedBody/useValidatedQuery(h3-zod) for input validation - Drizzle queries must filter by organization:
eq(tables.<table>.organizationId, orgId) - Use
ulid()for new IDs, setupdatedAton updates, and return rows with.returning()for mutations
From package.json:
- Build
pnpm build- Preview locally
pnpm preview- PM2 (cluster) — use
ecosystem.config.cjsafter build
pm2 start ecosystem.config.cjsNitro server entry: ./.output/server/index.mjs
- Pages: fetch with
useFetch('/api/<resource>') - Forms: submit with
$fetchand handle errors viahandleError(error, form) - Use Nuxt UI v4 components and minimal custom CSS (
app/assets/css/main.css)
- Nitro Tasks are enabled (experimental) — see
server/tasks/*for examples - UI copy is English; repository language should remain English for public publishing
- Enforce tenancy server-side; never leak tenant data
Fork this repository to reuse the boilerplate. Adjust schemas and modules under server/ and app/, then publish to GitHub. Happy building! ✨
{ "build": "nuxt build", "dev": "nuxt dev", "preview": "nuxt preview", "typecheck": "nuxt typecheck", "lint": "eslint .", "lint:fix": "eslint . --fix", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "auth:db": "npx @better-auth/cli@latest generate ..." }