A production-oriented starter template / boilerplate for building type-safe, secure web apps without wiring the foundations from scratch every time. It ships with end-to-end type safety, database-backed authentication, role-based access control, and a clean, accessible UI kit.
Use it as the base for admin dashboards, internal tools, or SaaS products: clone it, rename it, and start building features on day one.
- End-to-end type safety from the database to the UI (Prisma -> tRPC -> React).
- Email/password auth with database sessions (revocable), Argon2id hashing, and email verification.
- Role-based access control (
USER,MODERATOR,ADMIN,SUPER_ADMIN) enforced server-side on every procedure. - Built-in rate limiting, hardened security headers, and Zod-validated environment variables.
- Dockerized PostgreSQL with migrations and seed data.
- A small, accessible UI kit (forms, tables, dialogs) with loading/empty/ error states and a working example CRUD feature (Products).
| Concern | Choice |
|---|---|
| Framework | Next.js 16 (Pages Router) |
| Language | TypeScript (strict) |
| API | tRPC 11 |
| Data / ORM | Prisma 6 + PostgreSQL 17 |
| Auth | Better Auth (email/password, DB sessions) |
| Password hashing | Argon2id (@node-rs/argon2) |
| Validation | Zod |
| Forms | React Hook Form + @hookform/resolvers |
| Data fetching | TanStack Query (via tRPC) |
| Styling | Tailwind CSS 4 |
| Nodemailer (console in dev, SMTP in prod) | |
| Container | Docker Compose |
Why Better Auth instead of Auth.js/NextAuth? NextAuth's Credentials provider is JWT-only by design and cannot use database sessions, which rules out true server-side session revocation ("sign out everywhere"). Better Auth supports email/password on database sessions natively, with pluggable Argon2id, email verification, RBAC, rate limiting, and MFA/passkey plugins.
- Node.js 20+
- pnpm
- Docker (for PostgreSQL)
pnpm installcp .env.example .env
# Then set a strong secret:
# openssl rand -base64 32 -> BETTER_AUTH_SECRETdocker compose up -dpnpm db:migrate
pnpm db:seedpnpm dev
# http://localhost:3000 -> redirects to /products (sign in required)Register an account at /auth/register. In development, the verification email
is printed to the server console (no SMTP required) — open the logged link
to verify.
New users get the USER role. To grant admin rights (so you can create/edit/
remove products), promote yourself:
docker exec trpc_testing_db \
psql -U postgres -d trpc_testing \
-c "UPDATE \"user\" SET role='ADMIN' WHERE email='you@example.com';"| Script | Description |
|---|---|
pnpm dev |
Start the dev server |
pnpm build |
Generate Prisma client + production build |
pnpm start |
Run the production build |
pnpm lint |
Lint with ESLint |
pnpm db:migrate |
Create/apply Prisma migrations (dev) |
pnpm db:push |
Push schema without a migration |
pnpm db:seed |
Seed demo categories and products |
pnpm db:studio |
Open Prisma Studio |
prisma/
schema.prisma # Product/Category + Better Auth models
migrations/ # SQL migrations
seed.mjs # Demo data
src/
components/ # UI kit (ui.tsx), Layout, ConfirmDialog, ProductForm
features/
auth/ # AuthCard, RequireAuth guard, LogoutButton
hooks/
use-current-user.ts # Client session/role helper (UX only)
lib/
auth.ts # Better Auth server instance
auth-client.ts # Better Auth React client
env.ts # Zod-validated environment (server-only)
password.ts # Argon2id hash/verify
permissions.ts # Roles, hierarchy, access control (shared)
product.ts # Product schema/helpers
pages/
api/auth/[...all].ts # Better Auth catch-all handler
api/trpc/[trpc].ts # tRPC handler
auth/ # login, register, verify-email
products/ # list, new, [id]/edit
schemas/
auth.ts # Zod auth schemas (login/register/password policy)
server/
db.ts # Prisma client singleton
context.ts # tRPC context (resolves session server-side)
trpc.ts # public/protected/admin procedures + RBAC + errors
routers/ # auth, product, category
services/ # mailer, security logger
styles/ # Tailwind globals + design tokens
utils/
trpc.ts # Typed tRPC hooks
See PRODUCT.md and DESIGN.md for product intent and the design system.
- Sessions: database-backed (revocable),
HttpOnly+SameSite=Laxcookies,Securein production, with expiry and daily rotation. - Passwords: Argon2id (64 MiB / 3 iterations), never stored in plaintext.
- Email verification: required before sign-in; reset/verify tokens are single-use and short-lived.
- RBAC:
USER < MODERATOR < ADMIN < SUPER_ADMIN. tRPC exposespublicProcedure,protectedProcedure, andadminProcedure; authorization is always enforced server-side (the client UI only hides affordances). - Rate limiting: enabled with database storage, with stricter limits on sign-in, sign-up, verification, and password reset.
// src/server/routers/example.ts
import { z } from "zod";
import { protectedProcedure, adminProcedure, router } from "../trpc";
export const exampleRouter = router({
mine: protectedProcedure.query(({ ctx }) => ctx.user.id),
adminOnly: adminProcedure
.input(z.object({ value: z.string() }))
.mutation(({ input }) => input.value),
});Register it in src/server/routers/_app.ts.
Designed against OWASP ASVS Level 2 and the OWASP Top 10:
- Broken Access Control -> server-side RBAC + session checks on every mutation.
- Cryptographic Failures -> Argon2id, secure cookies, secrets from env only.
- Injection -> Prisma parameterized queries only (no raw SQL).
- Security Misconfiguration -> CSP, HSTS,
X-Content-Type-Options,Referrer-Policy,Permissions-Policy,X-Frame-Options(seenext.config.ts). - Identification & Auth Failures -> password policy, email verification, revocable sessions, rate limiting.
- CSRF ->
SameSite=Laxcookies + same-origin tRPC; Better Auth validates request origin for state-changing calls.
Errors returned to clients are generic; internal details (Prisma/stack traces) are logged server-side only.
This template ships Phase 1 (core auth + RBAC + hardening). The auth architecture is intentionally MFA- and passkey-ready:
- Phase 2: forgot/reset password, change password, change email, delete account, list sessions + revoke one / revoke all ("sign out everywhere").
- Phase 3: MFA/2FA (TOTP) and passkeys (WebAuthn).
Provided as a starter template — adapt freely for your own projects.