Skip to content

thaitype/thaitype-stack-postgresql-template

Repository files navigation

Todo App Template - Next.js Enterprise Stack

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.

✨ Features

  • πŸ” 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

Screenshot

πŸš€ Tech Stack

Core Framework

Backend & API

  • 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

Frontend & UI

Development Tools

πŸ—οΈ Architecture

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

Key Patterns

  • 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

🚦 Quick Start

Prerequisites

  • Node.js 18+
  • pnpm (recommended) or npm
  • Docker & Docker Compose (recommended for local development)
  • PostgreSQL instance (local, cloud, or via Docker)

1. Clone and Install

git clone <repository-url> my-todo-app
cd my-todo-app
pnpm install

2. Choose Development Environment

Option A: Docker Development (Recommended)

  1. Start Services

    # Start PostgreSQL and Drizzle Gateway containers
    docker-compose up -d
  2. 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"

Option B: Local PostgreSQL

If you prefer to use a local PostgreSQL installation:

  1. Create Database

    createdb devdb
  2. 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"

3. Database Setup

# 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

4. Start Development

pnpm dev

Visit http://localhost:3000 to see your app!

πŸ—„οΈ Database Setup Guide

Understanding Database Commands

This template uses Drizzle ORM with PostgreSQL. Here's the recommended workflow:

1. Migration-Based Workflow (Recommended for Production)

# 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

2. Push Workflow (Development Only)

# Push schema directly to database (bypasses migrations)
pnpm db:push

# Seed with sample data (optional)  
pnpm db:seed

Docker Development Setup

Starting the Stack

# 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

Stopping the Stack

# Stop all services
docker-compose down

# Stop and remove volumes (WARNING: destroys all data)
docker-compose down -v

First Time Setup (Any Environment)

  1. 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
  2. Set Up Schema

    # Generate initial migration files
    pnpm db:generate
    
    # Apply migrations to create tables
    pnpm db:migrate
  3. Add Sample Data

    # Populate database with test data
    pnpm db:seed

Sample Data Overview

Running pnpm db:seed creates:

  • 2 Roles: admin, user

Database Management

View Database

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

Reset Database (Development)

# WARNING: This destroys all data
pnpm db:drop

# Recreate schema
pnpm db:migrate

# Add sample data
pnpm db:seed

Migration vs Push

Command Use Case Description
db:migrate Production Applies versioned migrations, maintains history
db:push Development Direct schema sync, no migration files

Troubleshooting

"relation does not exist" Error

# This means tables haven't been created yet
# Solution: Run migrations first
pnpm db:migrate
pnpm db:seed

Database Connection Errors

Docker Environment:

  1. Check if Docker containers are running: docker-compose ps
  2. Restart services: docker-compose restart postgres
  3. Check container logs: docker-compose logs postgres
  4. Verify your .env uses: postgresql://devuser:devpass@localhost:5432/devdb

Local PostgreSQL:

  1. Check your DATABASE_URL in .env
  2. Ensure PostgreSQL service is running
  3. Verify database exists and credentials are correct

Migration Conflicts

# Reset and start fresh (loses all data)
pnpm db:drop
pnpm db:generate  
pnpm db:migrate
pnpm db:seed

Environment Variables Not Loaded

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

Docker Networking Notes

  • 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 database devdb

πŸ“‹ Development Commands

# 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

πŸ›οΈ Design Patterns

This template follows enterprise-grade design patterns documented in docs/repo-architecture.md.

Core Principles

  1. Single Source of Truth - Types derive from database entities
  2. Dedicated Methods - Explicit operations over generic CRUD
  3. Repository Validation - Single validation point at data boundary
  4. Service Abstraction - Database-agnostic business logic

Example: Todo Repository Pattern

// 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);
  }
}

πŸ” Authentication

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

Usage Example

// 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>;
}

πŸ“Š Database Schema

Users Table

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
}

Roles Table

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
}

User Roles Junction Table

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)
}

Todos Table

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
}

Domain Models (Service Layer)

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;
}

Database Features

  • UUID Primary Keys: All tables use UUID for primary keys
  • Automatic Timestamps: createdAt and updatedAt 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

πŸš€ Deployment

Production Build

pnpm build

Environment Variables

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"

Hosting Options

Vercel (Recommended)

pnpm dlx vercel

Railway

pnpm dlx @railway/cli deploy

Docker

docker build -t todo-app .
docker run -p 3000:3000 todo-app

πŸ› οΈ Customization

Adding New Features

  1. Define Domain Model in ~/server/domain/models/
  2. Create Database Entity in ~/server/infrastructure/entities/
  3. Build Repository Interface in ~/server/domain/repositories/
  4. Implement Repository in ~/server/infrastructure/repositories/
  5. Create Service Layer in ~/server/services/
  6. Add tRPC Router in ~/server/api/routers/
  7. Build UI Components in ~/app/_components/

Template Structure

β”œβ”€β”€ Authentication System βœ…
β”œβ”€β”€ Todo CRUD Operations βœ…  
β”œβ”€β”€ Real-time Updates βœ…
β”œβ”€β”€ Responsive UI βœ…
β”œβ”€β”€ Production Logging βœ…
β”œβ”€β”€ Type Safety βœ…

πŸ“š Documentation

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes following the established patterns
  4. Run quality checks: pnpm check
  5. Commit your changes: git commit -m 'Add amazing feature'
  6. Push to the branch: git push origin feature/amazing-feature
  7. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments


Made with ❀️ for modern web development

This template represents production-ready patterns and can be used as a foundation for enterprise applications.

About

Type-Safe Next.js Stack using Simple Clean Architecture with PostgreSQL Template For AI Friendly

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published