Schema-driven NestJS + GraphQL CRUD bindings for Prisma. Generates a thin per-model resolver / service / module that delegates heavy lifting to a shared runtime package, so updates ship without forcing you to regenerate everything.
v1 generated ~10+ files per model (resolver, service, module, args, inputs, outputs, model, …). v2 collapses resolver+service+module into a single <model>.crud.ts and moves shared behavior (error mapping, soft-delete, DataLoader, common filters) into nestjs-prisma-graphql-crud-gen-runtime. The result:
- ~80% fewer generated files
- Library updates land via runtime bumps — no re-generation required for bug fixes
- Cross-cutting concerns are correct every time (validation, error mapping, soft-delete) — the deterministic edge over hand-rolled or AI-generated scaffolds
- Node.js >= 20
- Prisma >= 7.x
- NestJS >= 10,
@nestjs/graphql>= 12
yarn add nestjs-prisma-graphql-crud-gen nestjs-prisma-graphql-crud-gen-runtimeIn schema.prisma:
generator nestjs_graphql_crud {
provider = "nestjs-prisma-graphql-crud-gen"
output = "../generated"
}In your NestJS root module, provide PRISMA_CLIENT for the runtime to consume:
import { PRISMA_CLIENT } from 'nestjs-prisma-graphql-crud-gen-runtime'
import { PrismaService } from './prisma.service' // your own PrismaService
@Module({
providers: [PrismaService, { provide: PRISMA_CLIENT, useExisting: PrismaService }],
exports: [PRISMA_CLIENT],
})
export class PrismaModule {}Then run:
npx prisma generateSet on the generator block in schema.prisma:
generator nestjs_graphql_crud {
provider = "nestjs-prisma-graphql-crud-gen"
output = "../generated"
useNormalizedNaming = "true" # default; "Users" model -> GraphQL "User"
emitValidation = "false" # class-validator decorators on inputs
emitDataLoader = "false" # per-relation DataLoader N+1 helpers
emitFederation = "false" # Apollo Federation v2 @key directives
emitOnly = "models,crud,inputs,outputs,enums" # subset to emit
runtimeImportPath = "nestjs-prisma-graphql-crud-gen-runtime"
}Annotate the schema to opt into v2 behaviors. Place them on the line above the field or model.
| Directive | Target | Effect |
|---|---|---|
/// @HideField |
field | Hide field from both GraphQL input and output |
/// @HideField({ input: true, output: false }) |
field | Selective hiding |
/// @ReadOnly |
field | Skip in Create / Update input types |
/// @SoftDelete |
model | delete -> update({ deletedAt: now() }); find queries auto-filter deletedAt IS NULL |
/// @SoftDelete({ field: "archivedAt" }) |
model | Use a non-default soft-delete field |
/// @Crud(only: ["findMany", "findUnique"]) |
model | Emit only the listed CRUD operations |
/// @Crud(except: ["delete", "deleteMany"]) |
model | Emit all CRUD ops except listed |
/// @Validate({ minLength: 3, isEmail: true }) |
field | class-validator decorators on inputs |
/// @Auth(roles: ["admin"]) |
model | Emit @UseGuards + @Roles() on resolver |
Example:
/// @SoftDelete
model User {
id String @id @default(cuid())
/// @HideField({ output: true })
password String
/// @Validate({ isEmail: true })
email String @unique
/// @ReadOnly
createdAt DateTime @default(now())
deletedAt DateTime?
}For a model Users { ... } (note plural in Prisma), with useNormalizedNaming: true (the default):
generated/
├── common/
│ ├── inputs/ # Prisma-generated common inputs (StringFilter, ...)
│ └── outputs/ # AffectedRowsOutput, etc.
├── enums/
├── models/
│ └── user.model.ts # @ObjectType('User')
└── user/
├── user.args.ts # FindFirstUserArgs, CreateOneUserArgs, ...
├── user.crud.ts # UserResolver + UserService + UserModule
├── inputs/
└── outputs/
prisma.users (plural) is still used for the Prisma client delegate call inside the service. Only the GraphQL surface is singularized.
See MIGRATION.md.
See usage/ in this repository, and nestjs-graphql-starter.
See CONTRIBUTING.md. The repo uses Jest snapshot tests under packages/generator/__tests__/.
MIT