generating-nest-servers
Installation
SKILL.md
NestJS Server Development Expert
Gotchas
declarekeyword on Model properties kills decorators —declare name: stringtells TypeScript "this property exists but isn't emitted at runtime" — which means Mongoose/Typegoose decorators (@Prop,@Field) attached to it are lost. Always usename!: string(definite assignment) orname: string = ''(default value) on model fields. Tests pass silently in memory but production MongoDB writes drop the field.securityCheck()returningthisunchanged is a red flag — A trivialreturn 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.- Code that extends the framework follows four NestJS 12 rules — (1) A Core service override that declares its own constructor marks each optional parameter
@Optional()itself: NestJS 12 reads the flags from the class whose constructor runs, so an unmarkedmapper?: Xbecomes required, and where nothing provides it the boot fails withNest can't resolve dependencies of the XService (…, ?). A subclass without a constructor of its own keeps the parent's flags. (2) Import@nestjs/*symbols and types from the package root (import type { JwtSignOptions } from '@nestjs/jwt'), never from…/dist/…:@nestjs/{jwt,mongoose,passport,schedule,swagger,terminus}export only their root, so a deep path fails withTS2307orERR_PACKAGE_PATH_NOT_EXPORTED. A type the root does not export is derived from one it does (NonNullable<ApiPropertyOptions['enum']>). (3) A GraphQL upload argument usesimport { GraphQLUpload } from '@lenne.tech/nest-server', nevergraphql-upload/GraphQLUpload.js: the scalar accepts onlyUploadinstances of its own copy, the multipart middleware is nest-server's, and with two copies every upload fails withUpload value invalid.. (4) No relative dynamicimport()in source: no specifier works everywhere (tscoutput needs./x.js, a ts-node source run does not map it to.ts); use a static import, or move what a cycle needs into a module of its own. - An ESM project (
"type": "module"in the API'spackage.json) needs five more rules, and its tests do not enforce the worst one — nest-server is an ES module from 12.0 on; a CommonJS project keeps writing code as before. In an ESM project: (1) every relative import names the emitted file,./x.jsor./dir/index.js(currentlt servergenerators write it there); (2) a JSON import carrieswith { type: 'json' }, and__dirnamebecomesimport.meta.dirname; (3) an interface or type alias in a decorated signature is imported withimport type(TS1272otherwise), never a class whose type drives dependency injection; (4) a CommonJS package whose names Node's analysis misses takes a type-only or default import:import { Connection } from 'mongoose'passes every vitest test and fails the production start withdoes not provide an export named 'Connection', so start the built server (check:server-start) after such an import; (5) a decorated property or constructor parameter typed by a class from an import cycle (two models referencing each other) is typedWrapperType<X>(nest-server export), otherwise the start throwsCannot access 'X' before initialization. Converting a project is its own step (lt server convert-esm), never a side effect of a feature (nest-server-updating). --controllerflag generates REST — even in GraphQL projects —lt server object <name> --controllerproduces a REST controller by default. For GraphQL projects, use--resolverinstead. The CLI does not auto-detect from the project's existing pattern.CrudServiceis the default — always extend it — New modules often get hand-rolled service classes instead ofextends CrudService. The framework's pagination, filtering, population, and permission integration all depend on CrudService inheritance. Hand-rolled services break/api/<entity>/findqueries silently for consumers.- Module models extend the project's
PersistenceModel, objects extendCoreModel—lt server modulegeneratesexport class X extends PersistenceModelwithimport { PersistenceModel } from '../../common/models/persistence.model'(the project base, itselfextends CorePersistenceModel), which carries id, timestamps and createdBy/updatedBy.lt server objectgeneratesextends CoreModelfor embedded, non-persisted types. A module model onCoreModelloses those persistence fields. - Alphabetical property order is enforced in reviews — Class fields, DTO properties, and model definitions are reviewed for alphabetical ordering. Generated code from
lt serverusually respects this; hand-edited additions often break it. Run a final pass before committing.
Ecosystem Context
Developers typically work in a Lerna fullstack monorepo created via lt fullstack init: