A production-grade NestJS starter that ships with a developer cockpit you'll actually want to use.
Pure-black dark theme. Electric-lime accent. Live ops health, logs, feature toggles, and service probes on one screen β coverage and tests on their own Hub pages. React 19 SPA built on shadcn/ui (Radix) + Tailwind CSS 4 + lucide-react + sonner. No cloud dependencies. No bloat.
Quick Start Β· Hub Β· Showcase Β· Features Β· Architecture Β· Testing
The full screenshot index β every Hub page β
/hub,/hub/features,/hub/email-builder,/admin/*,/errors,/openapiβ at desktop + mobile is indocs/showcase/.
Most NestJS starters give you a Hello World and call it a day. This one ships you a server you can actually run on day one plus a full-blown operator cockpit at /hub that knows what's running, what's failing, and what's available to switch on.
- Real cockpit, not a JSON dump β the
/hubdashboard pulls live health, job queue pressure, log tail, feature matrix, and service probes into one view; coverage and test gates live on/hub/coverageand/hub/tests. - Toggle features from the UI β no
.envediting dance: flip a feature on, the server restarts, the page reloads. 22 toggleable features ship with sensible defaults. - Template-owned core β
src/core/is the synced template surface,src/modules/is yours. Pull upstream improvements without losing your domain code. - Battle-tested defaults β Postgres RLS multi-tenancy, ETag concurrency, idempotency keys, RFC 7807 errors, AES-256-GCM field encryption, OpenAPI 3.1, OWASP-aligned headers.
- No proprietary tooling β pino-pretty for terminal logs, JSON-Viewer for any JSON endpoint, Scalar for the API reference, Prisma Studio for the DB. Everything self-hosted.
Prerequisites: Bun 1.x Β· Docker Desktop Β· macOS / Linux
# 1. Install dependencies
bun install
# 2. (optional, if you forked the template) Rename to your project name
bun run rename my-app
# 3. Generate .env, start Postgres + Redis, migrate, and seed demo data
bun run setup
# Demo credentials are printed only by `bun run seed` (terminal) β not in the Hub UI.
# Flags: --skip-bootstrap (env only), --skip-docker, --no-seed
# Re-run DB steps on an existing .env: bun run setup --bootstrap
# 4. (optional) Verify everything is wired correctly
bun run onboard # quick sanity check (Bun / .env / Postgres / Prisma)
bun run doctor # deep health check (containers, services, secrets, disk)
# 5. Start the dev server β opens the Hub (Postgres already running from setup)
bun run devRe-run
bun run prepare:schema(thenprisma:migrateif migrations changed) after toggling feature flags in/hub/featuresβ the concat step picks up whichprisma/features/*.prismafiles are active.
bun run rename <name>patchespackage.json,README.md,portless.yml, anddocker-compose.ymlin one shot. Idempotent β safe to run repeatedly.
bun run resetwipes the DB, replays every migration, and re-seeds β one command for "give me a clean slate". Refuses on production and non-local DATABASE_URL hosts as defense-in-depth.Recover from a stale Postgres volume? Wipe the volume first; the existing
.envalready carries a password compatible with the freshly-initialised volume:docker compose down -v docker compose up -d postgres bun run prepare:schema bun run prisma:migrateOnly delete
.envif you specifically want to regenerate the random secrets (e.g. you suspect leakage). The full re-setup dance (rm .env && bun run setup) is strictly more steps than this lighter path.Two workspaces with the same project name?
bun run setupwritesCOMPOSE_PROJECT_NAME=<name>-<6-hex-of-path>so twomy-appcheckouts in different directories get separate Postgres volumes (my-app-a1b2c3_postgres_datavsmy-app-d4e5f6_postgres_data). The volume-collision check honours the path-hash, so duplicate names across cache dirs no longer abort the wizard. Existing.envfiles keep whateverCOMPOSE_PROJECT_NAMEthey already carry β only fresh inits get the hashed namespace.
The Hub opens automatically at the URL the dev runner prints β https://api.<project>.localhost/ if you use portless, otherwise the bare http://localhost:<port>/. The runner picks :3000 when free and falls back to a dynamic port (e.g. :4266) when it isn't, so always trust the printed URL over a hard-coded :3000.
What
bun run devdoes for you: starts Postgres viadocker compose up -d postgresif the container isn't running, spawns Prisma Studio on:5555, watches.envfor changes (so feature toggles take effect without a manual restart), and opens the Hub in your browser athttp://localhost:3000/. SetNO_OPEN=1to skip the auto-open,SKIP_DB_BOOT=1to skip the Postgres boot.Need a public URL?
bun run dev --tunnelexposeslocalhost:<port>to the internet via Cloudflare Tunnel β handy for testing webhook receivers (Stripe, GitHub, Slack, OAuth callbacks) without deploying. Requirescloudflared(brew install cloudflared). Seedocs/dev-tunnel.md.
bun run onboardis the lightweight first-run check (Bun version,.envpresence, Postgres TCP reachability, Prisma client).bun run doctorgoes deeper: container statuses, env-var entropy, disk space, configurable services. JSON output viabun run doctor --jsonfor CI consumption.
A black + lime operator console powered by a React 19 SPA (src/core/dx/clients/) built on shadcn/ui (Radix) + Tailwind CSS 4 + lucide-react + sonner + TanStack Query. Hub pages (/hub, /hub/features, /hub/diagnostics, /hub/logs, /hub/jobs, β¦), /admin/* CRUD surfaces, /errors, and /openapi are all rendered by the same SPA shell; the Nest controllers return JSON sidecars + the SPA shell, the SPA decides which page to mount. The sidebar groups pages into Γbersicht, Laufzeit, API & Docs, and Admin.
Full screenshot gallery:
docs/showcase/.
/hub/* and /admin/* require a Better-Auth session (same as the API). Open /
and sign in with an account that has the right CASL permissions. After a local
bun run seed, demo logins are listed only in the seed command output (never in
the Hub UI). Hub (/hub/*) is limited to system-wide operators; tenant admins use
/admin/*.
Details: docs/hub/login.md.
Live overview of the running server: health verdict, service probes, operator status groups (DB / async / runtime / external), ops metrics (pending jobs, dead letters, slow queries, error logs), optional activity charts, tunnel card, log preview, feature matrix, quick navigation.
23 feature flags grouped by category (PRD Β§ Success Criteria pin β
verified by verify-spec.sh SC.FUSION.04). Each card shows description, exposed surfaces, and the matching FEATURE_* env-var. Flip the switch β .env is patched β server respawns β page reloads. No manual restarts.
Single-file brand config (src/modules/branding/brand.json, with template default at src/core/branding/brand.default.json) drives every Hub CSS variable, the OpenAPI title, the email layouts (Barebone + EJS legacy), the Better-Auth issuer/RP-name, and the EmailService default From:. Edit via the UI, save, the dev runner restarts the API, every surface picks up the new look. Schema-validated hex colors + email + URL β bad input fails at load time, not at next mail send. See docs/customization-guide.md#branding.
Reads coverage/test-summary.json (populated by bun run test:summary). Failed suites floated to the top with embedded failure snippets.
Reads reports/coverage/coverage-summary.json (populated by bun run test:coverage). Per-tier gate badges (core β₯ 80% lines / modules β₯ 75% lines), per-file table sorted worst-first.
In-memory ring buffer of the last 500 Pino records. Auto-polls every 2 seconds. Level chips (info / warn / error / fatal) with subtle color tints.
Heap usage bar (turns warn/bad above 70%/90%, clamped to 100% β Bun's JSC heap accounting can briefly show used > committed and that's not a leak), versions (Node, Bun, platform), active features matrix, app metadata.
In-memory ring buffer (200 entries) of recent HTTP request traces with method, status, duration, and request-id. Bound-height container with sticky header, polled every 2 s β newest entries flash in at the top. Click a row to inline-expand the DB queries that ran during that request (cross-references /hub/queries via requestId).
Every Prisma query event lands in a 500-entry ring buffer. The page surfaces:
- the slowest 10 queries colour-coded against thresholds (warn > 50 ms, critical > 200 ms),
- the most frequent SQL templates β a cheap N+1 detector (literals collapsed via
normaliseSql(), count β₯ 10 highlighted), - a tail of the most recent 100 queries,
- 4-tile summary (total Β· slow Β· critical Β· slowest).
If a slice you just shipped lands in the slowest section, that's your next thing to fix.
Two-tab view of the JobQueueService (BullMQ when REDIS_URL is set, in-memory fallback otherwise):
- Queues β per-queue counts, p95 latency, failure rate. Click a queue to filter the Jobs tab.
- Jobs β paginated, queue + state filterable list. Inspect opens a drawer with the full payload (rendered in the JSON viewer), the captured error stack on failed jobs, and a Retry button that POSTs to
/api/hub/jobs/:id/retryand re-enqueues a failed job with a bumped attempt counter.
Pure planner (buildJobAggregates()) computes counts / p95 / failure-rate over a flat JobRecord list. Auto-refresh every 4 s.
Two-column file browser over the Prisma file/folder metadata + storage adapter pipeline (issues #16 + #17 wired the foundations):
- Folder tree on the left β recursive, sorted alphabetically, click to navigate; pure planner (
buildFolderTree()) tolerates orphans and cycles. - File grid on the right β IPX thumbnails for
image/*rows via/_ipx/preset_thumbnail/..., generic icon fallback for other MIME types. - Breadcrumb above the grid β root-to-active path, every segment clickable.
- Sort + filter toolbar β name / size / createdAt / updatedAt / mimeType, asc/desc, free-text filename search.
- Folder create + file delete β wired to the existing
/foldersand/filesREST endpoints.
Tenant scope comes from the Better-Auth session (POST /api/auth/organization/set-active β activeOrganizationId). The page 404s outside NODE_ENV=development, identical to every other Hub surface.
Out of scope for this slice (deferred to follow-up issues): TUS upload UI, drag-and-drop move, multi-select bulk actions, lightbox, share-link creator, visibility toggle, server-side zip download.
Live audit of every endpoint registered in NestJS, with its decorator-derived guard kind: @Can(action, subject) (guarded), public (allowlist), dev-only (404s in prod), or unguarded (red). 5-tile summary with per-kind counts so an auditor can spot gaps. JSON endpoint at /api/hub/routes.json for SDK / agent tooling.
Live Mermaid erDiagram of the active Prisma schema (concat'd from schema.prisma + prisma/features/*.prisma). One-to-many vs many-to-many relations inferred from list-typed fields. Toggle source / copy Mermaid buttons.
Every registered email template rendered with a realistic sample payload. Subject + sandboxed HTML iframe + plain-text version side-by-side, plus the sample-payload JSON. Mailpit at :8025 shows actually-sent emails; this page is for "did my edit to the welcome template break anything?".
JSON snapshot of the email-outbox subsystem (issue #11 β at-least-once delivery). Shows the lag classification (pending count, oldest age, threshold) plus the 100 most-recent dispatchable rows. Better-Auth hooks (verify / reset / welcome / invitation) enqueue via the outbox by default with a deterministic idempotency-key (recipient + token), so a "click resend twice" collapses into one row and a server crash between trigger and SMTP-ACK never loses a verification mail. Worker tick is configurable via EMAIL_OUTBOX_TICK_MS (default 1s); records that fail transiently retry with exponential backoff (1m β 5m β 25m, 2h cap, 5 attempts) before graduating to dead-letter. The /health/ready probe trips to 503 when lag exceeds 30s.
Visual editor for transactional templates. Gallery lists every discovered .tsx template (core + module overlay) with rendered subject and a Core (default) / Core (overridden) / Module badge. Composer is three columns: block palette (Greeting Β· Paragraph Β· CTA Β· Footer Β· Code Β· Divider), ordered children with reorder/delete, live preview iframe. Save POSTs the JSON composition to /api/hub/email-builder/save, which codegens a deterministic .tsx source file under src/modules/email/templates/<slug>.tsx. Defense-in-depth path validation (planner-side resolveEmailTemplateTarget + runner-side prefix anchor) keeps path-traversal payloads off disk. Live preview goes through the same @react-email/render pipeline the saved templates use, so what-you-see-is-what-you-send.
Editing core templates (Issue #49): every gallery card has an Anpassen button that fetches /api/hub/email-builder/templates/:name/composition.json, decomposes the .tsx back into a JSON composition, and opens the composer pre-filled. Saving always writes a module overlay under src/modules/email/templates/<name>.tsx β the core file is never touched. Core (overridden) entries get a Reset to default action that DELETEs the overlay file (confirm dialog). Templates whose source uses JSX outside the composer grammar (e.g. ternaries, custom elements) open in a read-only source view instead of breaking the structural editor.
Five-tab handler for Prisma schema evolution: Status (rows from _prisma_migrations with retry on failed), Pending (preview SQL Β· apply one Β· apply all Β· dry-run in a transaction), Diff (prisma migrate diff between live DB and schema.prisma), History (timeline of applied migrations), Create New (kebab-case validated β prisma migrate dev --create-only β SQL preview β apply or discard). Drift banner above all tabs. Every mutating endpoint is gated by a Postgres advisory lock (409 on contention) and 404s outside NODE_ENV=development.
Every JSON endpoint has a sister HTML page that mounts the React SPA's shared JSON viewer β syntax-highlighted, collapsible tree, copy button, key-filter search. Browser default β viewer; Accept: application/json or ?format=json β raw JSON for SDKs.
Scalar renders the OpenAPI 3.1 spec with try-it-out. The raw JSON sits at /api/openapi.json for kubb SDK generation. The path /api-docs-json is a deprecated alias for older nuxt-base-starter workspaces β it serves the same body plus an RFC 8594 Deprecation header pointing at the canonical URL.
Offline type generation. A snapshot of the OpenAPI document ships at docs/openapi.snapshot.json, so frontend consumers can generate types without booting the dev API:
# In the frontend workspace:
openapi-ts --input ../api/docs/openapi.snapshot.json --output app/api-clientThe snapshot is refreshed via bun run dump:openapi. CI fails on drift (see tests/stories/openapi-snapshot.story.test.ts), so a contributor who adds a route can't ship without regenerating the file.
DTO schemas reach the OpenAPI document via the Zod β OpenAPI bridge in src/core/openapi/: the @ApiZodBody / @ApiZodResponse / @ApiZodQuery / @ApiZodParam decorators feed Zod schemas into @nestjs/swagger's metadata pipeline, and applyZodSchemaRegistry() splices any registerZodSchema('Name', schema) calls into components.schemas at boot. The slim-module reference (src/modules/example/) shows the full pattern. Without the bridge the kubb-generated SDK would type every request body as never and every response as unknown.
Permission tester, audit browser, search tester, webhook inspector, realtime inspector. All in the same dark-mode shell with consistent navigation.
The realtime inspector at /admin/realtime ships with three tabs (Sockets / Channels / Events), per-socket disconnect / send-event actions, payload-replay, and a 1.5 s React-Query poll so the live snapshot reflects every gateway lifecycle change without a page reload. Payloads are PII-masked and the surface 404s outside NODE_ENV=development.
| Category | Feature | Default | ENV Toggle |
|---|---|---|---|
| Infrastructure | Tenancy β BA Organizations + session org + RLS (GET /me/tenants, POST /tenants) |
β | FEATURE_MULTI_TENANCY_ENABLED |
| Rate Limiting (multi-window, Postgres) | β | FEATURE_RATE_LIMIT_ENABLED |
|
Idempotency (Stripe-style Idempotency-Key) |
β | FEATURE_IDEMPOTENCY_ENABLED |
|
| Background Jobs (BullMQ / in-memory fallback) | β | FEATURE_JOBS_ENABLED |
|
| Data | Files & TUS Uploads (S3 / local / postgres) | β | FEATURE_FILES_ENABLED |
Asset transforms via IPX (Nuxt-Image-compatible /_ipx/*) |
β | follows FEATURE_FILES_ENABLED |
|
| Full-Text Search (Postgres FTS) | β | FEATURE_SEARCH_ENABLED |
|
| PowerSync (offline-first) | β | FEATURE_POWERSYNC_ENABLED |
|
Field Encryption (AES-256-GCM, per-field keyed via FIELD_ENCRYPTION_KEK; KEK rotation via comma-separated FIELD_ENCRYPTION_LEGACY_KEKS legacy slots) |
β | FEATURE_FIELD_ENCRYPTION_ENABLED |
|
| Geo / Places (geocoding cache) | β | FEATURE_GEO_ENABLED |
|
| GeoIP (offline IPβcountry/city via .mmdb) | β | FEATURE_GEO_IP_ENABLED |
|
| Communication | Email (Nodemailer + Brevo) | β | FEATURE_EMAIL_ENABLED |
| Realtime (LISTEN/NOTIFY + Socket.IO) | β | FEATURE_REALTIME_ENABLED |
|
| Integration | Webhooks (HMAC-signed + retry) | β | FEATURE_WEBHOOKS_ENABLED |
| Model Context Protocol (MCP) | β | FEATURE_MCP_ENABLED |
|
| Observability | OpenTelemetry + Pino logs | β | FEATURE_OBSERVABILITY_ENABLED |
Each toggleable feature drives module imports, controller registration, and middleware wiring conditionally. Disabled features have zero runtime cost β no providers, no routes, no startup time.
src/
βββ core/ β Template-owned. Synced via `bun run sync:from-template`.
β βββ app/ β Bootstrap + AppModule + dev-tab auto-open
β βββ auth/ β Better-Auth wiring + API keys + PowerSync JWT + 24h verifications-cleanup cron
β βββ concurrency/ β ETag + If-Match optimistic concurrency
β βββ dx/ β /hub + /admin + /errors + /openapi (React SPA shell + JSON sidecars)
β βββ email/ β EmailService + React Email .tsx templates
β βββ encryption/ β AES-256-GCM field encryption
β βββ errors/ β CORE_* error codes + RFC 7807 filter
β βββ features/ β FeaturesSchema (Zod) β single source of truth
β βββ files/ β TUS uploads + storage adapters
β βββ multi-tenancy/ β Tenant guard + RLS helpers
β βββ observability/ β OTel + Pino + traceparent middleware
β βββ openapi/ β Zod β OpenAPI bridge (decorators + named-schema registry)
β βββ output-pipeline/ β 4-stage permission/secret-filter
β βββ permissions/ β CASL ability + DB-rule resolver + admin CRUD
β βββ prisma/ β PrismaService + driver-adapter
β βββ realtime/ β LISTEN/NOTIFY + Socket.IO gateway
β βββ search/ β FTS query parser + cross-resource search
β βββ webhooks/ β HMAC + retry-policy + dispatcher
βββ modules/ β Project-owned. Add your domain here.
βββ shared/ β Cross-tier types (channels, event payloads, SDK seeds).
Conventions: ESM with .js import suffixes (TypeScript nodenext). Pure-planner / thin-runner split β every helper that touches I/O has a pure planner + a thin glue layer. Named error sentinels mapped to RFC 7807 by the global filter.
The architectural rationale lives in docs/architecture.md, the coding conventions in docs/code-guidelines.md, the agent-readable orientation in CLAUDE.md.
| Command | What it does | Threshold |
|---|---|---|
bun run test:unit |
Pure-function tests (tests/unit/) |
β |
bun run test:e2e |
Story tests + HTTP e2e (tests/stories/, tests/*.e2e-spec.ts) |
β |
bun run test:types |
TypeScript compile checks (tests/types/) |
β |
bun run test:coverage |
Vitest + V8 coverage report | core β₯ 80% lines Β· modules β₯ 75% lines |
bun run test:summary |
Vitest JSON reporter β /hub/tests page |
β |
Discipline: strict red-green-refactor TDD β every behaviour change starts as a failing test. The 6 quality gates (lint, format, test:types, test:unit, test:e2e, test:coverage, build) gate every commit.
Currently 1712 tests across 194 files. Coverage 95.48% lines (well above the 80% gate). Lines are the headline metric; statements / functions / branches are tuned looser since defensive runtime guards inflate them without representing real risk β see src/core/testing/coverage-gate.ts.
| Runtime | Bun 1.x (Node 22 fallback) |
| Framework | NestJS 11 |
| ORM | Prisma 7 (driver-adapter mode) |
| Database | Postgres 18 (pg_uuidv7 for UUID v7 IDs) |
| Auth | Better-Auth 1.6 β email/password, social providers, passkeys, 2FA, API keys |
| Validation | Zod 4 |
| Tests | Vitest 4 + Testcontainers |
| Lint / Format | oxlint / oxfmt β Rust-fast tooling |
| API Docs | Scalar (UI) + @nestjs/swagger (spec) |
| SDK Generation | kubb |
| Observability | Pino + OpenTelemetry |
| Container | Docker Compose (Postgres, Mailpit, RustFS, OTel collector) |
# Development
bun run dev # Dev server + Prisma Studio + auto-open Hub
bun run dev --tunnel # β¦same, plus a public Cloudflare-Tunnel URL
bun run lint # oxlint (95 rules, 30ms)
bun run format # oxfmt --check
bun run format:fix # oxfmt
bun run build # Bundle to dist/
# Testing
bun run test:unit # Unit tests
bun run test:e2e # E2E + story tests
bun run test:types # tsc --noEmit
bun run test:coverage # V8 coverage with gate
bun run test:summary # JSON reporter for /hub/tests
# Schema
bun run prepare:schema # Concat feature schemas β schema.generated.prisma
bun run prisma:generate # Generate Prisma client (also wires the pnpm-hoisting symlink)
bun run prisma:migrate # Apply migrations
# Project lifecycle
bun run setup # .env + docker + prepare:schema + migrate + seed
# (idempotent env; use --bootstrap to re-run DB steps).
# Also writes API port + portless URL into
# projects/app/.env when present. Custom values are
# never clobbered.
bun run onboard # First-run sanity check (Bun / .env / Postgres-TCP / Prisma)
bun run doctor # Comprehensive health check (containers, services, secrets,
# disk space, env strength) β JSON output for CI via --json
bun run add:module <name> # Scaffold a new tenant-scoped resource under src/modules/
# (DTO / service / controller / module / README + story tests).
# Mirrors src/modules/example/. Idempotent: refuses to overwrite
# an existing folder. Prints next-steps for the Prisma model,
# RLS migration, and AppModule wiring.
bun run rename <new-name> # Rename project across the codebase
bun run reset # Wipe DB β migrate β seed in one shot (refuses on prod /
# non-local DATABASE_URL hosts as defense-in-depth)
bun run seed # Insert deterministic demo data (2 tenants, 6 users, roles)
bun run sync:from-template # Pull latest src/core/ from upstream
bun run sync:to-template # Contribute changes back upstream
bun run sdk:generate # kubb β typed SDK from /api/openapi.json
bun run docs:screenshots # Re-shoot every dev-portal page (Playwright,
# docs/showcase/screenshots/) β opt-in, see
# docs/showcase/README.mdThe setup wizard (bun run setup) generates a .env from .env.example with strong secrets. Key vars:
NODE_ENV=development
PORT=3000
APP_BASE_URL=https://api.your-project.localhost # or http://localhost:3000
DATABASE_URL=postgresql://user:pass@localhost:5432/db
BETTER_AUTH_SECRET=<32 bytes>
# Better-Auth rate-limiter for /api/auth/* (credential-stuffing protection).
# Default: false in test/development, true in production.
# Override: BETTER_AUTH_RATE_LIMIT_ENABLED=true|false
# BETTER_AUTH_RATE_LIMIT_ENABLED=false
# Optional but useful in dev
MAILPIT_WEB_URL=http://localhost:8025
POWERSYNC_URL=http://localhost:8080
# Hub controls
NO_OPEN=1 # Skip browser auto-open
PRISMA_STUDIO=0 # Skip Prisma Studio sibling spawn
DISABLE_PORTLESS=1 # Force http://localhost:<port>
# Feature toggles (all 22 toggleable + authMethods config via /hub/features)
FEATURE_WEBHOOKS_ENABLED=true
FEATURE_REALTIME_ENABLED=true
# ...This project is optimised for AI-assisted development with Claude Code β every convention, test pattern, and Hub page exists with an AI agent as a first-class user.
# Slash commands ship with the repo
/add-module <name> # New project resource (controller / service / DTO / tests)
/add-feature <key> "<desc>" # Toggleable feature flag end-to-end
/add-page <slug> "<title>" # New /hub or /admin page in the dark-mode shell
/upstream-pr # PR a src/core/ fix back to nest-base (downstream projects)Skills β procedural how-tos that encode the project's conventions so every agent does the right thing the first time:
| Skill | Use it when |
|---|---|
understanding-the-architecture |
First contact β mental model in 200 lines |
avoiding-common-pitfalls |
Catalogue of every place this codebase will burn you |
writing-story-tests |
TDD pattern for tests/stories/*.story.test.ts |
running-tdd-slice |
Red-green-refactor cycle for one behaviour change |
working-with-prisma |
Prisma 7 + driver-adapter mode |
writing-migrations |
Schema β prepare β migrate β RLS pattern |
wiring-permissions |
Add CASL ability checks to a handler |
debugging-permission-denials |
5-step diagnostic path: 403 β log β tester β DB rules β regression test |
adding-feature-flag |
New toggleable feature, end-to-end |
adding-feature-module |
Scaffold a feature module under src/modules/ |
adding-error-code |
New CORE_* error code with i18n |
extending-hub |
New Hub or admin page in the shared shell |
syncing-from-template |
Pull src/core/ updates from upstream |
contributing-upstream |
When and how to PR a fix back to nest-base |
A fresh agent reads .claude/QUICKSTART.md (60 sec) β .claude/AGENTS.md (lookup table) β the matching skill, and is productive in under 3 minutes. Six quality gates per commit ensure the agent can't ship a regression.
Two-way sync flow β when Claude in a downstream project fixes a bug in src/core/ or builds a generic capability, it offers to open a PR back to nest-base automatically. Generic improvements flow upstream so every consumer benefits on their next bun run sync:from-template. See .claude/skills/contributing-upstream.md.
Test-ability hatch β e2e specs that hit a @Can()-gated route can pre-seed an admin Ability via the X-Test-Ability: full header instead of driving the full Better-Auth sign-in flow. Honoured only when NODE_ENV=test (strict no-op in production). See src/core/permissions/test-ability.ts.
β Full guide: docs/working-with-ai-agents.md.
Getting started
docs/working-with-ai-agents.mdβ AI-driven development workflow (Claude Code, slash commands, skills)docs/consumer-guide.mdβ bootstrapping a new project on this templateCONTRIBUTING.mdβ TDD discipline, six gates, PR rituals
Reference
docs/architecture.mdβ module overview, permission model, output pipeline, security layersdocs/code-guidelines.mdβ conventions a quick scan won't teach youCLAUDE.mdβ agent-readable orientation.claude/AGENTS.mdβ full agent / skill / command cataloguedocs/api-stability-promise.mdβ semver + deprecation rulesdocs/webhook-spec.mdβ outbound webhook contractdocs/initialisation-history.mdβ historical record of the eight-phase bootstrap
Workflows
docs/template-update-workflow.mdβ pulling upstream changesdocs/customization-guide.mdβ adding domain modules insrc/modules/docs/core-contribution-guide.mdβ contributing back tosrc/core/docs/showcase/README.mdβ every/hub/*,/admin/*,/errors,/openapipage at desktop + mobile, plus thebun run docs:screenshotsreproduction command
Community
SECURITY.mdβ vulnerability disclosureCODE_OF_CONDUCT.mdβ community standards
MIT β see LICENSE.
The GeoIP feature reads .mmdb files produced by the configured
provider. The data is not redistributed with this template β each
deployment downloads its own copy at install time β but using it
imposes the provider's terms.
- dbip-lite (default): IP-to-City Lite database Β© db-ip.com. Distributed under the Creative Commons Attribution 4.0 International License. When you ship a product surface that exposes geo-derived data (e.g. "login from Berlin" e-mails), credit "IP geolocation by db-ip.com" somewhere accessible to end users (privacy notice or settings page is fine).
- MaxMind GeoLite2 (opt-in): subject to the
MaxMind GeoLite2 EULA.
Free signup + license key required; downloads track the requesting
IP. Pick
dbip-litefor Schrems-II-strict deployments.






