A production-ready full-stack TypeScript monorepo featuring NestJS backend API and Next.js frontend with authentication, i18n, and modern development tools.
- Tech Stack
- Project Structure
- Prerequisites
- Getting Started
- Development
- Testing
- Project Architecture
- Available Scripts
- Common Issues
- NestJS - Progressive Node.js framework
- TypeORM - TypeScript ORM with Active Record pattern
- PostgreSQL - Relational database
- JWT - Authentication with access/refresh tokens
- Passport - Authentication middleware
- Swagger - API documentation
- Class Validator - DTO validation
- i18n - Internationalization support
- Sharp - Image processing
- Helmet - Security headers
- Next.js 16 - React framework with App Router
- React 19 - Latest React with Server Components
- TanStack Query - Server state management
- React Hook Form - Form management
- Zod - Schema validation
- next-intl - Internationalization
- Tailwind CSS v4 - Utility-first CSS
- shadcn/ui - UI component library
- Sonner - Toast notifications
- Turborepo - Build system and task runner
- pnpm - Fast, disk space efficient package manager
- TypeScript - Type safety across all packages
- Shared Packages - API client, design system, env config, interfaces
nest-example/
├── apps/
│ ├── backend/ # NestJS API
│ │ ├── src/
│ │ │ ├── common/ # Shared utilities, guards, decorators
│ │ │ ├── database/ # TypeORM config, migrations, seeding
│ │ │ ├── modules/
│ │ │ │ ├── auth/ # Authentication (JWT, guards, strategies)
│ │ │ │ ├── users/ # User management
│ │ │ │ └── ...
│ │ │ └── main.ts # App entry point
│ │ ├── .env # Backend environment variables
│ │ └── package.json
│ │
│ └── web/ # Next.js Frontend
│ ├── src/
│ │ ├── app/ # App Router pages
│ │ │ └── [locale]/ # i18n routing
│ │ │ ├── (main)/
│ │ │ │ ├── (public)/ # Public pages (auth)
│ │ │ │ └── (private)/ # Protected pages
│ │ ├── components/ # Shared UI components
│ │ ├── context/ # React contexts (auth, theme)
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utilities, actions, API client
│ │ ├── i18n/ # Internationalization config
│ │ └── proxy.ts # Middleware (auth, i18n routing)
│ ├── .env.local # Frontend environment variables
│ └── package.json
│
├── packages/
│ ├── api-client/ # Auto-generated TypeScript API client
│ ├── design/ # Shared UI components (shadcn/ui)
│ ├── env/ # Environment variable schemas
│ └── interfaces/ # Shared TypeScript interfaces
│
├── tooling/ # ESLint, TypeScript configs
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # pnpm workspace definition
└── package.json # Root package with scripts
Before you begin, ensure you have the following installed:
- Node.js >= 20.0.0 (Download)
- pnpm >= 9.0.0 (Install:
npm install -g pnpm) - PostgreSQL >= 14 (Download)
- Git (Download)
git clone <your-repository-url>
cd nest-example# Install all dependencies for all packages
pnpm installThis will install dependencies for:
- Root workspace
- Backend application
- Frontend application
- All shared packages
- Start PostgreSQL service:
# macOS (Homebrew)
brew services start postgresql
# Linux (systemd)
sudo systemctl start postgresql
# Windows
# Start via Services or pgAdmin- Create a database:
# Connect to PostgreSQL
psql postgres
# Create database and user
CREATE DATABASE nestapp;
CREATE USER your_username WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE nestapp TO your_username;
\qdocker run --name postgres-dev \
-e POSTGRES_PASSWORD=your_password \
-e POSTGRES_USER=your_username \
-e POSTGRES_DB=nestapp \
-p 5432:5432 \
-d postgres:16Create/update apps/backend/.env:
cd apps/backend
cp .env.example .envEdit apps/backend/.env:
# Application
NODE_ENV=development
PORT=3030
# Database (PostgreSQL)
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=your_username # Change this
DB_PASSWORD=your_password # Change this
DB_DATABASE=nestapp
DB_SYNCHRONIZE=true # Auto-sync schema (dev only!)
DB_LOGGING=true # Log SQL queries
# JWT Secrets (CHANGE THESE!)
JWT_ACCESS_SECRET=your-super-secret-access-key-change-this-in-production
JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production
JWT_ACCESS_EXPIRATION=7d # Access token expiration
JWT_REFRESH_EXPIRATION=7d # Refresh token expiration
# CORS
CORS_ORIGIN=http://localhost:3000
# File Upload
MAX_FILE_SIZE=5242880 # 5MB in bytes
UPLOAD_DIR=./uploads
# Rate Limiting
THROTTLE_TTL=60 # Time window in seconds
THROTTLE_LIMIT=100 # Max requests per windowCreate/update apps/web/.env.local:
cd apps/web
cp .example.env .env.localEdit apps/web/.env.local:
# API URLs
API_URL="http://localhost:3030" # Backend URL (server-side)
NEXT_PUBLIC_API_URL="http://localhost:3000" # Frontend proxy URL (client-side)
NEXT_PUBLIC_BACKEND_URL="http://localhost:3030"The backend uses TypeORM with auto-synchronization enabled in development. When you start the backend for the first time, it will automatically create all tables.
Optionally, seed the database with sample data:
pnpm db:seedThis creates:
- Admin user:
admin@example.com/Admin123! - Regular user:
user@example.com/User123!
# From root directory
pnpm devThis starts:
- Backend API: http://localhost:3030
- Frontend: http://localhost:3000
Terminal 1 - Backend:
cd apps/backend
pnpm devTerminal 2 - Frontend:
cd apps/web
pnpm dev-
Backend API: Open http://localhost:3030/api
- You should see the Swagger API documentation
-
Frontend: Open http://localhost:3000
- You should see the homepage
- Navigate to
/auth/sign-into log in
-
Test Login:
- Email:
admin@example.com - Password:
Admin123!
- Email:
# Run both frontend and backend in development mode
pnpm dev
# Build all packages and applications
pnpm build
# Run production build
pnpm start
# Run linting across all packages
pnpm lint
# Run type checking
pnpm typecheck# Start backend in watch mode
cd apps/backend
pnpm dev
# Build backend
pnpm build
# Run production mode
pnpm start
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run E2E tests
pnpm test:e2e
# Generate TypeORM migration
pnpm typeorm migration:generate src/database/migrations/MigrationName
# Run migrations
pnpm typeorm migration:run
# Revert last migration
pnpm typeorm migration:revert
# Seed database
pnpm db:seed# Start frontend in development mode
cd apps/web
pnpm dev
# Build for production
pnpm build
# Run production build locally
pnpm start
# Check TypeScript types
pnpm check-types
# Lint code
pnpm lintThe API client is automatically generated from the backend's Swagger documentation:
# Generate API client from backend OpenAPI spec
pnpm generate:api
# Build API client package
pnpm build:api-clientWhen to regenerate:
- After adding new API endpoints
- After modifying DTOs or response types
- After changing API routes
The design system package (@myorg/design) contains shared UI components:
cd packages/design
# Add a new component
pnpm add-component button
# Build design system
pnpm buildWhen you modify entities, create a migration:
cd apps/backend
# Generate migration from entity changes
pnpm typeorm migration:generate src/database/migrations/AddUserProfile
# Run pending migrations
pnpm typeorm migration:run# Seed database with initial data
pnpm db:seed
# Or from root
pnpm --filter @nest-example/backend db:seedThis project uses Husky to enforce code quality through git hooks.
Git hooks are automatically installed when you run pnpm install (via the prepare script). You don't need to do anything manually!
Pre-commit Hook (.husky/pre-commit):
- Runs ESLint on both backend and frontend
- Automatically fixes auto-fixable issues
- Prevents commit if there are linting errors
- Warnings won't block commits
Commit Message Hook (.husky/commit-msg):
- Validates commit message format
- Enforces Conventional Commits
Your commit messages must follow this format:
<type>: <description>
[optional body]
[optional footer]
Valid types:
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting)refactor- Code refactoringperf- Performance improvementstest- Adding or updating testsbuild- Build system or dependenciesci- CI/CD changeschore- Other changesrevert- Revert a previous commit
Examples:
git commit -m "feat: add user profile page"
git commit -m "fix: resolve login redirect issue"
git commit -m "docs: update README with setup instructions"
git commit -m "refactor: simplify authentication logic"If you absolutely need to skip hooks (e.g., WIP commit):
git commit --no-verify -m "wip: work in progress"Note: This is not recommended as it bypasses code quality checks.
If hooks aren't working, you can manually reinstall them:
pnpm prepare- User submits credentials via frontend login form
- Frontend calls
/api/auth/loginserver action - Server action proxies request to backend API
- Backend validates credentials, generates JWT tokens
- Frontend stores tokens in httpOnly cookies
- Middleware (proxy.ts) validates tokens on each request
- Protected pages check authentication state
// Frontend: Using the auto-generated API client
import { createServerApi } from '@myorg/api-client';
const api = createServerApi({
baseUrl: env.API_URL,
token: 'optional-auth-token',
});
const users = await api.api.usersQueryControllerFindAll();The project supports multiple languages (English, Serbian):
Backend:
- Uses
nestjs-i18nfor API responses - Translation files:
apps/backend/src/i18n/{lang}/
Frontend:
- Uses
next-intlfor UI translations - Translation files:
apps/web/src/i18n/messages/{lang}.json - URL structure:
/en/...or/sr/...
The Next.js middleware (apps/web/src/proxy.ts) handles:
- Authentication checks (JWT validation)
- Role-based access control (ADMIN routes)
- Locale routing (i18n)
- Security headers
Protected route patterns:
/admin/*- Requires ADMIN role- All routes except public routes - Require authentication
| Command | Description |
|---|---|
pnpm dev |
Run all apps in development mode |
pnpm build |
Build all packages and apps |
pnpm start |
Start all apps in production mode |
pnpm lint |
Lint all packages |
pnpm test |
Run tests in all packages |
pnpm typecheck |
Type-check all packages |
pnpm clean |
Remove all node_modules and build artifacts |
pnpm generate:api |
Generate API client from OpenAPI spec |
pnpm db:seed |
Seed the database |
| Command | Description |
|---|---|
pnpm dev |
Start in watch mode |
pnpm build |
Build for production |
pnpm start |
Start production server |
pnpm test |
Run unit tests |
pnpm test:e2e |
Run E2E tests |
pnpm typeorm |
Run TypeORM CLI commands |
pnpm db:seed |
Seed database |
| Command | Description |
|---|---|
pnpm dev |
Start Next.js dev server |
pnpm build |
Build for production |
pnpm start |
Start production server |
pnpm lint |
Lint code |
pnpm check-types |
Type-check without building |
Solution:
- Ensure PostgreSQL is running:
psql -U postgres -c "SELECT 1" - Verify database exists:
psql -U postgres -l - Check credentials in
apps/backend/.env - Ensure
DB_HOST=localhostandDB_PORT=5432
Solution:
# Find process using port
lsof -ti:3000 # or :3030
# Kill the process
kill -9 <PID>
# Or change ports in .env filesSolution:
# Clean install
pnpm clean
pnpm install
# Rebuild packages
pnpm buildSolution:
# Regenerate API client after backend changes
pnpm generate:apiSolution:
cd apps/backend
# Development: Let TypeORM sync (if DB_SYNCHRONIZE=true)
# Just restart the server
# Production: Create and run migration
pnpm typeorm migration:generate src/database/migrations/SyncSchema
pnpm typeorm migration:runBoth frontend and backend support hot reload:
- Backend: NestJS watch mode restarts on file changes
- Frontend: Next.js Fast Refresh updates on save
Access Swagger docs at http://localhost:3030/api when backend is running.
- Never commit
.envor.env.localfiles - Use
.env.exampleas templates - Update both when adding new variables
- Development: Use
DB_SYNCHRONIZE=truefor auto-sync - Production: ALWAYS use migrations, set
DB_SYNCHRONIZE=false
After modifying backend DTOs or endpoints:
pnpm generate:apiThis keeps frontend types in sync with backend API.
- Set environment variables:
NODE_ENV=production
DB_SYNCHRONIZE=false- Build and run:
pnpm build
pnpm start- Run migrations:
pnpm typeorm migration:run- Build:
pnpm build- Start:
pnpm startOr deploy to Vercel/Netlify directly.
This project includes custom Claude Code skills that automate feature scaffolding. When working with Claude Code, you can generate complete features by describing what you need in natural language.
| Command | Description |
|---|---|
/project:create-full-feature |
End-to-end: backend module + frontend UI pages + migration |
/project:create-backend-module |
NestJS module (entity, DTOs, actions, controllers, service, i18n) |
/project:create-frontend-feature |
Next.js feature pages (list, edit, create with table/forms/filters) |
/project:create-entity |
TypeORM entity with database registration |
Explicit invocation:
/project:create-full-feature A blog module with posts that have title, body,
status (DRAFT/PUBLISHED), and belong to categories
Natural language (auto-detected): Simply describe what you want - Claude will automatically detect the intent and invoke the right skill:
"I want a notifications feature where admins can send notifications to users"
"Add an API for managing products with name, price, and category"
"Create admin pages for the orders module"
- Project skills (
.claude/commands/) define what to build and which project patterns to follow - They can be combined with execution modes like
autopilotfor fully autonomous feature generation - All generated code follows the project's conventions documented in
CLAUDE.md - Skills reference the Users module (
apps/backend/src/modules/users/) as the canonical pattern
A full feature (/project:create-full-feature) creates:
Backend (apps/backend/src/modules/{module}/):
- Entity with proper field ordering and indexes
- DTOs (Create, Update, Query with filters, Response)
- Actions (Create, Update, Delete) with business logic
- Service with QueryHelper pagination
- Controllers (Query + Command) with Swagger docs
- i18n files (en.json, sr.json)
- Module registration in
database.module.tsandapp.module.ts
Frontend (apps/web/src/app/[locale]/(main)/(private)/{section}/{feature}/):
- List page with server-side table and filters
- Edit page with form, Zod schema, and server actions
- Create page (optional) with form and validation
- All types imported from
@myorg/api-client