plugins/lt-dev/skills/generating-nest-servers/SKILL.md
Handles ALL NestJS and @lenne.tech/nest-server development tasks including module creation, service implementation, controller/resolver development, model definition, and debugging. Covers lt server commands, @Roles/@Restricted security, CrudService patterns, and API tests. Supports monorepos (projects/api/, packages/api/). Activates when working with src/server/ files, NestJS modules, services, controllers, resolvers, models, DTOs, guards, decorators, or REST/GraphQL endpoints. NOT for Vue/Nuxt frontend (use developing-lt-frontend). NOT for nest-server version updates (use nest-server-updating). NOT for TDD workflow orchestration (use building-stories-with-tdd).
npx skillsauth add lennetech/claude-code generating-nest-serversInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
declare keyword on Model properties kills decorators — declare name: string tells TypeScript "this property exists but isn't emitted at runtime" — which means Mongoose/Typegoose decorators (@Prop, @Field) attached to it are lost. Always use name!: string (definite assignment) or name: string = '' (default value) on model fields. Tests pass silently in memory but production MongoDB writes drop the field.securityCheck() returning this unchanged is a red flag — A trivial return this; implementation means no permission boundary exists on the model. The decorator system requires @Restricted/@Roles + an actual check (usually filtering sensitive fields or verifying ownership). If the model genuinely has nothing to restrict, add a comment explaining why — otherwise it will be flagged in every security review.--controller flag generates REST — even in GraphQL projects — lt server object <name> --controller produces a REST controller by default. For GraphQL projects, use --resolver instead. The CLI does not auto-detect from the project's existing pattern.CrudService is the default — always extend it — New modules often get hand-rolled service classes instead of extends CrudService. The framework's pagination, filtering, population, and permission integration all depend on CrudService inheritance. Hand-rolled services break /api/<entity>/find queries silently for consumers.lt server usually respects this; hand-edited additions often break it. Run a final pass before committing.Developers typically work in a Lerna fullstack monorepo created via lt fullstack init:
project/
├── projects/
│ ├── api/ ← nest-server-starter (depends on @lenne.tech/nest-server)
│ └── app/ ← nuxt-base-starter (depends on @lenne.tech/nuxt-extensions)
├── lerna.json
└── package.json (workspaces: ["projects/*"])
Package relationships:
projects/api/ and any code depending on @lenne.tech/nest-serverpnpm start, pnpm run dev, pnpm test)lt server module, lt server object, lt server addProp, lt server createRule: If it involves NestJS or @lenne.tech/nest-server in ANY way, use this skill!
| User Intent | Correct Skill |
|------------|---------------|
| "Create a NestJS module" | THIS SKILL |
| "Debug a service error" | THIS SKILL |
| "Add a REST endpoint" | THIS SKILL |
| "Add an AI tool / chat endpoint / MCP server" (nest-server ≥ 11.26.0) | THIS SKILL — see reference/ai-module-integration.md + reference/mcp-integration.md |
| "Update nest-server to v14" | nest-server-updating |
| "Write tests first, then implement" | building-stories-with-tdd |
| "Update npm packages" | maintaining-npm-packages |
| "Build a Vue component" | developing-lt-frontend |
| "Run lt fullstack init" | using-lt-cli |
developing-lt-frontend - For ALL Nuxt/Vue frontend development (projects/app/)building-stories-with-tdd - For TDD workflow (tests first, then implementation)using-lt-cli - For Git operations and Fullstack initializationnest-server-updating - For updating @lenne.tech/nest-server versionscontributing-to-lt-framework - When modifying @lenne.tech/nest-server itself and testing via pnpm link/lt-dev:review - General security review of branch diff/lt-dev:backend:sec-review - Security review after implementing endpoints or modifying auth/authz/lt-dev:backend:sec-audit - Full OWASP security audit for dependencies, config, and codeIn monorepo projects:
projects/api/ or packages/api/ → This skillprojects/app/ or packages/app/ → developing-lt-frontendWhen starting the API for manual testing, debugging, or E2E tests: prefer lt dev up over nest start / pnpm dev directly. lt dev up serves the API under a stable HTTPS URL (https://api.<slug>.localhost) via Caddy, sets BASE_URL/APP_URL/NSC__MONGOOSE__URI automatically, and detaches into <root>/.lt-dev/api.log. Stop with lt dev down. For non-lt-projects (or when explicitly requested): use run_in_background: true and pkill -f "nest start" afterwards. Leaving dev servers orphaned blocks the Claude Code session ("Unfurling..."). Full rules: managing-dev-servers skill.
When creating new modules, objects, or adding properties, use lt server CLI commands first before writing code manually. The CLI generates complete, standards-compliant scaffolding with all decorators, imports, and module integration.
# Always add --noConfirm --skipLint for non-interactive execution
lt server module --name Product --controller Rest --noConfirm --skipLint \
--prop-name-0 name --prop-type-0 string \
--prop-name-1 price --prop-type-1 number
lt server object --name Address --noConfirm --skipLint \
--prop-name-0 city --prop-type-0 string
lt server addProp --type Module --element User --noConfirm --skipLint \
--prop-name-0 avatar --prop-type-0 string --prop-nullable-0 true
After CLI scaffolding, customize the generated code: business logic, security rules (securityCheck), descriptions, and custom methods.
Complete flag reference: reference/configuration.md
@Restricted() or @Roles() decoratorssecurityCheck() to bypass securitylt server permissions after creating modulesComplete security rules: reference/security-rules.md | OWASP checklist: reference/owasp-checklist.md
Prefer CrudService methods over direct access to your own Mongoose Model (this.mainDbModel.xxx / this.<modelName>Model.xxx inside the owning Service). Direct own-Model access is an instance of the Informed-Trade-off Pattern (see also reference/security-rules.md Rule 14): allowed with a good reason, but every use must be analyzed for unintentionally bypassed processes, skipped authorization, or missing side-effects.
// AVOID - Direct model access bypasses security and permissions
const product = await this.productModel.findOne({ _id: id });
const users = await this.mainDbModel.find({ active: true });
await this.orderModel.updateOne({ _id: id }, { status: 'done' });
// PREFERRED - CrudService methods handle security, permissions, population
const product = await this.findOne({ id }, serviceOptions);
const users = await this.find({ filterQuery: { active: true }, currentUser });
await this.update(id, input, serviceOptions);
await this.userService.findOne({ id: userId }, { currentUser });
Why CrudService first:
checkRestricted() enforces field-level @Restricted permissions (set via @UnifiedField({ roles })) — direct model access bypasses this@Roles is enforced by RolesGuard at controller level; field-level @Restricted goes through checkRestricted() in the service layerLegitimate reasons to opt out (direct own-Model access):
$push, $pull, $inc, $addToSet) via findByIdAndUpdate — CrudService.update() doesn't expose these.aggregate([...])) for reporting/statsbulkWrite, insertMany, deleteMany) for migrations or cleanupprocess() overhead is measurableMandatory check before every direct own-Model access — ensure nothing is unintentionally bypassed or a side-effect is missed:
checkRestricted() is skipped. If the result reaches a user and the Model has role-restricted @UnifiedField({ roles }) fields: either follow up with super.update(id, {}, serviceOptions) to run the pipeline, or manually apply the filter.process() is skipped. Validate any service-built payload shapes explicitly.Documentation in code: comment the reason + which CrudService logic is safely bypassed OR manually replicated. Preferred follow-up patterns:
this.mainDbModel.findByIdAndUpdate(id, { $push: {...} }); await super.update(id, {}, serviceOptions);const doc = await this.mainDbModel.findById(id).exec(); return this.processResult(doc, serviceOptions); (processResult runs population + prepareOutput/secret removal but requires YOU to authorize upstream)const raw = await this.mainDbModel.aggregate(pipeline); return raw.map(r => this.mainModelConstructor.map(r)); or raw.map(r => this.mainDbModel.hydrate(r)).Force and Raw variants — Rule 15: every CrudService method has *Force (disables checkRights + removeSecrets + RoleGuard) and *Raw (additionally disables prepareInput/prepareOutput entirely) variants. Use getForce/findForce/createForce/etc. for system-internal flows where no user exists. Results may contain passwords, tokens, and hidden fields — they MUST NOT reach a user response without explicit field stripping. *Raw returns closest-to-DB shape, no translations, no type mapping. See reference/security-rules.md Rule 15.
Native driver access — Rules 5-6: mainDbModel.collection and mainDbModel.db are blocked at the type level via SafeModel<T>. Use this.getNativeCollection(reason) or this.getNativeConnection(reason) — both require ≥20-char reasons and log [SECURITY] warnings. Bypasses ALL Mongoose plugins.
Informed-Trade-off Pattern: several framework conventions have a standard safe path AND an opt-out for good reasons. Opt-outs require: (1) a documented reason in code, (2) analysis of what the standard path does that the opt-out skips, (3) either safe-skip justification or manual replication of bypassed logic. Applies to: foreign @InjectModel (Rule 12), plain-object responses (Rule 13), direct own-Model access (Rule 14), Force/Raw variants (Rule 15), native-driver access (Rules 5-6), deprecated-API use (Deprecation-scan phase). Full pattern definition: reference/informed-trade-off-pattern.md.
Foreign Model Rule — Rule 12 instance of the Informed-Trade-off Pattern (applies ONLY to Models that do NOT belong to this Service): @InjectModel(X.name) for the Service's OWN primary Model (passed to super({ mainDbModel })) is the standard pattern — no extra requirements. Injecting any OTHER Model is allowed but requires (1) good reason in a code comment, (2) analysis of the corresponding Service (securityCheck(), @Restricted/@Roles, ownership, field filtering, hooks, events, side-effects). See reference/security-rules.md Rule 12.
Model Instances vs. Plain Objects — Rule 13 instance of the Informed-Trade-off Pattern: the CheckSecurityInterceptor calls each response object's securityCheck(user, force) after the controller returns. Plain objects (from .lean(), toObject(), spreads { ...doc }, raw aggregate() output, native-driver results) lose the Model-specific securityCheck logic — ownership checks and role-based field clearing do not run. Framework removeSecrets() still strips configured secretFields and processDeep still recurses to nested Model instances, so plain objects are a trade-off, not an automatic leak. See reference/security-rules.md Rule 13. Rule 12 and Rule 13 share the same bypass vectors on securityCheck; a single call site can hit both.
CoreModel.securityCheck default return this is intentional when the Model has nothing to filter. Before accepting a trivial implementation, actively evaluate whether securityCheck is the only place where required authorization can live — ownership-based field visibility, relationship-based visibility, state-dependent exposure, conditional record hiding (return undefined in list responses), cross-field visibility rules. None of these can be expressed via @Roles/@Restricted/controller guards.
Details: reference/framework-guide.md
ErrorCode (NON-NEGOTIABLE)NEVER throw NestJS exceptions with raw string messages. Every project MUST use the framework's structured ErrorCode registry (src/core/modules/error-code/).
// WRONG — raw string, no code, not translatable
throw new NotFoundException('Buyer not found');
throw new BadRequestException('Invalid ObjectId format');
// CORRECT — typed code with #CODE: marker + auto-translation
import { ErrorCode } from '../../common/errors/project-errors';
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND);
throw new BadRequestException(ErrorCode.INVALID_FIELD_FORMAT);
Mandatory baseline per project:
src/server/common/errors/project-errors.ts with ProjectErrors registry + ErrorCode = mergeErrorCodes(ProjectErrors) exporterrorCode: { additionalErrorRegistry: ProjectErrors } in EVERY env block of src/config.env.tsErrorCode from the project file (NOT from @lenne.tech/nest-server) to get LTNS_* + PROJ_* combinedLTNS_* core codes when generic (not-found, validation, unauthorized); define PROJ_XXXX only for domain-specific semanticsPROJ_, APP_, …) and never mixComplete rules, ranges, HTTP mapping & migration pattern: reference/error-handling.md | Integration scenarios: src/core/modules/error-code/INTEGRATION-CHECKLIST.md
declare Keyword// WRONG - Decorator won't work!
declare name: string;
// CORRECT
@UnifiedField({ description: 'Product name' })
name: string;
Details: reference/declare-keyword-warning.md
Apply descriptions consistently to EVERY component (Model, CreateInput, UpdateInput, Objects, Class-level decorators). Format: 'English text' or 'English (Deutsch)' for German input.
Complete guide: reference/description-management.md
# Create module (REST is default!) — always use --noConfirm --skipLint
lt server module --name Product --controller Rest --noConfirm --skipLint
# Create SubObject
lt server object --name Address --noConfirm --skipLint
# Add properties
lt server addProp --type Module --element User --noConfirm --skipLint
# New project
lt server create <server-name> --noConfirm
# Permissions report (audit @Roles, @Restricted, securityCheck)
lt server permissions --format html --open
lt server permissions --format json --output permissions.json
lt server permissions --failOnWarnings # CI/CD mode
API Style: REST is default. Use --controller GraphQL only when explicitly requested.
Complete configuration & property flags: reference/configuration.md
1. Detect test framework BEFORE writing or running any test (see below)
2. Write API tests FIRST (REST/GraphQL endpoint tests)
3. Implement backend code until tests pass
4. Iterate until all tests green
5. Then proceed to frontend (E2E tests first)
BEFORE writing or running ANY test, detect which framework and import style the project uses. Vitest vs. Jest, plus globals: true/false in vitest.config.ts, determines whether describe/it/expect must be imported.
Details: reference/workflow-process.md
For full TDD workflow orchestration, use building-stories-with-tdd skill.
afterAll(async () => {
await db.collection('entities').deleteMany({ createdBy: testUserId });
await db.collection('users').deleteMany({ email: /@test\.com$/ });
});
Use separate test database: app-test instead of app-dev
ALWAYS read actual source code before guessing framework behavior. lenne.tech projects ship the framework source in one of two consumption modes:
@lenne.tech/nest-server installed as a dependency. Source lives in node_modules/@lenne.tech/nest-server/.src/core/VENDOR.md exists in the api project. Source lives DIRECTLY in <api-root>/src/core/ as first-class project code (no @lenne.tech/nest-server npm dependency). Detect via test -f <api-root>/src/core/VENDOR.md.All paths in the table below use the npm-mode base. In vendored projects, substitute:
node_modules/@lenne.tech/nest-server/src/core/ → src/core/node_modules/@lenne.tech/nest-server/src/core.module.ts → src/core/core.module.tsnode_modules/@lenne.tech/nest-server/CLAUDE.md → src/core/VENDOR.md (vendored projects document the baseline + local patches here instead)node_modules/@lenne.tech/nest-server/FRAMEWORK-API.md → same concept in upstream repo; vendored projects may copy it into src/core/ during sync, else consult the upstream GitHub repo at the baseline tag recorded in VENDOR.md.node_modules/@lenne.tech/nest-server/.claude/rules/ → not shipped into vendored projects; read from the upstream repo if needed.Generated imports MUST match the project mode:
import { CrudService } from '@lenne.tech/nest-server';import { CrudService } from '../../../core'; (relative depth varies by file location)| File (in node_modules/@lenne.tech/nest-server/) | When to Read |
|---------------------------------------------------|-------------|
| CLAUDE.md | Start of any backend task — framework rules and architecture |
| FRAMEWORK-API.md | Quick API reference — all interfaces, method signatures |
| src/core.module.ts | Module registration, CoreModule.forRoot() parameters |
| src/core/common/interfaces/server-options.interface.ts | ALL config interfaces (IServerOptions, IBetterAuth, ICoreModuleOverrides) |
| src/core/common/interfaces/service-options.interface.ts | ServiceOptions interface for service method calls |
| src/core/common/services/crud.service.ts | CrudService base class — ALL services extend this |
| src/core/modules/*/INTEGRATION-CHECKLIST.md | Integration steps when extending core modules |
| src/core/modules/*/README.md | Per-module documentation and usage |
| docs/REQUEST-LIFECYCLE.md | Complete request flow, interceptors, decorators |
| .claude/rules/ | 11 rule files (architecture, security, testing, modules, etc.) |
this.findOne(), this.find(), this.update()) over direct model access (this.someModel.findOne(), mainDbModel.find()) — direct model access only with comment explaining whycurrentUser)ErrorCode from src/server/common/errors/project-errors.ts — zero raw-string throw new XxxException('...') outside testsFRAMEWORK-API.md for quick overview of available interfaces and methodsComplete framework guide: reference/framework-guide.md
src/server/common/enums/Complete workflow: reference/workflow-process.md
ALL properties must be in alphabetical order in Model, Input, and Output files.
The @lenne.tech/nest-server includes a built-in permissions scanner that audits @Roles, @Restricted, and securityCheck() usage across all modules.
lt server permissions (generates MD/JSON/HTML report via AST scan — preferred)permissions: true in config.env.ts for a live dashboard at GET /permissionsThe scanner detects: missing class-level @Restricted, endpoints without @Roles, models without securityCheck(), unrestricted fields, and unrestricted methods. Use after creating new modules to verify decorator coverage.
ErrorCode keys (no raw string messages)lt server permissions --failOnWarnings)/lt-dev:backend:sec-review)Complete checklist: reference/verification-checklist.md
| Topic | File |
|-------|------|
| Permissions Report | Built-in: lt server permissions / GET /permissions |
| Service Health Check | reference/service-health-check.md |
| Framework Guide | reference/framework-guide.md |
| Configuration & Commands | reference/configuration.md |
| Specification Format | reference/reference.md |
| Examples | reference/examples.md |
| Workflow Process | reference/workflow-process.md |
| Description Management | reference/description-management.md |
| Security Rules | reference/security-rules.md |
| Error Handling (ErrorCode) | reference/error-handling.md |
| OWASP Checklist | reference/owasp-checklist.md |
| Declare Keyword Warning | reference/declare-keyword-warning.md |
| Quality Review | reference/quality-review.md |
| Verification Checklist | reference/verification-checklist.md |
| TypeScript Conventions | reference/typescript-conventions.md |
| AI Module Integration | reference/ai-module-integration.md |
| MCP Integration | reference/mcp-integration.md |
| Operation | Use Case |
|-----------|----------|
| goToDefinition | Find where a class/function/type is defined |
| findReferences | Find all usages of a symbol |
| hover | Get type info and documentation |
| documentSymbol | List all symbols in a file |
| workspaceSymbol | Search symbols across the project |
| goToImplementation | Find implementations of interfaces |
| incomingCalls / outgoingCalls | Analyze call dependencies |
development
Single source of truth for the lenne.tech fullstack production-readiness checklist. Defines the eight pillars (configuration & secrets, observability & logging, health & lifecycle, security hardening, data durability, resilience under load, deployment hygiene, runbook & rollback) with concrete file/line evidence requirements per pillar, severity classification (Critical / Major / Minor), and a canonical machine-parseable report block. Activates whenever an agent or command needs to gate a release on production-readiness — currently used by /lt-dev:production-ready, lt-dev:production-readiness-orchestrator, and the devops-reviewer (read-only). NOT for OWASP-style code-level security review (use security-reviewer). NOT for npm dependency audits (use maintaining-npm-packages).
development
Single source of truth for executing GitLab CI/CD pipelines locally with the same image, env vars, and service containers as the real runner — so pipeline failures are caught before push. Defines pipeline discovery (.gitlab-ci.yml + includes), per-job execution via gitlab-runner exec, service-container orchestration (Mongo, Redis, MailHog), env injection without secrets, cache/artifact handling, and a job-by-job verdict report. Also describes the GitHub Actions equivalent via act for projects that mirror to GitHub. Activates whenever an agent or command needs to validate that the CI pipeline will pass — currently used by /lt-dev:production-ready and lt-dev:production-readiness-orchestrator. NOT for running the local check script (use running-check-script). NOT for writing or refactoring CI configs (use the devops agent).
development
Single source of truth for designing, running, and interpreting k6 load tests against lenne.tech fullstack APIs. Defines installation paths (brew, docker, npm), the three canonical scenarios (smoke / load / soak), endpoint discovery from the generated SDK, realistic Better-Auth login flows, threshold defaults for ~10 concurrent users (p95 < 500ms, error rate < 1%, http_req_failed < 1%), result interpretation, and the optimisation ladder when the system fails (DB indices, query rewrites, caching, connection pool sizing, rate-limit relaxation, payload trimming). Activates whenever an agent or command needs to validate that the API is stable for ~10 concurrent users performing many actions in short time, or to detect performance regressions via k6. Currently used by /lt-dev:production-ready, lt-dev:production-readiness-orchestrator, and lt-dev:performance-reviewer. NOT for Lighthouse frontend performance (use a11y-reviewer). NOT for unit performance assertions (use the test runner directly).
tools
Migrates lenne.tech projects from the legacy jest+eslint+prettier toolchain to the current vitest+oxlint+oxfmt baseline used by nest-server-starter and nuxt-base-starter. Covers swc decoratorMetadata config, the @Prop union-type fix for SWC, supertest default-import correction, ESM/CJS interop, the Nitro PORT-vs-NITRO_PORT bug, ANSI escape stripping in workspace runners (lerna/nx), free-port logic for check-server-start.sh, the offers-pattern config.env.ts (NSC__-only + fail-fast + auto-derived appUrl), and the multi-phase check-envs.sh smoke test. Activates whenever someone is migrating an existing project to the new toolchain, debugging "Cannot determine a type for the X field" Mongoose errors, ERR_SOCKET_BAD_PORT crashes from check-server-start, or wants to align an existing project with the current starter conventions.