This project is using upstream from thaitype-stack-mongodb-template, however, this will be a sql-based template with drizzleorm
A production-ready Todo Application Template built with Next.js 15, implementing enterprise-grade patterns and modern development practices. This template serves as a robust foundation for building scalable full-stack applications with authentication, CRUD operations, and real-time updates.
- π Authentication System - Better Auth with email/password
- π Todo Management - Full CRUD operations with real-time updates
- π¨ Modern UI - Mantine components with responsive design
- ποΈ Enterprise Architecture - Entity-based repository pattern
- π Type Safety - Full TypeScript with tRPC API layer
- π Database - PostgreSQL with Drizzle ORM
- β‘ Performance - Next.js 15 with App Router and React 19
- π― Production Ready - ESLint, Prettier, structured logging
- Next.js 15 - React framework with App Router
- React 19 - Latest React with concurrent features
- TypeScript - Strict type safety
- tRPC - End-to-end typesafe APIs
- PostgreSQL - Relational database
- Drizzle ORM - TypeScript ORM with SQL-like syntax
- Better Auth - Modern authentication library
- Zod - TypeScript-first schema validation
- Mantine - React components library
- Tailwind CSS - Utility-first CSS framework
- @tabler/icons-react - Icon library
This template implements an Entity-Based Repository Architecture with strict separation of concerns:
src/
βββ app/ # Next.js App Router
β βββ _components/ # UI components
β βββ api/trpc/ # tRPC API routes
β βββ (auth pages) # Login, register, profile
βββ server/
β βββ api/ # tRPC routers and procedures
β βββ domain/ # Business domain layer
β β βββ models/ # Domain interfaces (string IDs)
β β βββ repositories/ # Repository interfaces
β βββ infrastructure/ # Data access layer
β β βββ entities/ # Database entities (ObjectIds)
β β βββ repositories/ # Repository implementations
β βββ services/ # Business logic (database-agnostic)
β βββ lib/ # Shared utilities
βββ trpc/ # Client-side tRPC setup
- Entity-First Design: All types derive from database entities
- Domain-Driven Services: Business logic with string-based IDs
- Type-Safe Validation: Zod schemas with
matches<T>()
utility - Native Features: Drizzle's built-in UUID and timestamp management
- Structured Logging: Request tracing and error context
- Node.js 18+
- pnpm (recommended) or npm
- Docker & Docker Compose (recommended for local development)
- PostgreSQL instance (local, cloud, or via Docker)
git clone <repository-url> my-todo-app
cd my-todo-app
pnpm install
-
Start Services
# Start PostgreSQL and Drizzle Gateway containers docker-compose up -d
-
Environment Setup
cp .env.example .env
The
.env.example
is pre-configured for Docker development:# Database (connects to Docker container via localhost port mapping) DATABASE_URL="postgresql://devuser:devpass@localhost:5432/devdb" # Authentication BETTER_AUTH_SECRET="your-secret-key-change-in-production" NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3000" # App Configuration NODE_ENV="development" PORT="3000"
If you prefer to use a local PostgreSQL installation:
-
Create Database
createdb devdb
-
Environment Setup
cp .env.example .env
Update your
.env
file with your local PostgreSQL credentials:DATABASE_URL="postgresql://your-username:your-password@localhost:5432/devdb"
# Generate migration files from schema
pnpm db:generate
# Apply migrations to create database tables
pnpm db:migrate
# Seed the database with sample data (optional)
pnpm db:seed
pnpm dev
Visit http://localhost:3000 to see your app!
This template uses Drizzle ORM with PostgreSQL. Here's the recommended workflow:
# 1. Generate migration files from your schema changes
pnpm db:generate
# 2. Review generated migrations in ./drizzle/ directory
# 3. Apply migrations to database
pnpm db:migrate
# 4. Seed with sample data (optional)
pnpm db:seed
# Push schema directly to database (bypasses migrations)
pnpm db:push
# Seed with sample data (optional)
pnpm db:seed
# Start all services (PostgreSQL + Drizzle Gateway)
docker-compose up -d
# View service status
docker-compose ps
# View logs
docker-compose logs postgres
docker-compose logs drizzle-gateway
# Stop all services
docker-compose down
# Stop and remove volumes (WARNING: destroys all data)
docker-compose down -v
-
Database Setup With Docker, the database is automatically created. For local PostgreSQL:
# Using psql (if PostgreSQL is installed locally) createdb devdb # Or connect to your cloud database provider # Update DATABASE_URL in .env with your connection string
-
Set Up Schema
# Generate initial migration files pnpm db:generate # Apply migrations to create tables pnpm db:migrate
-
Add Sample Data
# Populate database with test data pnpm db:seed
Running pnpm db:seed
creates:
- 2 Roles:
admin
,user
With Docker (Recommended):
# Drizzle Gateway is automatically available via Docker
# Opens at http://localhost:4983
open http://localhost:4983
Without Docker:
# Open Drizzle Studio - visual database browser
pnpm db:studio
# Opens at http://localhost:4983
# WARNING: This destroys all data
pnpm db:drop
# Recreate schema
pnpm db:migrate
# Add sample data
pnpm db:seed
Command | Use Case | Description |
---|---|---|
db:migrate |
Production | Applies versioned migrations, maintains history |
db:push |
Development | Direct schema sync, no migration files |
# This means tables haven't been created yet
# Solution: Run migrations first
pnpm db:migrate
pnpm db:seed
Docker Environment:
- Check if Docker containers are running:
docker-compose ps
- Restart services:
docker-compose restart postgres
- Check container logs:
docker-compose logs postgres
- Verify your
.env
uses:postgresql://devuser:devpass@localhost:5432/devdb
Local PostgreSQL:
- Check your
DATABASE_URL
in.env
- Ensure PostgreSQL service is running
- Verify database exists and credentials are correct
# Reset and start fresh (loses all data)
pnpm db:drop
pnpm db:generate
pnpm db:migrate
pnpm db:seed
The seed script automatically loads .env
file. Ensure your .env
contains:
Docker Development:
NODE_ENV=development
DATABASE_URL=postgresql://devuser:devpass@localhost:5432/devdb
Local PostgreSQL:
NODE_ENV=development
DATABASE_URL=postgresql://your-username:your-password@localhost:5432/devdb
- App Connection: Uses
localhost:5432
(host machine to container port mapping) - Drizzle Gateway Connection: Uses
postgres:5432
(container-to-container networking) - Credentials: Both use
devuser:devpass
for databasedevdb
# Development
pnpm dev # Start development server with Turbo
pnpm build # Build for production
pnpm start # Start production server
pnpm preview # Build and start production server
# Database
pnpm db:generate # Generate migration files from schema
pnpm db:migrate # Apply migrations to create/update tables
pnpm db:push # Push schema directly (development only)
pnpm db:studio # Open Drizzle Studio (database GUI)
pnpm db:seed # Seed database with sample data
pnpm db:drop # Drop all database tables (destructive)
# Code Quality
pnpm lint # Run ESLint
pnpm lint:fix # Fix ESLint errors
pnpm typecheck # Run TypeScript check
pnpm check # Run lint + typecheck together
# Formatting
pnpm format:check # Check Prettier formatting
pnpm format:write # Apply Prettier formatting
This template follows enterprise-grade design patterns documented in docs/repo-architecture.md
.
- Single Source of Truth - Types derive from database entities
- Dedicated Methods - Explicit operations over generic CRUD
- Repository Validation - Single validation point at data boundary
- Service Abstraction - Database-agnostic business logic
// Domain Model (strings)
interface Todo {
id: string;
title: string;
completed: boolean;
userId: string;
}
// Database Entity (UUIDs)
interface DbTodoEntity {
id: string;
title: string;
completed: boolean;
userId: string;
}
// Repository Interface (domain types)
interface ITodoRepository {
create(input: TodoCreateData, context: RepositoryContext): Promise<Todo>;
updateContent(id: string, input: TodoContentUpdate, userId: string): Promise<void>;
updateStatus(id: string, input: TodoStatusUpdate, userId: string): Promise<void>;
}
// Service Layer (business logic)
class TodoService {
async createTodo(userId: string, request: CreateTodoRequest): Promise<Todo> {
// Business validation and logic
const context = createRepositoryContext(userId);
return this.todoRepository.create(request, context);
}
}
Built-in authentication system with Better Auth:
- Email/Password authentication
- Session Management with secure cookies
- Protected Routes with middleware
- User Context propagation throughout the app
// Client-side
import { useSession, signIn, signOut } from '~/lib/auth-client';
function MyComponent() {
const { data: session, isPending } = useSession();
if (session?.user) {
return <div>Hello, {session.user.name}!</div>;
}
return <button onClick={() => signIn.email(credentials)}>Sign In</button>;
}
interface DbUserEntity {
id: string; // UUID primary key (auto-generated)
email: string; // Unique email address
name: string; // User display name
bio?: string; // Optional user biography
avatar?: string; // Optional avatar URL
website?: string; // Optional website URL
// Auto-managed timestamps
createdAt: Date; // Auto-set on creation
updatedAt: Date; // Auto-updated on changes
}
interface DbRoleEntity {
id: string; // UUID primary key (auto-generated)
name: string; // Unique role name (e.g., 'admin', 'user')
description?: string; // Optional role description
// Auto-managed timestamps
createdAt: Date; // Auto-set on creation
updatedAt: Date; // Auto-updated on changes
}
interface DbUserRoleEntity {
id: string; // UUID primary key (auto-generated)
userId: string; // Foreign key to users.id
roleId: string; // Foreign key to roles.id
// Auto-managed timestamps
createdAt: Date; // Auto-set on creation
updatedAt: Date; // Auto-updated on changes
// Unique constraint on (userId, roleId)
}
interface DbTodoEntity {
id: string; // UUID primary key (auto-generated)
title: string; // Todo title
description?: string; // Optional todo description
completed: boolean; // Completion status
userId: string; // Foreign key to users.id
// Auto-managed timestamps
createdAt: Date; // Auto-set on creation
updatedAt: Date; // Auto-updated on changes
}
The service layer works with normalized domain models:
interface User {
id: string; // String representation of UUID
email: string;
name: string;
roles: string[]; // Array of role names (e.g., ['admin', 'user'])
bio?: string;
avatar?: string;
website?: string;
createdAt: Date;
updatedAt: Date;
}
- UUID Primary Keys: All tables use UUID for primary keys
- Automatic Timestamps:
createdAt
andupdatedAt
managed automatically - Normalized Roles: Many-to-many relationship between users and roles
- Foreign Key Constraints: Referential integrity enforced
- Unique Constraints: Email uniqueness, role name uniqueness, user-role pairs
pnpm build
Set these in your production environment:
NODE_ENV="production"
DATABASE_URL="postgresql://user:pass@host:5432/dbname"
BETTER_AUTH_SECRET="secure-production-secret"
NEXT_PUBLIC_BETTER_AUTH_URL="https://yourdomain.com/api/auth"
pnpm dlx vercel
pnpm dlx @railway/cli deploy
docker build -t todo-app .
docker run -p 3000:3000 todo-app
- Define Domain Model in
~/server/domain/models/
- Create Database Entity in
~/server/infrastructure/entities/
- Build Repository Interface in
~/server/domain/repositories/
- Implement Repository in
~/server/infrastructure/repositories/
- Create Service Layer in
~/server/services/
- Add tRPC Router in
~/server/api/routers/
- Build UI Components in
~/app/_components/
βββ Authentication System β
βββ Todo CRUD Operations β
βββ Real-time Updates β
βββ Responsive UI β
βββ Production Logging β
βββ Type Safety β
- Entity Architecture Guide - Repository pattern details
- CLAUDE.md - AI development guidelines
- API Documentation - tRPC endpoint reference (generated)
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes following the established patterns
- Run quality checks:
pnpm check
- Commit your changes:
git commit -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with T3 Stack foundation
- UI components by Mantine
- Database ORM by Drizzle
- Authentication by Better Auth
Made with β€οΈ for modern web development
This template represents production-ready patterns and can be used as a foundation for enterprise applications.