prisma-types-generator
v1.3.2
Published
Generates DTO and Entity classes from Prisma Schema for NestJS
Downloads
115
Readme
Prisma Types Generator
What is it?
Generates ConnectDTO
, CreateDTO
, UpdateDTO
, DTO
, and Entity
classes for models in your Prisma Schema. This is useful if you want to leverage OpenAPI in your NestJS application - but also helps with GraphQL resources as well). NestJS Swagger requires input parameters in controllers to be described through classes because it leverages TypeScript's emitted metadata and Reflection
to generate models/components for the OpenAPI spec. It does the same for response models/components on your controller methods.
Output files are formatted with prettier using project config. Every output model is configurable, so you can choose what models to generate.
These classes can also be used with the built-in ValidationPipe and Serialization.
Usage?
npm install --save-dev prisma-types-generator
generator types {
provider = "prisma-types-generator"
output = "../src/generated/types"
outputToNestJsResourceStructure = "false"
flatResourceStructure = "false"
exportRelationModifierClasses = "true"
reExport = "false"
createDtoPrefix = "Create"
updateDtoPrefix = "Update"
dtoSuffix = "Dto"
entityPrefix = ""
entitySuffix = ""
classValidation = "false"
fileNamingStyle = "camel"
noDependencies = "false"
excludeEntity = "false"
excludePlainDto = "false"
excludeCreateDto = "false"
excludeUpdateDto = "false"
excludeConnectDto = "false"
definiteAssignmentAssertion = "false"
}
Parameters
All parameters are optional.
- [
output
]: (default:"../src/generated/types"
) - output path relative to yourschema.prisma
file - [
outputToNestJsResourceStructure
]: (default:"false"
) - writesdto
s andentities
to subfolders aligned with NestJS CRUD generator. Resource module name is derived from lower-cased model name inschema.prisma
- [
flatResourceStructure
]: (default:"false"
) - IfoutputToNestJsResourceStructure
istrue
, subfoldersdto
s andentities
are created within the resource folder. Setting this totrue
will flatten the hierarchy. - [
exportRelationModifierClasses
]: (default:"true"
) - Should extra classes generated for relationship field operations on DTOs be exported? - [
reExport
]: (default:false
) - Should an index.ts be created for every folder? - [
createDtoPrefix
]: (default:"Create"
) - phrase to prefix everyCreateDTO
class with - [
updateDtoPrefix
]: (default:"Update"
) - phrase to prefix everyUpdateDTO
class with - [
dtoSuffix
]: (default:"Dto"
) - phrase to suffix everyCreateDTO
andUpdateDTO
class with - [
entityPrefix
]: (default:""
) - phrase to prefix everyEntity
class with - [
entitySuffix
]: (default:""
) - phrase to suffix everyEntity
class with - [
excludeEntity
]: (default:"false"
) - excludeEntity
generation - [
excludePlainDto
]: (default:"false"
) - excludeDTO
generation - [
excludeCreateDto
]: (default:"false"
) - excludeCreateDTO
generation - [
excludeUpdateDto
]: (default:"false"
) - excludeUpdateDTO
generation - [
excludeConnectDto
]: (default:"false"
) - excludeConnectDTO
generation - [
fileNamingStyle
]: (default:"camel"
) - How to name generated files. Valid choices are"camel"
,"pascal"
,"kebab"
and"snake"
. - [
classValidation
]: (default:"false"
) - Add validation decorators fromclass-validator
. Not compatible withnoDependencies = "true"
andoutputType = "interface"
. - [
noDependencies
]: (default:"false"
) - Any imports and decorators that are specific to NestJS and Prisma are omitted, such that there are no references to external dependencies. This is useful if you want to generate appropriate DTOs for the frontend. - [
outputType
]: (default:"class"
) - Output the DTOs asclass
or asinterface
.interface
should only be used to generate DTOs for the frontend. - [
definiteAssignmentAssertion
]: (default:"false"
) - Use definite assignment assertion operator (!) on optional fields. Enable this if you are using TypeScript'sstrictPropertyInitialization
option.
Annotations
Annotations provide additional information to help this generator understand your intentions. They are applied as tripple slash comments to a field node in your Prisma Schema. You can apply multiple annotations to the same field.
model Post {
/// @DtoCreateOptional
/// @DtoUpdateHidden
createdAt DateTime @default(now())
}
- @DtoReadOnly - omits field in
CreateDTO
andUpdateDTO
- @DtoEntityHidden - omits field in
Entity
- @DtoCreateOptional - adds field optionally to
CreateDTO
- useful for fields that would otherwise be omitted (e.g.@id
,@updatedAt
) - @DtoUpdateOptional- adds field optionally to
UpdateDTO
- useful for fields that would otherwise be omitted (e.g.@id
,@updatedAt
) - @DtoRelationRequired - marks relation required in
Entity
although it's optional in PrismaSchema - useful when you don't want (SQL)ON DELETE CASCADE
behavior - but your logical data schema sees this relation as required
(Note: becomes obsolete once referentialActions are released and stable) - @DtoRelationCanCreateOnCreate - adds create option on a relation field in the generated
CreateDTO
- useful when you want to allow to create related model instances - @DtoRelationCanConnectOnCreate - adds connect option on a relation field in the generated
CreateDTO
- useful when you want/need to connect to an existing related instance - @DtoRelationCanCreateOnUpdate - adds create option on a relation field in the generated
UpdateDTO
- useful when you want to allow to create related model instances - @DtoRelationCanConnectOnUpdate - adds connect option on a relation field in the generated
UpdateDTO
- useful when you want/need to connect to an existing related instance
Schema Object annotations
With @nestjs/swagger
, you can generate an API specification from code.
Routes, request bodies, query parameters, etc., are annotated with special decorators.
Properties can be annotated with the @ApiProperty()
decorator to add schema object information.
They are partially added at runtime, which will then include type
, nullable
, etc.
But additional information, such as description, need to be added manually.
If using a generator like this, any custom @ApiProperty()
annotation would be overridden when updating the DTOs.
To enhance a field with additional schema information, add the schema property prefixed with @
to the comment section above the field.
Currently, following schema properties are supported:
description
minimum
maximum
exclusiveMinimum
exclusiveMaximum
minLength
maxLength
minItems
maxItems
example
Additionally, special data types are inferred and annotated as well:
Int: { type: 'integer', format: 'int32' }
BigInt: { type: 'integer', format: 'int64' }
Float: { type: 'number', format: 'float' }
Decimal: { type: 'number', format: 'double' }
DateTime: { type: 'string', format: 'date-time' }
Example
This example using @description
and @minimum
tags
/// @description Number of reviews
/// @minimum 9
reviewCount Int? @default(0)
will generate @ApiProperty()
decorator with description
and minimum
as properties as well as type
and format
to specify the data type.
If a default value is specified, it gets also added to the decorator.
@ApiProperty({
description: 'Number of reviews',
minimum: 9,
type: 'integer',
format: 'int32',
default: 0
})
reviewCount: number | null;
Validation decorators
If classValidation = "true"
, the generator will add validation decorators from class-validator to each field of CreateDTO
and UpdateDTO
that can then be used in combination with the NestJS ValidationPipe
(see NestJS Auto-validation).
Some decorators will be inferred from the field's attributes.
If the field is optional, it will add @IsOptional()
, otherwise @IsNotEmpty()
.
If the field is a list, it will add @IsArray()
.
Type validators are inferred from the field's type:
String
→@IsString()
Boolean
→@IsBoolean()
Int
→@IsInt()
BigInt
→@IsInt()
Float:
→@IsNumber()
Decimal:
→@IsNumber()
DateTime
→@IsRFC3339()
Json
→@IsJSON()
All remaining validation decorators can be added in the comment/documentation section above the field. The parentheses can be omitted if not passing a value.
Example
Prisma Schema
/// @Contains('Product')
name String @db.VarChar(255)
reviewCount Int? @default(0)
/// @ArrayNotEmpty
tags String[]
Generated output
@IsNotEmpty()
@IsString()
@Contains('Product')
name: string;
@IsOptional()
@IsInt()
reviewCount?: number;
@IsNotEmpty()
@IsArray()
@ArrayNotEmpty()
tags: string[];
Example
generator nestjsDto {
provider = "prisma-types-generator"
output = "../src"
outputToNestJsResourceStructure = "true"
}
model Question {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
/// @DtoReadOnly
createdAt DateTime @default(now())
/// @DtoRelationRequired
createdBy User? @relation("CreatedQuestions", fields: [createdById], references: [id])
createdById String? @db.Uuid
updatedAt DateTime @updatedAt
/// @DtoRelationRequired
updatedBy User? @relation("UpdatedQuestions", fields: [updatedById], references: [id])
updatedById String? @db.Uuid
/// @DtoRelationRequired
/// @DtoRelationCanConnectOnCreate
category Category? @relation(fields: [categoryId], references: [id])
categoryId String? @db.Uuid
/// @DtoCreateOptional
/// @DtoRelationCanCreateOnCreate
/// @DtoRelationCanConnectOnCreate
/// @DtoRelationCanCreateOnUpdate
/// @DtoRelationCanConnectOnUpdate
tags Tag[]
title String
content String
responses Response[]
}
// src/question/dto/connect-question.dto.ts
export class ConnectQuestionDto {
id: string;
}
// src/question/dto/create-question.dto.ts
import { ApiExtraModels } from '@nestjs/swagger';
import { ConnectCategoryDto } from '../../category/dto/connect-category.dto';
import { CreateTagDto } from '../../tag/dto/create-tag.dto';
import { ConnectTagDto } from '../../tag/dto/connect-tag.dto';
export class CreateQuestionCategoryRelationInputDto {
connect: ConnectCategoryDto;
}
export class CreateQuestionTagsRelationInputDto {
create?: CreateTagDto[];
connect?: ConnectTagDto[];
}
@ApiExtraModels(
ConnectCategoryDto,
CreateQuestionCategoryRelationInputDto,
CreateTagDto,
ConnectTagDto,
CreateQuestionTagsRelationInputDto,
)
export class CreateQuestionDto {
category: CreateQuestionCategoryRelationInputDto;
tags?: CreateQuestionTagsRelationInputDto;
title: string;
content: string;
}
// src/question/dto/update-question.dto.ts
import { ApiExtraModels } from '@nestjs/swagger';
import { CreateTagDto } from '../../tag/dto/create-tag.dto';
import { ConnectTagDto } from '../../tag/dto/connect-tag.dto';
export class UpdateQuestionTagsRelationInputDto {
create?: CreateTagDto[];
connect?: ConnectTagDto[];
}
@ApiExtraModels(CreateTagDto, ConnectTagDto, UpdateQuestionTagsRelationInputDto)
export class UpdateQuestionDto {
tags?: UpdateQuestionTagsRelationInputDto;
title?: string;
content?: string;
}
// src/question/entities/question.entity.ts
import { User } from '../../user/entities/user.entity';
import { Category } from '../../category/entities/category.entity';
import { Tag } from '../../tag/entities/tag.entity';
import { Response } from '../../response/entities/response.entity';
export class Question {
id: string;
createdAt: Date;
createdBy?: User;
createdById: string;
updatedAt: Date;
updatedBy?: User;
updatedById: string;
category?: Category;
categoryId: string;
tags?: Tag[];
title: string;
content: string;
responses?: Response[];
}
Principles
Generally we read field properties from the DMMF.Field
information provided by @prisma/generator-helper
. Since a few scenarios don't become quite clear from that, we also check for additional annotations (or decorators
) in a field's documentation
(that is anything provided as a tripple slash comments for that field in your prisma.schema
).
Initially, we wanted DTO
classes to implement Prisma.<ModelName><(Create|Update)>Input
but that turned out to conflict with required relation fields.
ConnectDTO
This kind of DTO represents the structure of input-data to expect from 'outside' (e.g. REST API consumer) when attempting to connect
to a model through a relation field.
A Model
s ConnectDTO
class is composed from a unique'd list of isId
and isUnique
scalar fields. If the ConnectDTO
class has exactly one property, the property is marked as required. If there are more than one properties, all properties are optional (since setting a single one of them is already sufficient for a unique query) - you must however specify at least one property.
ConnectDTO
s are used for relation fields in CreateDTO
s and UpdateDTO
s.
CreateDTO
This kind of DTO represents the structure of input-data to expect from 'outside' (e.g. REST API consumer) when attempting to create
a new instance of a Model
.
Typically the requirements for database schema differ from what we want to allow users to do.
As an example (and this is the opinion represented in this generator), we don't think that relation scalar fields should be exposed to users for create
, update
, or delete
activities (btw. TypeScript types generated in PrismaClient exclude these fields as well). If however, your schema defines a required relation, creating an entity of that Model would become quite difficult without the relation data.
In some cases you can derive information regarding related instances from context (e.g. HTTP path on the rest endpoint /api/post/:postid/comment
to create a Comment
with relation to a Post
). For all other cases, we have the
@DtoRelationCanCreateOnCreate
@DtoRelationCanConnectOnCreate
@DtoRelationCanCreateOnUpdate
@DtoRelationCanConnectOnUpdate
annotations that generate corresponding input properties on CreateDTO
and UpdateDTO
(optional or required - depending on the nature of the relation).
When generating a Model
s CreateDTO
class, field that meet any of the following conditions are omitted (order matters):
isReadOnly
OR is annotated with@DtoReadOnly
(Note: this apparently includes relation scalar fields)- field represents a relation (
field.kind === 'object'
) and is not annotated with@DtoRelationCanCreateOnCreate
or@DtoRelationCanConnectOnCreate
- field is a relation scalar
- field is not annotated with
@DtoCreateOptional
AND
UpdateDTO
When generating a Model
s UpdateDTO
class, field that meet any of the following conditions are omitted (order matters):
- field is annotated with
@DtoUpdateOptional
isReadOnly
OR is annotated with@DtoReadOnly
(Note: this apparently includes relation scalar fields)isId
(id fields are not supposed to be updated by the user)- field represents a relation (
field.kind === 'object'
) and is not annotated with@DtoRelationCanCreateOnUpdate
or@DtoRelationCanConnectOnUpdate
- field is a relation scalar
- field is not annotated with
@DtoUpdateOptional
AND
Entity
When generating a Model
s Entity
class, only fields annotated with @DtoEntityHidden
are omitted.
All other fields are only manipulated regarding their isRequired
and isNullable
flags.
By default, every scalar field in an entity is required
meaning it doesn't get the TypeScript "optional member flag" ?
next to it's name. Fields that are marked as optional in PrismaSchema are treated as nullable
- meaning their TypeScript type is a union of field.type
and null
(e.g. string | null
).
Relation and relation scalar fields are treated differently. If you don't specifically include
a relation in your query, those fields will not exist in the response.
- every relation field is always optional (
isRequired = false
) - relations are nullable except when
- the relation field is a one-to-many or many-to-many (i.e. list) type (would return empty array if no related records found)
- the relation was originally flagged as required (
isRequired = true
) - the relation field is annotated with
@DtoRelationRequired
(do this when you mark a relation as optional in PrismaSchema because you don't want (SQL)ON DELETE CASCADE
behavior - but your logical data schema sees this relation as required)
- default values are not added to the
@ApiProperty()
decorator
DTO
The plain DTO
class is almost the same as Entity
with the difference that all relation fields are omitted.
This is useful if your response is the plain entity without any (optional) relations.