A production-ready monorepo template for building full-stack web applications with end-to-end type safety.
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, Tailwind CSS 4, shadcn/ui |
| API | tRPC v11, Zod v4 |
| Database | PostgreSQL, Drizzle ORM |
| Authentication | Better Auth |
| State | TanStack Query |
| Monorepo | Turborepo, pnpm |
apps/
├── web/ # Next.js frontend application
└── cli/ # Developer CLI tools
packages/
├── db/ # Drizzle ORM schema & database client (single source of truth)
├── api/ # tRPC server with typed procedures
└── sdk/ # Isomorphic API client for web & CLI
- Single Source of Truth: Database schema in
packages/dbdrives everything — migrations, tRPC inputs, SDK types - Type-Safe APIs: tRPC procedures with Zod validation, full type inference from DB to client
- Lazy DB Connections: Pool created on first use, safe for serverless environments
- Soft-Delete Pattern: All application tables use
deletedAtwith proper indexes - Role-Based Access: Three-tier procedures —
publicProcedure,protectedProcedure,adminProcedure
- Node.js 20+
- pnpm 10+
- PostgreSQL database
# Install dependencies
pnpm install
# Configure environment
cp .env.example .env
# Edit .env with your database URL and auth secrets
# Push schema to database
pnpm drizzle-kit push
# Build all packages
pnpm build
# Start development
pnpm devDATABASE_URL=postgresql://user:password@localhost:5432/dbname
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-key-min-32-chars
# Optional: GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secretDatabase layer with Drizzle ORM.
import { db, posts, users, eq, isNull } from '@complete-web-template/db';
// Lazy singleton — safe for serverless
await db.select().from(posts).where(isNull(posts.deletedAt));tRPC server with typed procedures.
import { publicProcedure, protectedProcedure, adminProcedure } from '@complete-web-template/api';
// Procedures enforce auth at the router level
protectedProcedure.input(schema).mutation(({ ctx, input }) => {
// ctx.user is guaranteed here
});Isomorphic API client for web and CLI.
import { createClient } from '@complete-web-template/sdk';
const client = createClient({ apiKey: 'your-key', baseUrl: 'https://api.example.com' });
const result = await client.test();Next.js frontend with:
- App Router (React Server Components)
- TanStack Query for data fetching
- shadcn/ui component library
- Dark mode support
Developer CLI for local workflows.
cli auth login # Device authorization login
cli auth status # Check authentication status
cli auth logout # Clear credentials| Table | Description |
|---|---|
users |
Application users with roles (guest, user, admin) |
posts |
Blog posts with soft-delete |
| Table | Description |
|---|---|
user |
Auth users (email, OAuth accounts) |
session |
Active sessions |
account |
OAuth provider links |
verification |
Email verification tokens |
apikey |
API keys for external access |
device_code |
Device authorization flow |
pnpm build # Build all packages
pnpm dev # Start development servers
pnpm lint # Lint all packages
pnpm typecheck # Type-check all packages
pnpm clean # Clear build artifacts
# Database
pnpm drizzle-kit generate # Generate migrations
pnpm drizzle-kit push # Push schema to database
pnpm drizzle-kit studio # Open Drizzle Studio.
├── packages/
│ ├── db/
│ │ └── src/db/
│ │ ├── index.ts # DB client + re-exports
│ │ └── schema/
│ │ └── index.ts # All table definitions
│ │
│ ├── api/
│ │ └── src/
│ │ ├── context.ts # tRPC context (session + role lookup)
│ │ ├── init.ts # Procedure definitions
│ │ ├── index.ts # Public exports
│ │ ├── routers/ # Route handlers
│ │ └── auth/ # Better Auth config
│ │
│ └── sdk/
│ └── src/
│ ├── client.ts # API client implementation
│ └── index.ts # Public exports
│
├── apps/
│ ├── web/
│ │ ├── app/ # Next.js App Router
│ │ ├── components/ # React components
│ │ └── package.json
│ │
│ └── cli/
│ └── src/
│ ├── commands/ # CLI commands
│ └── index.ts # Entry point
│
├── turbo.json # Turborepo config
├── pnpm-workspace.yaml # pnpm workspaces
└── drizzle.config.ts # Drizzle Kit config
- Define the table in
packages/db/src/db/schema/index.ts - Add relations and indexes
- Build the db package:
pnpm --filter @complete-web-template/db build - Generate migration:
pnpm drizzle-kit generate
- Create router in
packages/api/src/routers/ - Import tables from
@complete-web-template/db(never directly from drizzle-orm) - Add to root router in
packages/api/src/routers/_app.ts - Build:
pnpm --filter @complete-web-template/api build
Extend packages/sdk/src/client.ts following the existing pattern with tRPC client.
ISC