Prisma + Better Auth + Next.js Setup Guide
Source: prisma.io/docs/guides/authentication/better-auth/nextjs
Prerequisites
- Node.js 20+, Next.js App Router + TypeScript
- PostgreSQL (Neon, Supabase, local) — for MySQL/SQLite swap the adapter
Steps Overview — Core (Basic Setup)
Steps Overview — Advanced (Production-Grade)
Key Files
| File | Purpose |
|---|
prisma.config.ts | Datasource URL (Prisma 7: DIRECT_URL fallback) |
src/lib/db/index.ts | Global Prisma + Neon adapter + slow query logging |
src/lib/auth.ts | Better Auth config (email, OAuth, 2FA, hooks) |
src/lib/auth-client.ts | React client (signIn, signUp, signOut, useSession, twoFactor) |
src/lib/rate-limit.ts | Upstash Redis rate limiter + in-memory fallback |
src/lib/auth/audit-log.ts | Structured auth event logger |
src/lib/email/resend.ts | Resend email sender + fire-and-forget variant |
src/app/api/auth/[...all]/route.ts | Auth API + rate limit + account lockout |
Quick Commands
# Core
npm install prisma tsx --save-dev
npm install @prisma/client @prisma/adapter-neon better-auth
npx @better-auth/cli@latest secret
npx @better-auth/cli generate
npx prisma migrate dev --name add-auth-models
# Advanced
npm install @upstash/ratelimit @upstash/redis resend react-email
Key Patterns
- Prisma 7:
prisma.config.ts for datasource, NOT schema.prisma
- @@map: All models use
@@map("lowercase") — required by Better Auth
- Neon dual URLs:
DATABASE_URL (pooled) for queries, DIRECT_URL for migrations
- trustedOrigins: Required if port != 3000 or in production
- Rate limiting: Upstash Redis (prod) / in-memory (dev), 4 presets
- Account lockout: 10 failures in 15 min → 30 min lockout
- IP extraction: Last
X-Forwarded-For IP (proxy-set, not spoofable)
- OAuth: Conditional enable via
Boolean(ID && SECRET)
- Email verification: Auto-enable via
Boolean(process.env.RESEND_API_KEY)
- Audit log: JSON in prod, readable in dev, no PII
Scaffold Script
python .claude/skills/prisma-better-auth-nextjs/scripts/scaffold.py [target-dir]