Skip to main content

Generatori dei DTO

Il sistema di gestione dei DTO permette di generare automaticamente le classi dei DTO, gli schemi di validazione di Yup, la documentazione di Swagger, a partire dalle Entità di TypeORM.

Comandi#

Per avviare la generazione dei DTO, utilizzare:

# Rigenera solo i DTO le cui entità risultano cambiate
npm run cli:dev -- generate:dtos

È possibile limitare la generazione solo ad alcuni file, indicando il nome del file dell'entità (anche parziale, i.e. "Contr" per "Contract.ts").

# Prenderà in considerazione `Contract.entity.ts`, `ContractReferent.entity.ts`, ecc.
npm run cli:dev -- generate:dtos --only Contract

In caso si vogliano rigenerare tutti i file, a prescindere dal fatto se siano cambiati o meno i file sorgenti (Entità, Dto delle query, ecc.), è possibile usare il flag --force

npm run cli:dev -- generate:dtos --force

Configurare le Entità#

Le entità devono avere il decoratore @Entity() di TypeORM o, in caso di embedded entities, @GenerateEmbedded()

@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
}

Gestire le relazioni#

Di default nei DTO non vengono inserite le proprietà relative alle relazioni OneToMany e ManyToMany.

Per generarle, è necessario aggiungere il decoratore @GenerateNested():

@Entity()
class Invoice {
/** Linea di Fatturazione */
@OneToMany(() => InvoiceLine, invoiceLine => invoiceLine.invoice)
@GenerateNested() // <--
lines!: InvoiceLine[];
}

In questo esempio, il DTO includerà la proprietà lines?: InvoiceLineDto[].

Metodi dell'entità sul Dto#

È possibile trasferire dei metodi definiti sulle entità sul DTO _"copiandoli". In questo modo possano essere utilizzati anche lato client.

Per farlo usare il decoratore @GenerateClient() sul metodo stesso, ad esempio:

class Invoice {
// ...
@GenerateClient()
isGreaterThanZero() {
return this.lines.some(l => l.amount.greaterThan(0));
}
}
Import

Non utilizzare moduli, classi e funzioni definite per essere eseguite solo lato server nel metodo, i.e. AppError, altrimenti lato client non funzionerà la compilazione o l'esecuzione.

Validazione#

Label#

È possibile specificare una label dedicata per il campo con il decorator @SchemaLabel('...')

@SchemaLabel('Fattura cartacea')
paper?: boolean;

Required#

È possibile impostare una proprietà come non richiesta con il decorator SchemaNotRequired()

@SchemaNotRequired()
id!: number;

Esclusione (sola lettura)#

È possibile rimuovere una proprietà dallo schema di Yup, e mantenerla soltanto nel DTO, ovvero nel caso in cui la proprietà sia di sola lettura.

@SchemaReadonly('Stato fattura')
state: InvoiceState;

Tutte le date con decoratore @PlatformColumnDate({ defaultToNow: true }) sono gestite automaticamente come Readonly.

Validatore personalizzato#

È possibile specificare qualsiasi regola di Yup per la personalizzazione della validazione con il decorator @SchemaValidate, ad esempio:

@SchemaValidate(schema => schema.positive().integer())
quantity?: number;

Utilizzo nei Controller#

Lato backend è necessario indicare quali DTO siano da convertire sia in richiesta (Query, Body) sia in risposta.

Per indicare i parametri in ingresso è possibile usare semplicemente @DtoQuery() al posto di @Query() o @DtoBody() al posto di @Body().

Validazione automatica

Tutti i parametri annotati con @DtoBody e @DtoQuery arrivano al metodo del controller già convertiti nella classe corretta e già validati con Yup.

Per la risposta invece usare @DtoResponse indicando il DTO in cui convertire la risposta

class InvoiceController {
@Put('/:id')
@DtoResponse(InvoiceDto)
async update(@IntParam('id') id: number, @DtoBody() input: InvoiceDto) {
// A questo punto `input` è già validato
const invoice = await this.invoiceRepo.findOneOrFail(id);
// ...
return invoice; // Viene convertito in un `InvoiceDto` per via di `@DtoResponse`
}
}

Query e altri DTO personalizzati#

Per i DTO delle Query (@Query() lato Nest) oppure per altri Body che non sono collegati a nessuna entità nello specifico, è possibile:

  1. Creare un file dedicato, i.e. in dto/query/InvoiceQueryDto.ts
  2. Creare una classe "template" senza il suffisso Dto e senza esportarla
  3. Annotarla con il decoratore @GenerateDto()

Una volta avviato il comando di generazione, verrà creata la classe del DTO e lo schema di Yup nello stesso file.

@GenerateDto()
class InvoiceQuery {
@SchemaPageNumber()
pageNumber!: number;
@SchemaPageSize()
pageSize!: number;
@SchemaLabel('Anno di emissione fattura')
issuedYear?: number;
}

Genererà una classe di DTO (e lo schema associato):

/**
* Rappresentazione DTO della classe InvoiceQuery
* Hash: 893c340f571e038e312b7d3421eee52e
*/
@ValidationSchema(() => InvoiceQuerySchema)
export class InvoiceQueryDto {
@ApiProperty({ type: Number, description: 'Numero di pagina' })
pageNumber!: number;
@ApiProperty({ type: Number, description: 'Dimensione pagina' })
pageSize!: number;
@ApiProperty({ type: Number, description: 'Anno di emissione fattura' })
issuedYear?: number | undefined;
// ...
}

Numero e Dimensione Pagina#

Per comodità, sono disponibili i decoratori @SchemaPageNumber() e @SchemaPageSize() per indicare i campi pageNumber e pageSize, che generano automaticamente l'etichetta e lo schema di validazione.

Aggiornato il da Leonardo Ascione