vegardit / prisma-generator-nestjs-dto

Generates NestJS DTO classes from Prisma Schema
Apache License 2.0
279 stars 78 forks source link
maintainer-wanted nestjs openapi prisma-schema typescript

Project Status: Looking for new maintainers!

This project is looking for maintainers.

We are not using this package anymore ourselves, so we can no longer validate/review/test any incoming PRs anymore and ensure correct functionality.

If you are an experienced open source contributor and are interested in taking over maintenance, please open a GitHub issue and let's discuss how to proceed.

Prisma Generator NestJS DTO

Release License

  1. What is it?
  2. Usage
  3. Annotations
  4. Example
  5. Principles
  6. License

What is it?

Generates ConnectDTO, CreateDTO, UpdateDTO, 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.

These classes can also be used with the built-in ValidationPipe and Serialization.

Usage?

npm install --save-dev @vegardit/prisma-generator-nestjs-dto
generator nestjsDto {
  provider                        = "prisma-generator-nestjs-dto"
  output                          = "../src/generated/nestjs-dto"
  outputToNestJsResourceStructure = "false"
  exportRelationModifierClasses   = "true"
  reExport                        = "false"
  createDtoPrefix                 = "Create"
  updateDtoPrefix                 = "Update"
  dtoSuffix                       = "Dto"
  entityPrefix                    = ""
  entitySuffix                    = ""
  fileNamingStyle                 = "camel"
}

Parameters

All parameters are optional.

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())
}

Example

Prisma Schema ```prisma generator nestjsDto { provider = "prisma-generator-nestjs-dto" 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[] } ````
Generated results ```ts // src/question/dto/connect-question.dto.ts export class ConnectQuestionDto { id: string; } ```` ```ts // 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; } ``` ```ts // 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; } ``` ```ts // 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 Models 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.

ConnectDTOs are used for relation fields in CreateDTOs and UpdateDTOs.

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

annotations that generate corresponding input properties on CreateDTO and UpdateDTO (optional or required - depending on the nature of the relation).

When generating a Models CreateDTO class, field that meet any of the following conditions are omitted (order matters):

UpdateDTO

When generating a Models UpdateDTO class, field that meet any of the following conditions are omitted (order matters):

Entity

When generating a Models 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.

License

All files are released under the Apache License 2.0.