Skip to main content

Local Development

Last reviewed: 2026-05-13
Maintained by: Engineering

This is the practical guide for day-to-day local work in the repo.

Canonical docs entrypoint: developer-index.md

First-Time Setup

  1. Activate pnpm if it is not already available:
corepack prepare pnpm@9.15.9 --activate
  1. Install dependencies:
pnpm install
  1. Copy the env template:
cp .env.example .env

For app-local env files:

cp apps/manager-desk/.env.example apps/manager-desk/.env.local
cp apps/back-office/.env.example apps/back-office/.env.local
cp apps/zgrid-mobile/.env.example apps/zgrid-mobile/.env

Local email defaults:

  • keep EMAIL_PROVIDER=dev_log in local development so no real emails are sent
  • EMAIL_FROM, EMAIL_REPLY_TO, and EMAIL_SUPPORT_ADDRESS are local placeholders and must be replaced with real values outside local dev
  • EMAIL_ACTION_BASE_URL should point at your local Manager Desk sign-in / action flow origin
  • MANAGER_DESK_URL should point at your local Manager Desk app origin
  • RESEND_API_KEY is not required locally and should only be added in staging/production when EMAIL_PROVIDER=resend

Local Sentry defaults:

  • keep SENTRY_ENABLED=false in the root .env
  • keep NEXT_PUBLIC_SENTRY_ENABLED=false in apps/manager-desk/.env.local
  • keep NEXT_PUBLIC_SENTRY_ENABLED=false in apps/back-office/.env.local
  • keep EXPO_PUBLIC_SENTRY_ENABLED=false in apps/zgrid-mobile/.env
  • if you want to verify Sentry locally, set the matching DSN and turn on the matching enabled flag
  1. Start local Postgres:
docker compose up -d postgres
  1. Apply migrations:
pnpm run db:migrate
  1. Seed demo data:
pnpm run seed:demo

Important:

  • the current schema includes issue_kind, strict emergency categories, and service_requests tables
  • if you pull latest main on an existing local DB, run pnpm run db:migrate before using Issues/Service Requests screens

Running the Apps

Through Root Scripts

This is the standard team workflow:

pnpm run dev:api
pnpm run dev:manager-desk

Directly with pnpm --filter

Use this when you want to work in only one workspace:

pnpm --filter @zgrid/api dev
pnpm --filter @zgrid/manager-desk dev

Rule:

  • use root scripts for the standard team workflow
  • use --filter for focused work in a single app or package

Local URLs

  • API: http://localhost:3000
  • Manager Desk: http://localhost:3001
  • Zgrid App mobile: Expo dev server via pnpm run dev:zgrid-mobile

Zgrid App

The mobile app lives in apps/zgrid-mobile and uses Expo Router.

Common commands:

pnpm run dev:zgrid-mobile
pnpm run ios:zgrid-mobile
pnpm run android:zgrid-mobile
pnpm run check:zgrid-mobile
pnpm run lint:zgrid-mobile
pnpm run cleanup:privacy-metadata

The mobile app reads EXPO_PUBLIC_API_URL. If it is not set, it defaults to:

  • iOS / simulator-friendly: http://127.0.0.1:3000/api
  • Android emulator-friendly: http://10.0.2.2:3000/api

Examples:

EXPO_PUBLIC_API_URL=http://127.0.0.1:3000/api pnpm run dev:zgrid-mobile
EXPO_PUBLIC_API_URL=http://10.0.2.2:3000/api pnpm run android:zgrid-mobile

If you want mobile Sentry on a non-local build, also provide:

EXPO_PUBLIC_SENTRY_ENABLED=true
EXPO_PUBLIC_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
EXPO_PUBLIC_SENTRY_ENVIRONMENT=staging
EXPO_PUBLIC_SENTRY_RELEASE=mobile@2026.05.13
EXPO_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.05
  • Postgres: localhost:5433

Migrations

Create a New Migration

pnpm run db:migrate:create add-some-change

Apply Pending Migrations

pnpm run db:migrate

Roll Back the Latest Migration

pnpm run db:migrate:down

Rules:

  • do not edit old migrations that have already been applied
  • every new schema change should go into a new migration
  • database/init.sql is not the source of truth for ongoing schema changes

Checks Before Commit

pnpm run lint
pnpm test
pnpm run check:api
pnpm run check:manager-desk

If you changed the database:

pnpm run db:migrate
pnpm run db:schema:check
pnpm run seed:demo

db:schema:check prerequisites:

  • PostgreSQL client tools installed with pg_dump available in your PATH
  • Postgres running and reachable via current DATABASE_URL (default local setup uses localhost:5433)

If you intentionally changed the schema and added a new migration:

pnpm run db:migrate
pnpm run db:schema:sync

Common Problems

SymptomMost common causeCommand / fix
API query failsdatabase is not migratedpnpm run db:migrate
Seed failsschema and seed are out of syncpnpm run db:migrate then pnpm run seed:demo
Migration drift check failsschema snapshot is stalepnpm run db:migrate then pnpm run db:schema:sync
Lint failsESLint rule or unused importpnpm run lint and fix reported files
Tests faila utility or env contract changedpnpm test and fix the failing workspace
API typecheck failsbackend TypeScript errorpnpm run check:api
Manager Desk typecheck failsNext.js or route typing errorpnpm run check:manager-desk
Web shows stale build statestale .next artifactsremove apps/manager-desk/.next and restart dev
Next type info looks stalestale TS build inforemove apps/manager-desk/tsconfig.tsbuildinfo
Login breaks after auth changesstale cookie or seed statelog out, log back in, and if needed run pnpm run seed:demo

When to Use Root Scripts vs Filters

Use root scripts when:

  • you want the standard team command
  • you are running check, lint, migrate, or seed commands
  • you are documenting commands in README or onboarding docs

Use pnpm --filter when:

  • you are adding a dependency to one workspace only
  • you want dev or build for a single workspace
  • you are doing targeted debugging

Source of Truth for Local Setup

When you change the local workflow, keep these aligned: