Development

Generators

@nestledjs/generators contains the generators you use during normal development. They handle workspace setup, code generation from your Prisma schema, and additive scaffolding for new models.

The current release is 3.0.3 and exposes six generators:

  • workspace-setup
  • crud
  • models
  • custom
  • model-extension Added in 2.0.0
  • sdk

This page documents the latest release

Nestled keeps one always-current documentation set rather than a separate copy per version. Anything whose behavior depends on which version you are on carries an inline badge — Added in 2.0.0 or Changed in 3.0.0 — so you can tell at a glance whether a section applies to your workspace. Full history lives in the changelog.

You rarely run these individually

In day-to-day development, pnpm db-update runs the schema-driven generators automatically. The full pipeline is: prisma:generate → @nestledjs/generators:crud → @nestledjs/generators:models → @nestledjs/generators:custom → @nestledjs/generators:sdk. You usually run generators directly only for first-time workspace setup or when regenerating one layer while debugging.

model-extension is intentionally not part of that pipeline — you run it by hand when you want a resolver module for a specific model.

Upgrading to 3.x — security migration required

3.0.0 makes every generated CRUD operation admin-only and removes @crudAuth. Before deleting the old annotations, replace every user, public, or custom-level operation with an explicit application resolver using a purpose-built input and a user- or tenant-scoped query.

Follow the Migrating to 3.0 guide before running pnpm db-update. Generator 3 rejects a schema that still contains @crudAuth rather than silently ignoring it.

Coming from 1.1.x?

2.0.0 also has an ordered migration: it changes how generated resolvers are registered and removes the per-model shells the custom generator used to emit. Complete Migrating to 2.0 before the 3.0 security migration.

Security fixes in 1.1.3 and 1.1.5 — audit required for deployed workspaces

If you are upgrading from an older line, two releases carry exposure that upgrading alone does not resolve:

  • Before 1.1.3, the models generator ignored @graphqlOmit, so annotated fields (commonly secrets like encryptedAccessToken) stayed queryable. See the 1.1.3 audit steps.
  • Before 1.1.5, generated list queries accepted an untyped filters blob that reached Prisma's where clause verbatim, making every database column filterable — including @graphqlOmit columns — and usable as an oracle to recover tokens. See the 1.1.5 audit steps.

Both require rotating affected secrets, not just regenerating.


@nestledjs/generators:workspace-setup

nx g @nestledjs/generators:workspace-setup --name my-app

Run this once, immediately after cloning the starter template. It bootstraps a fresh workspace in order:

  1. Renames the project — replaces nestled-template with your --name across every file
  2. Preserves updater metadata — deliberately skips .nestled/ and .nestled-updates/ so upgrade and doctor workflows keep working
  3. Ensures .env exists — copies .env.example if .env is missing
  4. Validates DATABASE_URL — refuses non-localhost database URLs as a safety guard
  5. Starts infrastructure — ensures Docker and Docker Compose are running, then waits for the database
  6. Applies Prisma migrations — runs the template's Prisma apply command
  7. Generates the Prisma client — runs pnpm prisma:generate before anything imports Prisma
  8. Generates GraphQL types — runs the template's GraphQL generation command
  9. Seeds the database — creates initial development data

Options: --name (required) — your project name. It must match ^[a-z][a-z0-9-]*$, for example my-app, acme-saas, or todo-pro. This becomes the @name/ namespace for all imports, so keep it short.

Requires Docker and a local database. The generator intentionally refuses production or hosted database URLs.


@nestledjs/generators:crud

Part of pnpm db-update

nx g @nestledjs/generators:crud

Reads your Prisma schema via DMMF and generates a complete admin CRUD API:

  • Data access module at libs/api/generated-crud/data-access/ — model metadata and pagination config
  • Feature module at libs/api/generated-crud/feature/ — one resolver per model with full CRUD operations

For each model, generates:

  • {model} query (read one)
  • {models} query (read many with pagination)
  • {models}Count query
  • create{Model}, update{Model}, delete{Model} mutations

Handles BigInt IDs with the GraphQLBigInt scalar. List operations use typed, bounded-depth filters that omit fields marked @graphqlOmit.

Admin-only security boundary

Changed in 3.0.0

Every generated resolver class declares both @AdminOnly() and @UseGuards(GqlAuthAdminGuard). The guard cannot be lowered per model or per operation: @crudAuth was removed in 3.0.0, and the crud, sdk, models, and model-extension generators fail before writing if any model still carries it.

Generated create/update inputs, administrative filters, and recursive relation selection exist for the admin data browser. Do not reuse them as a user-facing API. Application workflows belong in additive custom resolvers with:

  • purpose-built inputs containing only allowed fields;
  • an explicit access decorator and guard;
  • user or tenant scope derived from authenticated server context; and
  • explicit Prisma where and select clauses.

The recursive GraphQL-to-Prisma selection compiler is now a private, non-exported implementation detail inside the generated ApiCrudDataAccessService. It resolves relation metadata through the generated DATABASE_MODELS_BY_NAME map instead of repeatedly scanning the model array. See Migrating to 3.0 for replacing lower-privilege operations and removing the old createSelect/viewer-context machinery.

Typed admin filters

Changed in 3.0.1

Filter fields and operators are derived from the visible Prisma model:

  • all supported scalar filters provide equals, in, and not;
  • strings also provide contains, startsWith, and endsWith;
  • numbers and dates also provide lt, lte, gt, and gte;
  • list relations provide some, every, and none; and
  • to-one relations provide is and isNot, including null checks, while retaining the direct nested shorthand from 1.1.5.

@graphqlOmit fields never enter the filter types. Json columns and scalar lists remain unfilterable.

AND, OR, and NOT are bounded rather than self-referencing: each relation or logical operator points only to the next generated input depth. The default --filterDepth is 3, and the deepest level contains scalars only. Generated data access normalizes the compatible to-one shorthand into Prisma's relation-filter form and rejects a contradictory is: null plus direct predicate before it reaches Prisma.

3.0.2 also makes this normalization compile when noPropertyAccessFromIndexSignature is enabled. See the 3.0.1 and 3.0.2 entries for patch-specific upgrade notes.

How generated resolvers get registered

Changed in 2.0.0

The feature library exports a single module, ApiGeneratedCrudFeatureModule, written to api-generated-crud-feature.module.ts. The generator imports it into coreModules in apps/api/src/app.module.ts, and that import is the registration — it's what puts every generated resolver into the running schema. Re-running the generator is safe: the module is only added if it isn't already listed.

Before 2.0.0, generated resolvers reached the schema indirectly, by having each per-model custom resolver extend its generated counterpart. That's gone. If you are upgrading, see Migrating to 2.0 — importing this module while inherited custom resolvers are still registered will register every generated field twice.


@nestledjs/generators:models

Part of pnpm db-update

nx g @nestledjs/generators:models

Reads your Prisma schema and generates TypeScript classes with GraphQL decorators at libs/api/core/models/. These models are used by generated CRUD resolvers and by your custom API code.

This generator replaces the older template-local model generation script. Keeping model generation in @nestledjs/generators means fixes ship with the generator package instead of requiring template file changes.

Prisma Json fields are emitted as GraphQLJSON, which supports objects, arrays, and scalar JSON values. If you upgraded from a version that emitted GraphQLJSONObject, regenerate models after installing @nestledjs/[email protected].

Fields annotated with @graphqlOmit in the Prisma schema are dropped from the generated models (both the @Field() decorator and the property). Because the emitted @ObjectType() is the server GraphQL schema, this makes models.ts the single authoritative enforcement point — an omitted field never reaches api-schema.graphql and is not queryable through the API. This behavior was fixed in 1.1.3; see the changelog for the security audit steps if you built with an earlier version.

model OAuthAccount {
  id                   String @id
  /// @graphqlOmit
  encryptedAccessToken String   // not emitted to the GraphQL schema
}

@nestledjs/generators:custom

Part of pnpm db-update

nx g @nestledjs/generators:custom
Changed in 2.0.0No longer generates a module per model.

Creates and maintains the custom API library at libs/api/custom/ — the library itself plus its stable barrel files:

  • src/index.ts — the library barrel, re-exporting the two below
  • src/lib/default/index.ts — created empty if missing; where your model-specific modules are exported from
  • src/lib/plugins/index.ts — created with export const customPlugins = [] if missing or empty

It does not read your Prisma schema and does not create per-model code. Running it is safe and effectively idempotent: existing extensions under src/lib/default/ are left untouched.

Changed in 2.0.0

Through 1.1.x this generator emitted a {model}.service.ts / {model}.resolver.ts / {model}.module.ts shell for every Prisma model, because generated CRUD relied on those custom resolvers inheriting from the generated ones in order to be registered at all. 2.0.0 registers generated CRUD directly through ApiGeneratedCrudFeatureModule, so the shells are no longer needed and are no longer produced.

To add a module for one specific model, use model-extension — it is explicit and on-demand rather than blanket-generated. See Migrating to 2.0 for what to do with shells you already have.

Options: --name (default: custom), --directory, --overwrite (default: false)

--overwrite removes and recreates the library. It is destructive — everything under libs/api/custom/ goes, including your own extensions — so it is only for rebuilding a broken library from scratch.


@nestledjs/generators:model-extension

Added in 2.0.0

Run on demand — not part of pnpm db-update

nx g @nestledjs/generators:model-extension Post

Scaffolds an additive, model-specific resolver module for a single Prisma model. Use it when a model needs behavior beyond generated CRUD — a computed field, a domain-specific mutation, an extra guard.

In 3.0.0, this is also the starting point for any application workflow that used to lower generated CRUD through @crudAuth. Define a new, non-colliding GraphQL operation with its own input and explicitly scoped database query; do not inject ApiCrudDataAccessService or import generated CRUD inputs into the user-facing module.

This is the deliberate replacement for the blanket per-model shells that custom used to emit: you get a module for the models that actually need one, when they need one, instead of a directory of empty classes for every model in the schema.

It writes two files to libs/api/custom/src/lib/default/{name}/:

  • {name}.resolver.ts — an empty @Resolver(() => Model) class for you to fill in
  • {name}.module.ts — a NestJS module providing and exporting that resolver

It then adds the export to libs/api/custom/src/lib/default/index.ts and registers the module in the defaultModules array in apps/api/src/app.module.ts. No service is generated — inject what you need.

Options:

  • model (required, first positional) — the Prisma model to target. Matched case-insensitively; an unknown name fails listing the available models.
  • --name — artifact name; defaults to the model name

--name changes what the generated files and class are called without changing the GraphQL type they target — the resolver is always @Resolver(() => Model) for the model you passed. So Post can carry a PostPublishing extension alongside a plain Post one, both resolving Post fields:

nx g @nestledjs/generators:model-extension Post --name PostPublishing

It refuses rather than overwrites. If the target folder already exists and is non-empty, the generator errors instead of touching your code. It also requires the custom library to exist first — run @nestledjs/generators:custom if you see "The custom API library is not initialized" — and rejects models marked @skipCrud, since those have no generated GraphQL model to target.


@nestledjs/generators:sdk

Part of pnpm db-update

nx g @nestledjs/generators:sdk

Reads your Prisma schema and maintains the GraphQL client SDK. As of 3.0.3, it has a strict ownership boundary:

  • Generated admin documents at libs/shared/sdk/src/__admin/<model>/ — recreated for every visible Prisma model, including fragments, read/count queries, create/update/delete mutations, nested relation IDs, and _count fields
  • Application documents under libs/shared/sdk/src/graphql/<feature>/ — preserved exactly; never created or deleted based on Prisma models
  • Shared SDK source — updates database-models.ts, the core uptime/paging document, SDK exports, and GraphQL Code Generator setup

Write purpose-built queries, mutations, and fragments under src/graphql/<feature>/ only after their explicit application resolver exists. Do not copy the generated admin root operations into this tree, and do not add empty .graphql placeholders.

Changed in 3.0.3Public feature documents are fully application-owned.

The generator adds sdk / sdk:watch scripts. It creates codegen.yml when missing and preserves an existing file unless you opt into replacing it.

Options: --forceCodegen (default: false) — overwrite codegen.yml even if it exists

Looking for older scaffolders?

The old split-package commands such as @nestledjs/api:plugin, @nestledjs/api:integrations, and @nestledjs/shared:sdk are legacy template-build commands, not active @nestledjs/generators commands. See Generators (Legacy) only if you are studying how the template was originally assembled.

Previous
Architecture