A tech demo built in a hackathon, this app showcases fullstack web development with a focus on scalable infrastructure and a strong developer experience. The app is mobile, tablet, and web responsive with accessible design system features
- Tech Stack
- Project Structure
- Prerequisites
- Quick Start
- Available Scripts
- Development Workflow
- Pre-commit Hooks
- Database Setup Options
- Architecture
- CI/CD
- Troubleshooting
- Framework: Next.js 14 (App Router)
- Language: TypeScript 5.5 (strict mode)
- Database: PostgreSQL with Drizzle ORM
- Styling: Tailwind CSS
- Monorepo: Turborepo with pnpm workspaces
- Testing: Vitest
- CI/CD: GitHub Actions
- Code Quality: ESLint, Prettier, Husky
/
├── apps/
│ └── web/ # Next.js application
├── packages/
│ ├── database/ # Drizzle schema and client
│ ├── types/ # Shared TypeScript types
│ ├── ui/ # React component library (Atomic Design + Storybook)
│ ├── services/ # Business logic
│ ├── queries/ # Database queries
│ ├── hooks/ # React hooks
│ ├── utils/ # Utility functions
│ ├── typescript-config/ # Shared tsconfig
│ └── eslint-config/ # Shared ESLint config
└── docs/local/ # Local documentation (gitignored)
The @repo/ui package contains a comprehensive design system built with:
- Atomic Design: Components organized as atoms, molecules, organisms, and templates
- Design Tokens: Centralized theming via Tailwind CSS configuration
- Storybook: Interactive component documentation and development environment
- Type Safety: Full TypeScript support with exported prop types
All components use thematic Tailwind classes—no inline styles or magic values.
- Node.js >= 20.18.0 (specified in
.nvmrc) - pnpm >= 9.0.0
- Docker (for local PostgreSQL)
npm install -g pnpmpnpm installcp .env.example .envThe default .env configuration works out of the box for local development:
DATABASE_URL=postgres://postgres:postgres@db.localtest.me:5432/main
NODE_ENV=development# Start database, run migrations, seed data, and start all apps
pnpm devThis single command:
- Starts PostgreSQL + Neon proxy via Docker Compose
- Runs database migrations automatically
- Seeds the database with initial data
- Starts all development servers (web app on port 3000)
Open http://localhost:3000 in your browser.
Note: The database containers keep running after you stop the dev command (Ctrl+C). To completely reset the database, run:
pnpm --filter @repo/database dev:cleanpnpm dev # Start development servers
pnpm build # Build all packages and apps
pnpm lint # Run ESLint across all packages
pnpm type-check # TypeScript type checking
pnpm test # Run all tests
pnpm format # Format code with Prettier
pnpm format:check # Check code formattingpnpm --filter @repo/ui storybook # Start Storybook dev server
pnpm --filter @repo/ui build-storybook # Build Storybook for productionpnpm --filter @repo/database dev # Start database (auto-migrates & seeds)
pnpm --filter @repo/database dev:clean # Stop database and remove all data
pnpm db:generate # Generate migration from schema changes
pnpm db:migrate # Apply pending migrations
pnpm db:studio # Open Drizzle Studio (database GUI)# Run command in specific package/app
pnpm --filter web dev
pnpm --filter @repo/database type-check- Edit schema
- Generate migration:
pnpm db:generate - Review migration file
- Apply migration:
pnpm db:migrate - Types automatically update across codebase
- Create directory in packages
- Add package.json with workspace dependencies
- Add tsconfig.json extending shared config
- Export public API from src/index.ts
- Install with pnpm install
# Run all tests
pnpm test
# Run tests in specific package
pnpm --filter web test
# Run tests in watch mode
pnpm --filter web test --watch
# Generate coverage
pnpm --filter web test --coveragePre-commit hooks automatically run before each commit:
- ESLint (blocks on errors)
- Prettier (auto-formats code)
The default setup uses Docker Compose with Neon's local development proxy, ensuring your local environment matches production (Vercel + Neon):
pnpm dev # Automatically starts Docker, migrates, and seedsFor production or cloud development:
- Set up Neon PostgreSQL database
- Update
DATABASE_URLin.envwith your Neon connection string - Run migrations:
pnpm db:migrate - Seed database:
pnpm --filter @repo/database db:seed
All types derive from Drizzle schema:
// Schema defines structure
export const advocates = pgTable("advocates", { ... });
// Types package exports inferred types
export type Advocate = InferSelectModel<typeof advocates>;
// Import types
import { type Advocate } from "@repo/types";Database Schema → @repo/types → All Packages/Apps
↓
@repo/database
↓
@repo/queries
↓
API Routes
↓
UI Pages
- packages/: Reusable, framework-agnostic code
- apps/: Application-specific implementation
- Pages orchestrate packages, reading like pseudocode
GitHub Actions runs on every PR:
- Linting
- Type checking
- Unit tests
- Build validation
All checks must pass before merge.
# Clear build artifacts
rm -rf **/.next **/dist
pnpm install# Check Docker container
docker-compose ps
# View logs
docker-compose logs db
# Restart containers
docker-compose restart# Clear all caches
rm -rf **/.next **/dist **/.turbo
# Reinstall dependencies
rm -rf node_modules **/node_modules
pnpm install
# Rebuild
pnpm build# Reinitialize Husky
pnpm prepare