Quick reference for AI assistants working on the Boilerplate repo (HTTP API + Next.js app).
- Node.js 24+ (see
.nvmrc) - npm workspaces:
apps/api,apps/web,apps/web/sidecar - TypeScript strict, ESM
npm install
npm run build
npm run lint
npm run dev:api # API only (default port 4000)
npm run dev:web # Next.js only
npm run dev:web-sidecar # Build sidecar + run web with sidecarNode and npm are provided by the repo's Nix flake, not a global install. When running terminal commands (e.g. in Cursor's agent), use the wrapper so the correct environment is available:
- Wrapper:
./scripts/nix/with-env <command> [args...] - Examples:
./scripts/nix/with-env npm run build,./scripts/nix/with-env npm run dev:api - Run from repo root. Full explanation and setup-in-other-repos: docs/CURSOR-NIX-WITH-ENV.md.
- Agent runs that use the wrapper may need full permissions so Nix can write to its cache.
apps/api/– Standalone Express HTTP APIapps/web/– Next.js app (fetches runtime config from sidecar whenRUNTIME_CONFIG_URLis set)apps/web/sidecar/– Runtime-config sidecar (serves env-derived config for the Next.js app)packages/ui/– Shared UI: design tokens and mixins inpackages/ui/src/styles/(variables, mixins); component groups (form, layout, modal, navigation, table, feedback, bucket) and hooks (useDeleteModal, useTableFilterState, useAuthValidation) documented in packages/ui/PACKAGES-UI.md. Form controls live underpackages/ui/src/components/form/(see that directory’s PACKAGES-UI-SRC-COMPONENTS-FORM.md).- Validation schemas: Joi validation lives in
apps/api/src/schemas/andapps/management-api/src/schemas/. Controllers and routes import from these directories only; no ad-hoc schema definitions in controllers or routes. Shared request/response types may live inpackages/helpers-requestsorpackages/helpers. See each app’sschemas/APPS-API-SRC-SCHEMAS.mdandschemas/APPS-MANAGEMENT-API-SRC-SCHEMAS.mdfor the file layout.
Secrets (JWT, DB, Valkey, etc.) are auto-generated by make local_env_setup via
scripts/env-setup-secrets.sh. Canonical variable names and defaults live in infra/env/classification/ (see docs/development/ENV-REFERENCE.md); scripts/local-env/setup.sh generates app and infra/config/local/*.env files via scripts/env-classification/boilerplate-env.rb when missing. Optional override files under dev/env-overrides/local/*.env (symlinked from ~/.config/boilerplate/local-env-overrides/ via prepare/link) are applied when present:
info.env (WEB_BRAND_NAME for web UI and for API transactional email when AUTH_MODE uses email flows; MANAGEMENT_WEB_BRAND_NAME for management-web UI), user-agent.env (API_USER_AGENT, MANAGEMENT_API_USER_AGENT; legacy USER_AGENT_* and prior MANAGEMENT_USER_AGENT are mapped in setup.sh), db-management-superuser.env (DB_MANAGEMENT_SUPERUSER_USERNAME, DB_MANAGEMENT_SUPERUSER_PASSWORD — copied into db.env and management-api env by setup.sh so create-super-admin.mjs can bootstrap the management super admin without a TTY when both are set), mailer.env (SMTP — no defaults; devs bring their own; tests use mailpit), auth.env (AUTH*MODE), locale.env (DEFAULT_LOCALE, SUPPORTED_LOCALES with sensible defaults). API_CORS_ORIGINS is api.vars (literal, aligned with http.web WEB_BASE_URL for local dev); MANAGEMENT_API_CORS_ORIGINS is management-api.vars (literal, aligned with http.management-web MANAGEMENT_WEB_BASE_URL for local dev; not home overrides; legacy **CORS_ORIGINS*\*** and prior MANAGEMENT_CORS_ORIGINS are still mapped in setup.sh). **WEB_BASE_URL, WEB_URL** stay as local dev defaults in generated app env (not in those override stubs).
- Prepare:
make local_env_prepare— ensures~/.config/boilerplate/local-env-overrides/exists and creates missing override.envfiles with all anchor keys and merged classification defaults (write-home-override-stubs.rb; never overwrites existing files); edit for non-default values - Link:
make local_env_link— symlinksdev/env-overrides/local/*.envto existing files in the home overrides directory - Clean:
make local_env_clean— removes generated repo env anddev/env-overrides/local/*.envsymlinks; does not remove home files under~/.config/boilerplate/local-env-overrides/. Runlocal_env_linkbeforelocal_env_setupafter a clean if you use home overrides. - Setup:
make local_env_setup— generate env files from classification, auto-generated secrets, and overrides (info, user-agent, db-management-superuser, mailer, auth, locale) when those override files exist (via repo paths underdev/env-overrides/local/after link). - One-shot:
make local_setup—local_env_setup+local_infra_up
See docs/development/LOCAL-ENV-OVERRIDES.md.
See .llm/LLM.md for full guidelines. Use the llm-history skill when updating history or starting feature work. The llm-history-tracking rule (always applied) requires updating .llm/history/active/[feature]/ on any file-modifying response.
- Follow
.cursor/skills/llm-history/SKILL.mdfor history timing, format, and 10-session limit.
When implementing features or executing plans that touch api or management-api, include integration tests (see api-testing). When they touch web or management-web, include E2E tests (see e2e-page-tests). If an API change affects UI in web or management-web, add or update the relevant E2E specs as well.
- API integration tests: One setup file (apps/api/src/test/setup.ts) provides smart-default env for all tests. Tests that need different env (e.g. signup/mailer) override only those vars at the top of the file and load app/config via dynamic import in
beforeAllso overrides apply. Full coverage:npm run testfrom repo root. The first step is a requirements check: Postgres and Valkey must be reachable at the test ports (defaults 5532, 6479). If not, the script exits with instructions (e.g.make test_deps). In Nix/agent environments use./scripts/nix/with-env npm run test. - Test requirements (Makefile): Test-related commands live in
makefiles/local/Makefile.local.test.mk. From repo root:make test_depsstarts Postgres on 5532 and Valkey on 6479, creates two test databases:boilerplate_app_test(main app;infra/k8s/base/stack/postgres-init/0003_app_schema.sql) andboilerplate_management_test(management-api;infra/k8s/base/stack/postgres-init/0005_management_schema.sql.frag), and creates app/management read and read_write roles (boilerplate_app_read/boilerplate_app_read_write,boilerplate_management_read/boilerplate_management_read_write).make help_testprints instructions. - Test databases: Tests use dedicated DBs on the same Postgres instance. Main:
boilerplate_app_test(api and management-api main-user CRUD). Management:boilerplate_management_test(management identities, permissions, events). Default test ports are 5532 (Postgres) and 6479 (Valkey). Each test run starts with a clean slate: globalSetup truncates main and management tables once before any test file runs (api:apps/api/src/test/global-setup.mjs; management-api:apps/management-api/src/test/global-setup.mjs). - Database naming (dev/Docker/K8s): Two databases in one Postgres instance, aligned with Podverse: app DB
boilerplate_app, management DBboilerplate_management. Classification definesDB_APP_NAMEandDB_MANAGEMENT_NAME(viadbenv group keys); cluster superuser isDB_USER(defaultuser) andDB_PASSWORDindb.env(withDB_HOST/DB_PORTfor clients). The official Postgres Docker image still readsPOSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB; local Compose maps them fromDB_USER,DB_PASSWORD, andDB_APP_NAME. Apps useDB_APP_NAME(synced bylocal_env_setup). Management-api uses the sameDB_HOST/DB_PORTplusDB_MANAGEMENT_NAMEandDB_MANAGEMENT_READ_WRITE_*(inherited from classification env groupdb; no separateMANAGEMENT_DB_*vars). Role names:boilerplate_app_read/boilerplate_app_read_write,boilerplate_management_read/boilerplate_management_read_write; keysDB_APP_READ_*andDB_MANAGEMENT_READ_*. - Mailer: No local mailer service is required. Tests that cover verification flows use a Vitest mock of the
mailer module to capture tokens and call verification endpoints; see
apps/api/src/test/*.test.ts.
- Credentials: The system stores only
passwordHash(no plaintext password). Never returnpasswordHashor any credential field in API responses. UseuserToJson(or a similar safe serializer) for user data; never serializereq.useroruser.credentialsdirectly. - Verification tokens: Token hashes and raw tokens are never returned or listed by the API; they are only consumed server-side (verify-email, reset-password, confirm-email-change). Do not add endpoints that expose verification token entities.
- User in responses: For the authenticated user only (login, me, signup, refresh, confirm-email-change, update-profile), use
PublicUser(id, shortId, email, username, displayName) viauserToJson. Email is PII and must not be returned for other users. For any response that describes another user (e.g. bucket admins list/detail), usePublicUserSummary(id, shortId, username, displayName) viauserToPublicSummaryfromapps/api/src/lib/userToJson.ts. - API messages and locale: API structural messages (e.g. "Invalid or expired link", "Authentication required") are American English. Password validation text and email content (verification, password reset, email-change) are localized: resolved from
Accept-Languagewhen the value is supported (en-US, es), else app default fromDEFAULT_LOCALEenv. Do not localize themessagefield for structural errors; user-facing content such as email subject/body and password validation messages is localized.
- Upgrade policy: Apply patch and minor updates routinely (e.g.
npm outdatedthen bump versions andnpm install). Major upgrades (e.g. @faker-js/faker, dotenv, eslint-plugin-perfectionist, express-rate-limit, lint-staged, nanoid, @types/node) are done separately with migration and testing. After any dependency change, runnpm run buildandnpm run lint; commit the updatedpackage-lock.jsonso CI and Docker use the same versions. - Linux-canonical lockfile: CI runs on Linux and needs Linux optional deps (e.g.
@parcel/watcher,@next/swc-linux-x64-gnu, next-intl's@swc/core) in the lockfile. Generate or refresh the lockfile under Linux x64 so it stays correct for CI: run./scripts/development/update-lockfile-linux.sh(requires Docker). The bump-version script runs this automatically before committing. When you add or update dependencies from a Mac, run that script and commit the updatedpackage-lock.json.
- Strict equality (
===/!==only) - Avoid type assertions (
as); prefer types and narrowing - Semicolons in JS/TS
Imports must follow a consistent hierarchy and be alphabetized within groups: (1) Node built-ins, (2) external packages, (3) workspace packages (@boilerplate/*), (4) relative imports (parent, sibling, index), (5) style imports (SCSS/CSS) last. Type-only imports use a separate line (import type { X } from '...') and are ordered with their group. Enforced by ESLint rule perfectionist/sort-imports; run npm run lint:fix to auto-fix.
- .cursor/skills/ – Project skills (one SKILL.md per directory). Use when the task matches the skill’s “when to use” so the agent follows the right patterns.
- .cursor/rules/ – Glob-triggered or always-applied rules (e.g. eqeqeq, avoid-type-assertions, llm-history-tracking, path-casing). Rules apply automatically when editing matching files.
All configuration is project-scoped (no home-directory skills or rules).
| Task or area | Skill(s) to use |
|---|---|
| History / plans | llm-history when updating history or starting feature work. plan-files-convention when creating, saving, or completing multi-step plans (e.g. COPY-PASTA execution). single-readme when adding index/overview docs (repo has only one README at root). Plans live in .llm/plans/active/; split if over 300 lines (see plan-creation rule). |
| API | api for Express API patterns. api-testing when adding or changing API routes, auth, or env-dependent behavior (integration tests). swagger-openapi when changing API surface. api-no-pii-credentials rule applies to responses. |
| Web / management-web | web for Next.js app patterns. e2e-page-tests when layout or behavior changes in web/management-web. response-ending-make-verify (and end-with-targeted-make-report-verify rule) for E2E verification commands. Use make E2E targets only (e2e-run-with-make-only). |
| i18n | i18n when adding/editing translation keys, locales, or generating translations. |
| Forms / UI | use-form-component, button-loading-async, password-strength-on-set-update. avoid-type-assertions, eqeqeq-strict-equality rules apply. |
| DB / ORM | database-schema-naming, typeorm-orderby-property-names, generate-data-sync when schema or entities change. nested-resource-prefix-naming for nested resources. |
| K8s / Argo CD | Remote cluster runbook: docs/development/REMOTE-K8S-GITOPS.md. argocd-gitops-push when changing files under infra/k8s/ or sync targets for k8s (add push-to-Git reminder in response so Argo CD can sync). |
| Env / classification | classification-env when adding or changing infra/env/classification/, env generators, or K8s env render. |
| Imports / paths | path-casing-imports when adding or changing relative imports (rule also applies on .ts/.tsx). type-imports-separate-line rule for type-only imports. |
For file-modifying work: update .llm/history/active/[feature]/ per llm-history and the llm-history-tracking rule. For large or multi-step plans: save under .llm/plans/active/ and use plan-files-convention; execute via COPY-PASTA prompts one plan at a time.