Skip to content

Development Setup

Nametag supports two development environments. Choose the one that works best for you.

  • Dev Container (Option A): easiest for new contributors, one-click setup.
  • Local Development (Option B): best for daily development, faster iteration.

Recommended for new contributors. Gets you running quickly with zero local configuration, and works on any OS, including GitHub Codespaces.

Prerequisites:

Setup:

  1. Fork and clone the repository:

    Terminal window
    git clone https://github.com/YOUR_USERNAME/nametag.git
    cd nametag
  2. Open in VS Code:

    Terminal window
    code .
  3. When prompted, click Reopen in Container (or press F1, then choose “Dev Containers: Reopen in Container”).

  4. Wait for the container to build. Setup happens automatically:

    • Installs Node.js dependencies
    • Generates the Prisma client
    • Runs database migrations
    • Seeds the database with demo data
  5. Start the dev server:

    Terminal window
    npm run dev
  6. Access the app at http://localhost:3000.

Demo credentials:

  • Email: demo@nametag.one
  • Password: password123

Benefits:

  • Consistent environment across all contributors
  • No local Node.js installation required
  • Works identically on Windows, macOS, and Linux
  • Can develop entirely in the browser via GitHub Codespaces
  • All tools and extensions pre-configured

Best for daily work: faster iteration and a more familiar debugging experience. Requires Node.js installed locally, but only runs database services in Docker.

Prerequisites:

  • Node.js 24 (22.23.4 or newer also works)
  • Docker and Docker Compose
  • Git

Setup:

  1. Fork and clone the repository:

    Terminal window
    git clone https://github.com/YOUR_USERNAME/nametag.git
    cd nametag
  2. Install dependencies:

    Terminal window
    npm install
  3. Copy the environment file:

    Terminal window
    cp .env.example .env

    Edit .env and set at minimum:

    • NEXTAUTH_SECRET, generate with openssl rand -base64 32
    • CRON_SECRET, generate with openssl rand -base64 16

    The default values for DATABASE_URL and REDIS_URL already work with the Docker services below.

  4. Start database services only:

    Terminal window
    docker-compose -f docker-compose.services.yml up -d

    This starts PostgreSQL on port 5432 and Redis on port 6379.

  5. Set up the database:

    Terminal window
    ./scripts/setup-db.sh

    This runs migrations, generates the Prisma client, and seeds the database with demo data.

  6. Start the dev server:

    Terminal window
    npm run dev
  7. Access the app at http://localhost:3000.

Demo credentials:

  • Email: demo@nametag.one
  • Password: password123

Benefits:

  • Native performance, no Docker overhead for Node.js
  • Instant hot reload
  • Familiar debugging with native Node.js tooling
  • Full control over the development environment
  • Faster test execution

Common commands:

Terminal window
# Stop database services
docker-compose -f docker-compose.services.yml down
# Start services
docker-compose -f docker-compose.services.yml up -d
# View database logs
docker-compose -f docker-compose.services.yml logs -f db
# Reset database
npx prisma migrate reset
ScriptWhat it does
npm run devStart the development server
npm run buildProduction build
npm run startStart the production server
npm run lintRun ESLint
npm run typecheckRun the TypeScript compiler in check-only mode
npm run testRun unit tests in watch mode
npm run test:runRun unit tests once
npm run test:dbRun database tests against a real PostgreSQL (needs TEST_DATABASE_URL)
npm run test:coverageRun unit tests with coverage
npm run test:e2eRun Playwright end-to-end tests
npm run verifyLint, typecheck, unit tests, and build
npm run verify:allSame as verify, plus E2E tests (matches CI)
npm run seed:devSeed the database with demo data

Dev Container issues:

  • Make sure Docker Desktop is running.
  • Try “Dev Containers: Rebuild Container” from the command palette (F1).
  • Check that ports 3000, 5432, and 6379 aren’t already in use.

Local Development issues:

  • Verify your Node.js version: node --version (should be 24, or 22.23.4 or newer).
  • Make sure the Docker services are running: docker-compose -f docker-compose.services.yml ps.
  • Check the database connection: npx prisma db execute --stdin <<< "SELECT 1".
  • Clear and reinstall dependencies: rm -rf node_modules && npm install.

Database connection errors:

  • Verify the services are healthy: docker ps.
  • Check that .env has the correct DATABASE_URL and REDIS_URL.
  • Try restarting the services: docker-compose -f docker-compose.services.yml restart.

Port conflicts:

  • Check what’s using a port: lsof -i :3000 (or :5432, :6379).
  • Change the port in .env, or stop the conflicting service.
Terminal window
# 1. Edit prisma/schema.prisma
# 2. Create and apply a migration
npx prisma migrate dev --name describe_your_change
# 3. Regenerate the Prisma client (usually automatic, but sometimes needed)
npx prisma generate

Useful database commands:

Terminal window
# Open Prisma Studio (database GUI)
npx prisma studio
# Reset the database (deletes all data)
npx prisma migrate reset
# Seed the database
npx prisma db seed
Terminal window
npm run test # unit tests, watch mode
npm run test:run # unit tests, single run
npm run test:coverage # unit tests with coverage
npm run test:e2e # end-to-end tests
npm run test:e2e:ui # end-to-end tests with UI

Some behavior (database triggers, locking) can only be verified against a real PostgreSQL, so those tests live in tests/db/ as *.db.test.ts files. They are excluded from npm run test:run and run separately:

Terminal window
TEST_DATABASE_URL=postgresql://user:password@localhost:5432/nametag_test npm run test:db

Before the tests start, the suite runs prisma migrate reset --force against TEST_DATABASE_URL, so the schema is built from migrations only. This wipes the database, so the suite refuses to run unless the database name ends in _test or -test and the host is localhost, 127.0.0.1 or ::1. Never point it at a database you care about.

Every write to a synced table (people, groups, relationships, important dates, relationship types, journal entries, custom field templates and the contact child tables) takes a per-user lock for delta sync. Two concurrent transactions for the same user can therefore deadlock, and Postgres aborts one of them. Single queries on prisma are retried automatically, but a whole transaction must be retried by its caller:

import { withDeadlockRetry } from '@/lib/db/deadlock';
await withDeadlockRetry(() => prisma.$transaction(async (tx) => {
// ...
}));

Build a batch transaction’s queries inside the callback, so a retry gets fresh ones. A transaction that writes several records should also call lockUserRow(tx, userId) from lib/db/user-lock.ts first, so it waits its turn instead of deadlocking. tests/unit/db/transaction-retry-guard.test.ts fails on an unwrapped $transaction unless its file is allowlisted with a reason.

Terminal window
npm run lint # linter
npm run typecheck # type check
npm run build # production build, catches many issues
npm run verify # lint, typecheck, unit tests, and build
npm run verify:all # same as verify, plus E2E tests, matches CI

Before submitting a PR, run npm run verify (or npm run verify:all for larger changes) to catch issues early and save CI time.

Dev Container:

  • View terminal output directly in VS Code.
  • Use VS Code’s built-in debugger.
  • Inspect data with Prisma Studio: npx prisma studio.
  • Check the browser console for frontend issues.

Local Development:

  • Check database logs: docker-compose -f docker-compose.services.yml logs -f db.
  • Use your IDE’s Node.js debugger.
  • Inspect data with Prisma Studio: npx prisma studio.
  • Add console logs or breakpoints as needed.
  • Check the browser console for frontend issues.

See Code Guidelines for coding standards, and Architecture for an overview of how the codebase fits together.