Aller au contenu

DTOs

DTOs (Data Transfer Objects) define the structure of data transferred between client and server. In Type GraphQL Query, DTOs are TypeScript classes decorated with GraphQL decorators.

This guide will help you understand:

  • ✅ How to define DTOs using decorators
  • ✅ The difference between @EnhancedField and @Field
  • ✅ How to configure field options (filterable, sortable, searchable, localizable)
  • ✅ How to use @IDField for ID fields
  • ✅ Best practices for DTO design

The type-graphql-query package leverages most decorators from type-graphql, with the addition of EnhancedField and IDField decorators that provide enhanced capabilities.

The EnhancedField decorator is similar to the Field decorator from TypeGraphQL, however it allows you to specify additional options for filtering, searching, sorting, and localization.

In addition to the normal field options from TypeGraphQL, you can also specify the following options:

Option Type Description
allowedComparisons string[] Array of allowed comparisons (e.g., ['eq', 'like', 'in'])
filterOnly boolean Field appears only in filters, not in the ObjectType
filterable boolean Include field in filter type (default: true)
sortable boolean Field can be used for sorting (default: true)
searchable boolean | SearchableFieldOptions Enable full-text search
localizable boolean Enable multi-language support

In the following example we allow id, title, and completed to be used in queries with filtering, sorting, and searching capabilities.

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@EnhancedField()
title!: string;
@EnhancedField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}

Restrict the comparisons that are allowed when filtering on certain fields.

For the id field only eq, neq, in, and notIn comparisons will be exposed in the schema.

The title field will only allow eq, like, and notLike.

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID, { allowedComparisons: ['eq', 'neq', 'in', 'notIn'] })
id!: string;
@EnhancedField({ allowedComparisons: ['eq', 'like', 'notLike'] })
title!: string;
@EnhancedField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}

The filterOnly option makes a field filterable but doesn’t return it as a field in queries.

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@EnhancedField()
title!: string;
@EnhancedField()
completed!: boolean;
@EnhancedField({ filterOnly: true })
assigneeId!: string;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}

Enable full-text search on fields. The title field uses default search options, while description uses custom search options.

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@EnhancedField({ searchable: true })
title!: string;
@EnhancedField({
searchable: {
unaccent: true,
simplified: true,
escapeHtml: true,
op: 'ILIKE',
},
})
description!: string;
@EnhancedField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}

Search Options:

Option Type Default Description
unaccent boolean false Remove accents from search text
simplified boolean false Use simplified text matching
escapeHtml boolean false Escape HTML characters
op 'ILIKE' | 'EQUAL' 'ILIKE' SQL operator to use

When searchable is enabled, a search parameter will be automatically added to queries:

query {
todoItems(search: "important task") {
id
title
description
}
}

Enable multi-language support for fields. The name and description fields support multiple languages.

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('Product')
export class ProductDTO {
@IDField(() => ID)
id!: string;
@EnhancedField({ localizable: true })
name!: string;
@EnhancedField({ localizable: true })
description!: string;
@EnhancedField()
price!: number;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}

When localizable is enabled, a locale parameter will be automatically added to queries and mutations:

query {
products(locale: "fr") {
id
name # Returns French translation
description # Returns French translation
price
}
}

You can combine multiple options to create powerful field configurations:

import { EnhancedField, IDField } from '@lib/type-graphql-query';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@lib/type-graphql';
@ObjectType('Article')
export class ArticleDTO {
@IDField(() => ID)
id!: string;
@EnhancedField({
searchable: {
unaccent: true,
op: 'ILIKE',
},
localizable: true,
sortable: true,
allowedComparisons: ['eq', 'like', 'notLike'],
})
title!: string;
@EnhancedField({
searchable: true,
localizable: true,
filterOnly: false,
})
content!: string;
@Field(() => GraphQLISODateTime)
publishedAt!: Date;
}

By default type-graphql-query uses the default GraphQL ID scalar. If you need to use a different GraphQL scalar type, you can use the @IDField decorator.

Recommendation

We recommend using @IDField by default. In the future if you need to change the type later on, it should be a trivial change to find all fields that use the @IDField decorator to update.

import { IDField } from '@lib/type-graphql-query';
import { ObjectType, ID } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@EnhancedField()
title!: string;
}

You can disable filtering for the ID field:

import { IDField } from '@lib/type-graphql-query';
import { ObjectType, ID } from '@lib/type-graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID, { filterable: false })
id!: string;
@EnhancedField()
title!: string;
}

Input DTOs are used for mutations. Always use @Field from type-graphql for input types, not @EnhancedField.

import { InputType, Field } from '@lib/type-graphql';
@InputType()
export class CreateProductDTO {
@Field()
name!: string;
@Field()
price!: number;
@Field({ nullable: true })
description?: string;
}

Important

Never use @EnhancedField in @InputType classes. Always use @Field from type-graphql.

Update DTOs typically make fields optional:

import { InputType, Field } from '@lib/type-graphql';
@InputType()
export class UpdateProductDTO {
@Field({ nullable: true })
name?: string;
@Field({ nullable: true })
price?: number;
}
Decorator Use Case Output Type Input Type
@IDField ID fields ✅ Yes ❌ No
@EnhancedField Fields needing filtering/sorting/search ✅ Yes ❌ No
@Field Regular fields ✅ Yes ✅ Yes

DTOs should represent the API contract, not your internal domain models.

Add validation to ensure data integrity:

import { MinLength, MaxLength } from 'class-validator';
@EnhancedField(() => String)
@MinLength(3)
@MaxLength(100)
name!: string;

Add descriptions to help API consumers:

@EnhancedField(() => String, { description: "Product name" })
name!: string;
  • Output Types: Use @EnhancedField for fields that need filtering, sorting, or searching
  • Input Types: Use @Field from type-graphql (not @EnhancedField)
  • ID Fields: Use @IDField for consistency

This ensures consistency across the API and makes future changes easier.

@ObjectType()
export class Product {
@IDField(() => ID)
id!: string;
@EnhancedField(() => String)
name!: string;
@EnhancedField(() => Number)
price!: number;
}
@ObjectType()
export class Article {
@IDField(() => ID)
id!: string;
@EnhancedField({ searchable: true, localizable: true })
title!: string;
@EnhancedField({ searchable: true, localizable: true })
content!: string;
}
@ObjectType()
export class OrderLine {
@IDField(() => ID)
id!: string;
@EnhancedField({ filterOnly: true })
orderId!: string;
@Relation('order', () => Order)
order!: Order;
}

Continue learning:

  1. 📊 Types - Learn about GraphQL type definitions
  2. ✏️ Mutations - Understand data modifications
  3. 🔍 Queries - Query operations with filtering and sorting
  4. 🔗 Relations - Manage relationships between entities