Shop It Docs

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:

  1. pnpm (v10 or higher)

    • Install globally: npm install -g pnpm
    • Verify installation: pnpm --version
  2. Docker & Docker Compose (for PostgreSQL database)

    • Download Docker Desktop from docker.com
    • Verify installation: docker --version and docker compose version
    • Make sure Docker is running
  3. 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.json

Understanding 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 nomor

2. Install Dependencies

Install all dependencies for the entire monorepo:

pnpm install

What 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/.env

Required variables to update:

  • DATABASE_URL: Your PostgreSQL connection string
  • COOKIE_SECRET: Random secret for cookie encryption
  • JWT_PRIVATE_KEY_BASE64 & JWT_PUBLIC_KEY_BASE64: Generate using the command below

Generate JWT keys:

pnpm --filter api generate:jwt-keys

This automatically generates RSA key pairs and updates your .env file.

4. Start PostgreSQL Database

Start the PostgreSQL container using Docker Compose:

pnpm db:start

What this does: Starts a PostgreSQL container in the background. The database will be accessible at localhost:5432.

To view database logs:

pnpm db:watch

To stop the database:

pnpm db:stop

Option B: Using Local PostgreSQL

If you prefer a local PostgreSQL installation:

  1. Make sure PostgreSQL is running
  2. Create a database: create database starter_nest_db
  3. Update DATABASE_URL in apps/api/.env with your credentials

5. Initialize the Database Schema

Run migrations to update the database schema to PostgreSQL:

pnpm db:migrate

What 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:seed

Optional - View your database:

pnpm db:studio

This opens Drizzle Studio in your browser where you can view and edit database records.

6. Start the Development Servers

Start all applications simultaneously:

pnpm dev

What 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

Configuration

Environment Variables

API Server (apps/api/.env)

Key environment variables you need to configure:

VariableDescriptionExample
PORTBackend API port5000
NODE_ENVEnvironment modedevelopment
DATABASE_URLPostgreSQL connection stringpostgresql://user:pass@localhost:5432/dbname
COOKIE_SECRETSecret for cookie encryptionRandom string (min 32 chars)
CORS_ORIGINSAllowed frontend originshttp://localhost:3000
FRONTEND_BASE_URLFrontend URL for redirectshttp://localhost:5000
JWT_PRIVATE_KEY_BASE64RSA private key (base64)Auto-generated via script
JWT_PUBLIC_KEY_BASE64RSA public key (base64)Auto-generated via script
JWT_ACCESS_TOKEN_TTL_SECONDSAccess token expiry900 (15 minutes)
JWT_REFRESH_TOKEN_TTL_SECONDSRefresh token expiry604800 (7 days)

Optional variables:

  • Storage: Configure STORAGE_* variables for file uploads (supports local, S3, Supabase)
  • OAuth: Configure GOOGLE_* and FACEBOOK_* 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/dbname

Port Configuration

If ports 3000 or 5000 are already in use, you can change them:

  1. Backend API: Update PORT in apps/api/.env
  2. Update CORS: If you change frontend port, update CORS_ORIGINS in apps/api/.env
  3. 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 dev

What 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:server

Note: The script name is dev:server but it runs the apps/api folder (NestJS backend).

Production Build & Start

Build all applications:

pnpm build

This creates optimized production builds in the dist folders of each app.

Start production servers:

pnpm start

For backend only, you can run:

cd apps/api
pnpm start

Database Commands

CommandDescription
pnpm db:startStart PostgreSQL container (detached)
pnpm db:watchStart PostgreSQL container (with logs)
pnpm db:stopStop PostgreSQL container
pnpm db:downStop and remove PostgreSQL container
pnpm db:pushPush schema changes to database
pnpm db:studioOpen Drizzle Studio (database GUI)
pnpm db:generateGenerate SQL migrations from schema
pnpm db:migrateRun pending migrations
pnpm db:seedSeed database with initial data

Code Quality Commands

Type checking across all packages:

pnpm check-types

Linting and formatting:

pnpm check

Run tests:

pnpm test

Available Scripts

ScriptDescription
pnpm devStart all applications in development mode
pnpm buildBuild all applications for production
pnpm startStart all production builds
pnpm testRun tests across all packages
pnpm dev:serverStart backend API only
pnpm check-typesType check all TypeScript code
pnpm checkRun Biome linting and formatting
pnpm db:startStart PostgreSQL container (detached)
pnpm db:stopStop PostgreSQL container
pnpm db:downRemove PostgreSQL container
pnpm db:pushPush schema changes to database
pnpm db:studioOpen Drizzle Studio database GUI
pnpm db:generateGenerate SQL migrations
pnpm db:migrateRun database migrations
pnpm db:seedSeed 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 package

For dev dependencies:

pnpm add -D <package-name> --filter api

Working with Database Schemas

1. Edit schema files in packages/db/src/schema/

2. Push changes to database:(Only For Development)

pnpm run db:push

Use this command during development to quickly sync schema changes without generating migrations.

3. Review changes in Drizzle Studio:

pnpm run db:studio

4. Generate migrations for production:

pnpm db:generate
pnpm db:migrate

Adding 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 definition
  • your-module.controller.ts - HTTP endpoints
  • your-module.service.ts - Business logic
  • your-module.types.ts - TypeScript types
  • dto/ - 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 install

Issue: Database connection fails

Symptoms: Errors like "ECONNREFUSED" or "password authentication failed"

Solutions:

If using Docker:

  1. Check if PostgreSQL container is running:
    docker ps
  2. Start the database if not running:
    pnpm run db:start
  3. Check container logs:
    pnpm run db:watch

If using local PostgreSQL:

  1. Verify PostgreSQL is running:
    pg_isready
  2. Check your DATABASE_URL in apps/api/.env
  3. 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-keys

This 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:

  1. 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)
  2. Or change the port numbers:

    • Backend: Edit PORT in apps/api/.env

Issue: TypeScript errors after pulling new code

Cause: Type definitions or generated files might be outdated.

Solution:

pnpm install
pnpm run check-types

Issue: 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 build

Issue: Database schema out of sync

Symptoms: Database errors about missing columns, tables, or relations

Solution:

pnpm run db:push

If 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 schema

Issue: Docker container won't start

Symptoms: Database container fails to start or keeps restarting

Solutions:

  1. Check if another PostgreSQL instance is using port 5432:

    lsof -i :5432
  2. Check Docker logs:

    docker compose -f packages/db/docker-compose.yml logs
  3. 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:

  1. Check CORS_ORIGINS in apps/api/.env includes your frontend URL:

    CORS_ORIGINS=http://localhost:3000
  2. Restart the backend after changing:

    pnpm run dev:server

Issue: Frontend can't reach backend API

Checklist:

  1. Verify backend is running at http://localhost:5000
  2. Check browser console for CORS errors
  3. Verify API base URL in frontend (should be http://localhost:5000)
  4. Check CORS_ORIGINS in apps/api/.env matches frontend URL

Issue: "pnpm not found" error

Solution:

Install pnpm globally:

npm install -g pnpm

Or use Corepack (built into Node.js 16.13+):

corepack enable
corepack prepare pnpm@latest --activate

Issue: Uploads/file storage not working

Symptoms: File uploads fail or images don't display

Solution:

  1. Check UPLOAD_LOCATION in apps/api/.env exists and is writable
  2. Verify STORAGE_DRIVER is configured correctly
  3. Check storage configuration in apps/api/.env for your chosen provider

Getting Help

If you encounter issues not covered here:

  1. Check error messages carefully - they often point to the exact problem
  2. Review the NestJS documentation for backend issues
  3. Review the TanStack Router documentation for frontend routing issues
  4. Review the Drizzle ORM documentation for database issues
  5. Try a fresh installation following the Getting Started steps

Happy coding! 🚀