VOOZH about

URL: https://dev.to/tommasomeli/prisma-generator-nestjs-dto-pluggable-dtos-with-annotations-and-custom-generators-26d8

⇱ Prisma Generator NestJS DTO — pluggable DTOs with annotations and custom generators - DEV Community


If you build NestJS APIs on top of Prisma, you've probably felt the friction: your schema is the source of truth, but your DTOs, validation rules, and Swagger metadata live somewhere else — and they drift.

Most Prisma DTO generators solve the basics (Create / Update / Entity classes with decorators). That's useful, but the moment you need custom output — audit metadata, GraphQL types, RBAC manifests, your own validators — you're back to hand-written glue code.

@tommasomeli/prisma-generator-nestjs-dto is a Prisma generator built for that second phase. It emits the usual NestJS DTOs, then gets out of your way — with a plugin API, annotation-driven decorators, and a type-safe config file when the Prisma schema block isn't enough.

What you get out of the box

For every Prisma model User, run npx prisma generate and you get:

src/generated/nestjs-dto/
 user/
 user.entity.ts
 create-user.dto.ts
 update-user.dto.ts
 index.ts
 index.ts

Each file ships with:

  • class-validator decorators (@IsString, @IsOptional, …)
  • @nestjs/swagger metadata (@ApiProperty, @ApiHideProperty, …)
  • Self-contained imports — relation DTOs, @DtoOverrideType targets, and annotation arguments are resolved automatically

Configure it directly in schema.prisma:

generator nestjsDto {
 provider = "prisma-generator-nestjs-dto"
 output = "../src/generated/nestjs-dto"
 outputType = "class"
 outputStructure = "nestjs"
 fileNamingStrategy = "kebab"
 reExport = "true"
 classValidator = "true"
 swaggerDocs = "true"
 prettier = "true"
}

Install:

npm i -D @tommasomeli/prisma-generator-nestjs-dto

Annotations — control visibility without touching generated code

Triple-slash comments (///) above models and fields drive the built-in generators. No post-processing, no manual edits.

model User {
 id Int @id @default(autoincrement())
 email String @unique
 name String
 /// @DtoHidden
 passwordHash String
 /// @DtoReadOnly
 createdAt DateTime @default(now())
}
Annotation Effect
@DtoHidden Hide everywhere
@DtoReadOnly Exclude from Create and Update DTOs
@DtoEntityHidden Hide in the Entity (API response)
@DtoCreateHidden / @DtoUpdateHidden Hide in one DTO only
@DtoOverrideType(MyType) Override the TypeScript type (auto-imported)
@DtoIgnoreModel Skip the model entirely

You can also bind your own validators and decorators via config — more on that below.

The plugin system — when built-in DTOs aren't enough

The differentiator is extraGenerators: drop in any class extending BaseGenerator and it runs alongside the built-ins in the same pipeline.

A plugin receives:

  • The parsed model graph (with annotations on every field)
  • Resolved config (extraDecorators, extraValidators, extraImports, …)
  • Import-merging helpers (addImport, formatImports, getTemplate)

Here's a minimal plugin that reuses the built-in renderer to emit an Entity without class-validator:

import { isEntityHidden } from '@tommasomeli/prisma-generator-nestjs-dto';
import { BaseGenerator } from '@tommasomeli/prisma-generator-nestjs-dto';
import type { Field, File, Model } from '@tommasomeli/prisma-generator-nestjs-dto';

export default class EntityDtoGenerator extends BaseGenerator {
 filePrefix = '';
 fileSuffix = '.entity';
 classPrefix = '';
 classSuffix = '';

 async generate(): Promise<File[]> {
 return this.models.map((model) => {
 const filteredFields = model.fields.filter((f: Field) => !isEntityHidden(f));
 const processedModel: Model = { ...model, fields: filteredFields as Field[] };
 const outputPath = this.getPath(model);
 return {
 path: outputPath,
 content: this.getTemplate({ model: processedModel, classValidator: false, outputPath }),
 };
 });
 }
}

Lifecycle hooks

Plugins can hook the full run:

  • beforeAll(models) — mutate the shared model list before any generator runs (shared pre-pass)
  • afterAll(files) — append barrels, audit reports, or aggregated indexes after all generators finish

TypeScript plugins, no precompilation

Point extraGenerators at a .ts file and the generator loads it via jiti. No build step required for your plugin.

Real example: custom @Auditable annotation

The repo includes a runnable example under examples/blog/.

Schema — annotate models with a custom @Auditable name:

/// @Auditable("user_audit")
model User {
 id Int @id @default(autoincrement())
 email String @unique
 /// @DtoHidden
 passwordHash String
 posts Post[]
}

/// @Auditable("post_audit")
model Post {
 id Int @id @default(autoincrement())
 title String
 author User @relation(fields: [authorId], references: [id])
 authorId Int
}

Config — register the annotation and the plugin:

import { from, type GeneratorConfigFile } from '@tommasomeli/prisma-generator-nestjs-dto';

export default {
 extraAnnotations: ['Auditable'],
 extraGenerators: from('./generators/audit-generator.ts', ['AuditGenerator']),
} satisfies GeneratorConfigFile;

Output — alongside the usual DTOs, you get per-model audit metadata and an aggregated index:

generated/
 user/user.audit.ts
 post/post.audit.ts
 audit-index.ts ← emitted by AuditGenerator#afterAll

Type-safe external config

Prisma's generator block is great for simple flags, but it can't express nested objects or multi-line arrays. For anything richer, use a configFile:

generator nestjsDto {
 provider = "prisma-generator-nestjs-dto"
 output = "../generated"
 configFile = "../nestjs-dto.config.ts"
}

The from() helper validates paths and named exports at compile time (the import closure is never invoked at runtime):

import { from, fromNamespace, type GeneratorConfigFile } from '@tommasomeli/prisma-generator-nestjs-dto';

export default {
 extraValidators: from(() => import('src/common/validators'), ['IsUnique', 'IsStrongPassword']),
 extraDecorators: from(() => import('src/common/decorators'), ['Trim', 'Sanitize']),
 extraScalars: {
 Decimal: { ts: 'Decimal', from: 'decimal.js' },
 Json: { ts: 'MyJson', from: 'src/json', apiType: 'Object' },
 },
} satisfies GeneratorConfigFile;

Then wire annotations in the schema:

model User {
 /// @IsUnique()
 email String @unique
 /// @IsStrongPassword({ minLength: 10 })
 password String
 /// @Trim()
 name String
}

If a custom validator collides by name with a built-in (IsBoolean, ApiProperty, …), your module wins for that symbol only.

Optional runtime manifest

Enable emitManifest = "true" to get:

  • manifest.tsRecord<Prisma.ModelName, { primaryKey, entityFields, relations }> for select builders, audit middleware, RBAC field lists
  • model-entity-map.ts — type-only map from model names to Entity classes

Useful when you need schema-aware runtime logic without parsing Prisma DMMF yourself.

How it compares

This generator Typical Prisma NestJS DTO generators
Create / Update / Entity DTOs
Swagger + class-validator
Annotation-driven hide / readonly / type override partial
Pluggable sub-generators
Custom annotations for plugins
Override built-in imports by name
Type-safe external configFile
Optional runtime manifest

Try it

npm i -D @tommasomeli/prisma-generator-nestjs-dto
npx prisma generate

Issues and PRs are welcome — MIT licensed.

If this saves you time on your NestJS + Prisma stack, a ⭐ on the repo (or a coffee ☕) goes a long way.