Technical Introduction
A modern full-stack TypeScript monorepo combining React 19, NestJS, TanStack Router, Drizzle ORM, and PostgreSQL - all managed with Turborepo and pnpm workspaces.
Backend
- NestJS - Progressive Node.js framework with TypeScript
- Drizzle ORM - TypeScript-first ORM with excellent type inference
- PostgreSQL - Robust, scalable relational database
- Passport.js - Authentication with JWT, Google OAuth, Facebook OAuth
- Swagger/OpenAPI - Auto-generated API documentation
Packages
- @nomor/db - Centralized database schemas and migrations
- @nomor/email - Email service with SMTP support
- @nomor/otp - OTP generation and validation
- @nomor/storage - File storage (local, S3, Supabase)
Developer Experience
- TypeScript - End-to-end type safety
- Turborepo - Optimized monorepo build system with smart caching
- pnpm - Fast, disk-efficient package manager
- Biome - Fast linting and formatting
- Husky - Git hooks for code quality
- Docker Compose - Easy PostgreSQL setup
Prerequisites
Before you begin, ensure you have the following installed on your system:
-
pnpm (v10 or higher)
- Install globally:
npm install -g pnpm - Verify installation:
pnpm --version
- Install globally:
-
Docker & Docker Compose (for PostgreSQL database)
- Download Docker Desktop from docker.com
- Verify installation:
docker --versionanddocker compose version - Make sure Docker is running
-
Git (for version control)
- Download from git-scm.com
- Verify installation:
git --version
Note: If you prefer to use a local PostgreSQL installation instead of Docker, you can skip Docker and install PostgreSQL manually from postgresql.org
Project Structure
This is a monorepo managed by Turborepo. Here's how the code is organized:
nomor/
├── apps/ # Applications
│ └── api/ # Backend NestJS API (Port 5000)
│ ├── src/
│ │ ├── modules/ # Feature modules (auth, users, etc.)
│ │ └── database/ # Database configuration
│ ├── sample.env # Environment variables template
│ ├── nest-cli.json # NestJS CLI configuration
│ └── package.json
│
├── packages/ # Shared packages
│ ├── db/ # Database layer (Drizzle ORM)
│ │ ├── src/
│ │ │ └── schema/ # Database schemas
│ │ ├── drizzle.config.ts
│ │ └── docker-compose.yml # PostgreSQL container
│ │
│ ├── email/ # Email service package
│ ├── otp/ # OTP (One-Time Password) utilities
│ └── storage/ # File storage utilities
│
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # pnpm workspace configuration
└── package.json # Root package.jsonUnderstanding the Monorepo Structure
Applications (apps/)
apps/api: Backend API built with NestJS framework. Handles authentication, business logic, and data operations. Runs on port 5000 by default.
Packages (packages/)
@nomor/db: Centralized database layer using Drizzle ORM. Contains schemas, migrations, and database connection. Includes Docker Compose for easy PostgreSQL setup.@nomor/email: Reusable email service for sending transactional emails via SMTP.@nomor/otp: OTP generation and validation utilities for two-factor authentication.@nomor/storage: File upload and storage management supporting local filesystem and cloud storage providers.
Getting Started
Follow these steps to get your development environment running:
1. Clone the Repository
If you haven't already, clone this repository:
git clone <your-repo-url>
cd nomor2. Install Dependencies
Install all dependencies for the entire monorepo:
pnpm installWhat this does: pnpm will install dependencies for all apps and packages in the monorepo. Thanks to pnpm workspaces, shared dependencies are installed only once, saving disk space.
3. Set Up Environment Variables
API Environment Variables
Create a .env file in the apps/api directory:
cp apps/api/sample.env apps/api/.envRequired variables to update:
DATABASE_URL: Your PostgreSQL connection stringCOOKIE_SECRET: Random secret for cookie encryptionJWT_PRIVATE_KEY_BASE64&JWT_PUBLIC_KEY_BASE64: Generate using the command below
Generate JWT keys:
pnpm --filter api generate:jwt-keysThis automatically generates RSA key pairs and updates your .env file.
4. Start PostgreSQL Database
Option A: Using Docker (Recommended)
Start the PostgreSQL container using Docker Compose:
pnpm db:startWhat this does: Starts a PostgreSQL container in the background. The database will be accessible at localhost:5432.
To view database logs:
pnpm db:watchTo stop the database:
pnpm db:stopOption B: Using Local PostgreSQL
If you prefer a local PostgreSQL installation:
- Make sure PostgreSQL is running
- Create a database:
create database starter_nest_db - Update
DATABASE_URLinapps/api/.envwith your credentials
5. Initialize the Database Schema
Run migrations to update the database schema to PostgreSQL:
pnpm db:migrateWhat this does: Uses Drizzle ORM to create all tables, columns, and relationships in your database based on the schema files in packages/db/src/schema/.
Optional - Seed your database:
pnpm db:seedOptional - View your database:
pnpm db:studioThis opens Drizzle Studio in your browser where you can view and edit database records.
6. Start the Development Servers
Start all applications simultaneously:
pnpm devWhat this does: Turborepo will start both the frontend and backend in parallel with hot-reloading enabled. You'll see output from both in your terminal.
7. Access Your Applications
- Backend (API Server): http://localhost:5000
- API Documentation: http://localhost:5000/api (Swagger/OpenAPI docs)
- Database Studio: Run
pnpm run db:studioand visit https://local.drizzle.studio
Configuration
Environment Variables
API Server (apps/api/.env)
Key environment variables you need to configure:
| Variable | Description | Example |
|---|---|---|
PORT | Backend API port | 5000 |
NODE_ENV | Environment mode | development |
DATABASE_URL | PostgreSQL connection string | postgresql://user:pass@localhost:5432/dbname |
COOKIE_SECRET | Secret for cookie encryption | Random string (min 32 chars) |
CORS_ORIGINS | Allowed frontend origins | http://localhost:3000 |
FRONTEND_BASE_URL | Frontend URL for redirects | http://localhost:5000 |
JWT_PRIVATE_KEY_BASE64 | RSA private key (base64) | Auto-generated via script |
JWT_PUBLIC_KEY_BASE64 | RSA public key (base64) | Auto-generated via script |
JWT_ACCESS_TOKEN_TTL_SECONDS | Access token expiry | 900 (15 minutes) |
JWT_REFRESH_TOKEN_TTL_SECONDS | Refresh token expiry | 604800 (7 days) |
Optional variables:
- Storage: Configure
STORAGE_*variables for file uploads (supports local, S3, Supabase) - OAuth: Configure
GOOGLE_*andFACEBOOK_*variables for social login - Email: Configure
EMAIL_SMTP_*variables for sending emails
See apps/api/sample.env for complete list and detailed descriptions.
Database Package (packages/db/.env)
The database package can use its own .env or inherit from apps/api/.env:
DATABASE_URL=postgresql://user:pass@localhost:5432/dbnamePort Configuration
If ports 3000 or 5000 are already in use, you can change them:
- Backend API: Update
PORTinapps/api/.env - Update CORS: If you change frontend port, update
CORS_ORIGINSinapps/api/.env - Update OAuth: If you change backend port, update callback URLs in
apps/api/.env
Running the Applications
Run Everything Together
Start all applications in development mode:
pnpm devWhat runs: apps/api (backend) starts with hot-reloading enabled.
Before apps/api starts, workspace package dev tasks (@nomor/db, @nomor/jobs, @nomor/redis, @nomor/realtime-core, @nomor/mongodb, @nomor/firebase) are orchestrated first so linked dist outputs are ready.
Run Individual Applications
Backend only:
pnpm dev:serverNote: The script name is
dev:serverbut it runs theapps/apifolder (NestJS backend).
Production Build & Start
Build all applications:
pnpm buildThis creates optimized production builds in the dist folders of each app.
Start production servers:
pnpm startFor backend only, you can run:
cd apps/api
pnpm startDatabase Commands
| Command | Description |
|---|---|
pnpm db:start | Start PostgreSQL container (detached) |
pnpm db:watch | Start PostgreSQL container (with logs) |
pnpm db:stop | Stop PostgreSQL container |
pnpm db:down | Stop and remove PostgreSQL container |
pnpm db:push | Push schema changes to database |
pnpm db:studio | Open Drizzle Studio (database GUI) |
pnpm db:generate | Generate SQL migrations from schema |
pnpm db:migrate | Run pending migrations |
pnpm db:seed | Seed database with initial data |
Code Quality Commands
Type checking across all packages:
pnpm check-typesLinting and formatting:
pnpm checkRun tests:
pnpm testAvailable Scripts
| Script | Description |
|---|---|
pnpm dev | Start all applications in development mode |
pnpm build | Build all applications for production |
pnpm start | Start all production builds |
pnpm test | Run tests across all packages |
pnpm dev:server | Start backend API only |
pnpm check-types | Type check all TypeScript code |
pnpm check | Run Biome linting and formatting |
pnpm db:start | Start PostgreSQL container (detached) |
pnpm db:stop | Stop PostgreSQL container |
pnpm db:down | Remove PostgreSQL container |
pnpm db:push | Push schema changes to database |
pnpm db:studio | Open Drizzle Studio database GUI |
pnpm db:generate | Generate SQL migrations |
pnpm db:migrate | Run database migrations |
pnpm db:seed | Seed database with initial data |
Development Workflow
Adding New Dependencies
For the entire monorepo (root level):
pnpm add -w <package-name>For a specific app or package:
pnpm add <package-name> --filter api # Backend API
pnpm add <package-name> --filter @nomor/db # Database package
pnpm add <package-name> --filter @nomor/email # Email package
pnpm add <package-name> --filter @nomor/otp # OTP package
pnpm add <package-name> --filter @nomor/storage # Storage packageFor dev dependencies:
pnpm add -D <package-name> --filter apiWorking with Database Schemas
1. Edit schema files in packages/db/src/schema/
2. Push changes to database:(Only For Development)
pnpm run db:pushUse this command during development to quickly sync schema changes without generating migrations.
3. Review changes in Drizzle Studio:
pnpm run db:studio4. Generate migrations for production:
pnpm db:generate
pnpm db:migrateAdding New NestJS Modules
NestJS follows a modular architecture. To add a new module:
1. Create module structure in apps/api/src/modules/your-module/:
your-module.module.ts- Module definitionyour-module.controller.ts- HTTP endpointsyour-module.service.ts- Business logicyour-module.types.ts- TypeScript typesdto/- Data transfer objects
2. Import the module in apps/api/src/app.module.ts
Git Hooks
This project uses Husky for Git hooks. Before each commit:
- Code is automatically formatted using Biome
- Linting rules are enforced
If a commit fails, fix the issues shown in the error message and try again.
Common Issues & Troubleshooting
Issue: "Cannot find module" errors
Cause: Dependencies might not be installed correctly or pnpm workspace links are broken.
Solution:
rm -rf node_modules
rm -rf apps/*/node_modules packages/*/node_modules
rm pnpm-lock.yaml
pnpm installIssue: Database connection fails
Symptoms: Errors like "ECONNREFUSED" or "password authentication failed"
Solutions:
If using Docker:
- Check if PostgreSQL container is running:
docker ps - Start the database if not running:
pnpm run db:start - Check container logs:
pnpm run db:watch
If using local PostgreSQL:
- Verify PostgreSQL is running:
pg_isready - Check your
DATABASE_URLinapps/api/.env - Verify database exists and credentials are correct
Issue: JWT keys not generated
Symptoms: API fails to start with JWT key errors
Solution:
pnpm --filter api generate:jwt-keysThis generates RSA key pairs and automatically updates apps/api/.env.
Issue: Port already in use
Symptoms: Error like "EADDRINUSE: address already in use ::3000" or "::5000"
Solutions:
-
Find and kill the process using the port:
lsof -ti:3000 | xargs kill -9 # For frontend (port 3000) lsof -ti:5000 | xargs kill -9 # For backend (port 5000) -
Or change the port numbers:
- Backend: Edit
PORTinapps/api/.env
- Backend: Edit
Issue: TypeScript errors after pulling new code
Cause: Type definitions or generated files might be outdated.
Solution:
pnpm install
pnpm run check-typesIssue: Turborepo cache issues
Symptoms: Unexpected build behavior, stale outputs, or changes not reflecting
Solution:
Clear Turborepo cache:
rm -rf .turbo
rm -rf apps/*/.turbo packages/*/.turbo
pnpm run buildIssue: Database schema out of sync
Symptoms: Database errors about missing columns, tables, or relations
Solution:
pnpm run db:pushIf that doesn't work and you're okay losing data:
pnpm run db:down # Remove database container
pnpm run db:start # Start fresh container
pnpm run db:push # Apply schemaIssue: Docker container won't start
Symptoms: Database container fails to start or keeps restarting
Solutions:
-
Check if another PostgreSQL instance is using port 5432:
lsof -i :5432 -
Check Docker logs:
docker compose -f packages/db/docker-compose.yml logs -
Remove old volumes and restart:
pnpm run db:down docker volume prune pnpm run db:start
Issue: CORS errors in browser
Symptoms: Browser console shows CORS policy errors
Solution:
-
Check
CORS_ORIGINSinapps/api/.envincludes your frontend URL:CORS_ORIGINS=http://localhost:3000 -
Restart the backend after changing:
pnpm run dev:server
Issue: Frontend can't reach backend API
Checklist:
- Verify backend is running at http://localhost:5000
- Check browser console for CORS errors
- Verify API base URL in frontend (should be
http://localhost:5000) - Check
CORS_ORIGINSinapps/api/.envmatches frontend URL
Issue: "pnpm not found" error
Solution:
Install pnpm globally:
npm install -g pnpmOr use Corepack (built into Node.js 16.13+):
corepack enable
corepack prepare pnpm@latest --activateIssue: Uploads/file storage not working
Symptoms: File uploads fail or images don't display
Solution:
- Check
UPLOAD_LOCATIONinapps/api/.envexists and is writable - Verify
STORAGE_DRIVERis configured correctly - Check storage configuration in
apps/api/.envfor your chosen provider
Getting Help
If you encounter issues not covered here:
- Check error messages carefully - they often point to the exact problem
- Review the NestJS documentation for backend issues
- Review the TanStack Router documentation for frontend routing issues
- Review the Drizzle ORM documentation for database issues
- Try a fresh installation following the Getting Started steps
Happy coding! 🚀