Gold Standard Nx Monorepo Template
December 10, 2025 ยท View on GitHub
A production-ready Nx monorepo template demonstrating best practices for building type-safe, full-stack applications with shared business logic across web, server, and mobile platforms.
๐ Live Demo
Dual Frontend Architecture (Deployment Portability Proof)
| Environment | Platform | Web App | API |
|---|---|---|---|
| Production | Vercel | nx-monorepo-web-zwizzly.vercel.app | nx-monoreposerver-production.up.railway.app |
| Production | Railway | nx-monorepoweb-production.up.railway.app | (same API) |
| Staging | Vercel | nx-monorepo-web-git-e5-2-zwizzly.vercel.app | nx-monoreposerver-staging.up.railway.app |
| Staging | Railway | nx-monorepoweb-staging.up.railway.app | (same API) |
Why two frontends? Both Vercel (native Next.js builder) and Railway (Docker container) serve the same application, demonstrating deployment portability. Both connect to the same backend and database.
Walking Skeleton Features:
/health- Health check page demonstrating end-to-end connectivity (web โ API โ database)/api/health- API health endpoint with database connectivity status/sentry-test- Test page for Sentry error tracking validation
What is this?
This is a walking skeleton implementation - a minimal, fully-integrated system that exercises all major architectural layers and infrastructure. It validates that:
- Multiple applications can share code through buildable libraries
- Type safety flows end-to-end from database to UI
- Testing strategy covers all levels (unit, integration, E2E)
- CI/CD pipeline ensures quality gates
- Development workflow is smooth and productive
Current Status: Phase 1 Stage 2 Complete
- โ Next.js 15 web application with Playwright E2E tests
- โ Express server with REST+OpenAPI for type-safe APIs
- โ Four shared packages: database (Prisma), schemas (Zod), api-client (REST+OpenAPI), supabase-client
- โ Complete QA infrastructure: Jest, ESLint, Prettier, CI/CD
- โณ In progress: QA hooks (Husky, lint-staged), Supabase configuration
Technology Stack
Applications
- Web: Next.js 15.2, React 19, Tailwind CSS
- Server: Express with REST+OpenAPI for type-safe APIs
- Mobile (deferred to Phase 2): Expo React Native
Shared Libraries
- Database: Prisma + Supabase (PostgreSQL)
- Schemas: Zod validation schemas with TypeScript type inference
- API Client: REST+OpenAPI client for type-safe API calls
- Supabase Client: Browser and server Supabase client factories
Infrastructure
- Monorepo: Nx 21.6 with remote caching and distributed task execution
- Language: TypeScript 5.9 (strict mode)
- Testing: Jest (unit), Playwright (E2E)
- Linting: ESLint 9 with flat config
- Formatting: Prettier
- CI/CD: GitHub Actions with Nx Cloud integration
- Code Review: CodeRabbit automated PR reviews
- Dependencies: Dependabot for automated updates
Quick Start
Prerequisites
- Node.js 18+ and pnpm 8+
- Git
Installation
# Clone the repository
git clone <repository-url>
cd nx-monorepo
# Install dependencies
pnpm install
# Start development server
pnpm run dev
Verify Setup
# Run all tests
pnpm run test
# Run linting
pnpm run lint
# Build all projects
pnpm run build
# Run E2E tests
pnpm run e2e
Project Structure
nx-monorepo/
โโโ apps/
โ โโโ web/ # Next.js web application
โ โโโ web-e2e/ # Playwright E2E tests
โ โโโ server/ # Express API server
โโโ packages/ # Shared libraries
โ โโโ database/ # Prisma client + utilities
โ โโโ schemas/ # Zod schemas + TypeScript types
โ โโโ api-client/ # REST+OpenAPI client
โ โโโ supabase-client/ # Supabase client configuration
โโโ docs/ # Project documentation
โ โโโ roadmap.md # Implementation roadmap (MVP, Task App PoC, Extensions)
โ โโโ tech-findings-log.md # Technical decisions log
โโโ .ruler/ # AI agent instructions
Architecture
Dependency Flow
The architecture follows strict unidirectional dependencies:
apps (web, mobile) โ api-client โ schemas
โ
apps (server) โ database โ schemas
โ
supabase-client
Key Principles:
- Applications depend on packages, never the reverse
- Packages can depend on other packages, but no circular dependencies
- All packages are buildable libraries with proper TypeScript compilation
- Use
pnpm exec nx graphto visualize dependencies
Type Safety
- Database โ TypeScript: Prisma generates types from schema
- Validation โ Types: Zod schemas with
z.infer<typeof schema> - Server โ Client: REST+OpenAPI provides end-to-end type safety via generated types
- Shared Types: All types centralized in
@nx-monorepo/schemas
Package Naming
All packages use the scoped naming pattern: @nx-monorepo/<package-name>
// Import from shared packages
import { createApiClient } from '@nx-monorepo/api-client';
import { exampleSchema } from '@nx-monorepo/schemas';
import { prisma } from '@nx-monorepo/database';
import { createSupabaseBrowserClient } from '@nx-monorepo/supabase-client';
Common Commands
Note: This documentation uses short-form Nx commands (
nx dev web) for readability. AI agents and automated tools use the equivalent long-form syntax (nx run web:dev) for clarity and explicitness. Both forms are functionally identical.
Workspace Scripts (Recommended)
These convenience scripts run tasks across all projects:
# Start web dev server
pnpm run dev
# Build all projects
pnpm run build
# Run all tests
pnpm run test
# Lint all projects
pnpm run lint
# Run E2E tests
pnpm run e2e
# Check code formatting
pnpm run format:check
# Format all code files
pnpm run format:write
Project-Specific Commands
# Run dev server for web app
pnpm exec nx dev web
# Run server
pnpm exec nx serve server
# Build specific project
pnpm exec nx build web
# Test specific project
pnpm exec nx test schemas
# Show project details
pnpm exec nx show project web
Advanced Nx Commands
# Build all apps
pnpm exec nx run-many -t build --projects=tag:type:app
# Test all libraries
pnpm exec nx run-many -t test --projects=tag:type:lib
# Build only affected projects
pnpm exec nx affected -t build
# View dependency graph
pnpm exec nx graph
# Clear Nx cache
pnpm exec nx reset
Database Commands
The project uses dotenv-cli for environment-specific database operations. This ensures Prisma CLI commands use the correct database credentials based on the environment.
Environment-specific commands:
# Development Database
pnpm run db:push:dev # Push schema changes to development
pnpm run db:migrate:dev # Create and apply migration in development
pnpm run db:migrate:deploy:dev # Apply pending migrations to development
pnpm run db:studio:dev # Open Prisma Studio for development database
# Test Database
pnpm run db:push:test # Push schema changes to test
pnpm run db:migrate:test # Create and apply migration in test
pnpm run db:migrate:deploy:test # Apply pending migrations to test
pnpm run db:studio:test # Open Prisma Studio for test database
# Schema Management
pnpm run db:generate # Generate Prisma Client (after schema changes)
Important Notes:
- โ
Always use npm scripts - Never run raw Prisma CLI commands (
npx prisma) directly - โ
Scripts handle environment loading - dotenv-cli automatically loads the correct
.env.*.localfile - โ Never commit .env files - Only
.env.exampleshould be in git
Environment files (gitignored):
.env.development.local- Development database credentials (DEV Supabase).env.test.local- Test context credentials (STAGING Supabase, loaded whenNODE_ENV=test).env.example- Template for setting up new environments
Note: File names follow
NODE_ENVconvention, not database naming..env.test.localconnects to STAGING because it's loaded when running tests.
Required environment variables:
# Transaction mode pooler for application queries
DATABASE_URL="postgresql://postgres.{project-ref}:{password}@aws-X-{region}.pooler.supabase.com:6543/postgres?pgbouncer=true"
# Session mode pooler for migrations and schema operations
DIRECT_URL="postgresql://postgres.{project-ref}:{password}@aws-X-{region}.pooler.supabase.com:5432/postgres"
# Supabase public credentials (safe for client-side)
NEXT_PUBLIC_SUPABASE_URL="https://{project-ref}.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="{your-anon-key}"
Getting Supabase connection strings:
- Go to: https://supabase.com/dashboard/project/{PROJECT_ID}/settings/database
- Scroll to "Connection string" section
- Select "Session pooler" tab
- Copy the exact hostname from the connection string (don't assume
aws-0, it may beaws-1,aws-2, etc.) - Update both
.env.development.localand.env.test.local
Why dotenv-cli?
Prisma CLI doesn't respect NODE_ENV or load environment-specific files automatically. dotenv-cli explicitly specifies which .env file to load, ensuring database commands target the correct environment. See docs/memories/adopted-patterns.md (Pattern 13) for full rationale.
Troubleshooting:
- "Tenant or user not found" โ Verify pooler hostname matches Supabase dashboard exactly
- "Can't reach database server" โ Check network connectivity, verify you're using pooler (not direct connection)
- Missing DATABASE_URL โ Ensure
.env.*.localfile exists and contains all required variables
For detailed troubleshooting, see:
docs/memories/adopted-patterns.md- Pattern 13 (Database Environment Management)docs/memories/tech-findings-log.md- Supabase pooler hostname and IPv6 findings
Development Workflow
Adding a New Feature
- Define schemas first: Add Zod schemas to
@nx-monorepo/schemas - Implement database layer: Add Prisma models to
@nx-monorepo/database - Create server endpoints: Add REST endpoints in
apps/server - Update API client: Client types auto-update via OpenAPI spec generation
- Implement UI: Build features in
apps/web - Write tests: Add unit tests (packages), integration tests (server), E2E tests (apps)
Creating a Shared Library
# Generate buildable TypeScript library
pnpm exec nx g @nx/js:library my-lib --directory=packages/my-lib --bundler=tsc
# For Prisma packages (special case)
pnpm exec nx g @nx/js:library my-lib --directory=packages/my-lib --bundler=none
Before Committing
The project uses Husky pre-commit hooks (when configured):
- Linting and formatting run automatically
- TypeScript compilation validated
- Tests run for affected projects
Manual validation:
pnpm exec nx affected -t lint test build
Testing Strategy
Unit Tests (Jest)
- Co-located with source files:
src/**/*.spec.ts - Test individual functions, classes, components
- Fast execution (< 5 seconds per suite)
- Target: >= 80% code coverage
# Run all unit tests
pnpm exec nx run-many -t test
# Run with coverage
pnpm exec nx run web:test --coverage
# Watch mode
pnpm exec nx run web:test --watch
Integration Tests
- Test interactions between layers (server โ database)
- Located in
apps/server/src/**/*.spec.ts - May use test database or mocks
E2E Tests (Playwright)
- Full browser-based user journey tests
- Located in
apps/web-e2e/src/ - Test complete flows: page load โ interaction โ data persistence
# Run E2E tests
pnpm exec nx run web-e2e:e2e
# Run in UI mode
pnpm exec nx run web-e2e:e2e --ui
Docker
The project includes Docker configuration for containerized deployments and local testing.
Files
apps/server/Dockerfile- Server container (Express API)apps/web/Dockerfile- Web container (Next.js, for secondary deployment target)docker-compose.yml- Local development stack.dockerignore- Build context exclusions.env.docker.example- Environment variable template
Quick Start
# Copy environment template
cp .env.docker.example .env.docker
# Edit with your Supabase credentials
nano .env.docker
# Build and start containers
docker compose up --build
# Access the applications
# Web: http://localhost:3000
# Server: http://localhost:4000
Building Individual Containers
# Build server container
docker build -f apps/server/Dockerfile -t nx-monorepo-server .
# Build web container
docker build -f apps/web/Dockerfile -t nx-monorepo-web .
Environment Variables
Docker containers accept configuration via environment variables:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | Supabase PostgreSQL connection string |
SUPABASE_URL | Yes | Supabase project URL |
SUPABASE_ANON_KEY | Yes | Supabase anonymous key |
SENTRY_DSN | No | Sentry error tracking DSN |
PORT | No | Server port (default: 4000) |
Deployment Targets
| Target | Platform | Usage |
|---|---|---|
| Primary | Vercel (web) + Railway (server) | Server Dockerfile deployed to Railway |
| Secondary | Railway (both) | Both Dockerfiles deployed to Railway |
See docs/architecture-decisions.md for platform selection rationale.
CI/CD
GitHub Actions workflow runs on all PRs and main branch commits:
- Install dependencies
- Install Playwright browsers
- Run:
pnpm exec nx run-many -t lint test build typecheck e2e - Execute
nx fix-ciif failures occur (self-healing)
Nx Cloud Integration: Enabled for distributed caching and task execution across team and CI.
Documentation
- roadmap.md: Implementation roadmap (MVP, Task App PoC, Extensions)
- testing-reference.md: Comprehensive Jest and testing configuration reference
- troubleshooting.md: Common development troubleshooting solutions
- tech-findings-log.md: Technical decisions and empirical findings
- CLAUDE.md: AI agent instructions (auto-generated from
.ruler/AGENTS.md)
External Services
- Nx Cloud: Remote caching, distributed execution, CI analytics
- CodeRabbit: Automated PR reviews
- Dependabot: Automated dependency updates
- Sentry: Error tracking and performance monitoring (setup guide)
Troubleshooting
Nx Cache Issues
pnpm exec nx reset
rm -rf node_modules
pnpm install
Build Failures
# Build in dependency order
pnpm exec nx run-many -t build
# Build only affected
pnpm exec nx affected -t build
Jest Slow Exit (Windows)
If Jest hangs after tests complete:
# Disable Nx daemon
NX_DAEMON=false pnpm exec nx run-many -t test
# Or disable Nx Cloud
pnpm exec nx run-many -t test --no-cloud
See docs/memories/tech-findings-log.md for detailed diagnosis.
Contributing
This is a template project. To use it:
- Clone the repository
- Remove git history:
rm -rf .git && git init - Update package names in
package.jsonfiles - Update scoped package names throughout the codebase
- Configure external services (Nx Cloud, Sentry, etc.)
Learn More
- Nx Documentation
- Next.js Documentation
- Prisma Documentation
- OpenAPI Specification
- Playwright Documentation
License
MIT
Built with Nx - Smart Monorepos ยท Fast CI