DTOs
The query-graphql
package leverages most decorators from @nestjs/graphql
and TypeGraphQL, with the exception of FilterableField
.
@FilterableField
The FilterableField
is very similar to the Field
from
TypeGraphQL, however it allows you to specify the fields that should be filterable when querying.
If you use the @nestjs/graphql Field
decorator it will not be exposed in the query type for the DTO.
Options
In addition to the normal field options you can also specify the following options
allowedComparisons
- An array of allowed comparisons. You can use this option to allow a subset of filter comparisons when querying through graphql.- This option is useful if the field is expensive to query on for certain operators, or your data source supports a limited set of comparisons.
filterRequired
- When set totrue
the field will be required whenever afilter
is used. Thefilter
requirement applies to allread
,update
, anddelete
endpoints that use afilter
.- The
filterRequired
option is useful when your entity has an index that requires a subset of fields to be used to provide certain level of query performance. - NOTE: When a field is a required in a filter the default
filter
option is ignored.
- The
filterOnly
- When set totrue
, the field will only appear asfilter
but isn't included as field inside theObjectType
.- This option is useful if you want to filter on foreign keys without resolving the relation but you don't want to have the foreign key show up as field in your query type for the DTO. This might be especially useful for federated relations
Example
In the following example we allow id
, title
, and completed
to be used in queries.
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Example - allowedComparisons
In the following example the allowedComparisons
option is demonstrated by restricting 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 { FilterableField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID, { allowedComparisons: ['eq', 'neq', 'in', 'notIn'] })
id!: string;
@FilterableField({ allowedComparisons: ['eq', 'like', 'notLike'] })
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Example - filterRequired
In the following example the filterRequired
option is applied to the completed
field, ensuring that all endpoints that use a filter will require a comparison on the completed
field.
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField({ filterRequired: true })
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Example - filterOnly
In the following example the filterOnly
option is applied to the assigneeId
field, which makes a query filterable
by the id of an assigned user but won't return the assigneeId
as field.
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@Relation('assignee', () => UserDTO)
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@FilterableField({ filterOnly: true })
assigneeId!: string;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
@IDField
By default nestjs-query
uses the default graphql ID
scalar, if you need to use a different graphql
scalar
type you can @IDField
decorator. nestjs-query
will scalar
type passed to the @IDField
for all auto-generated
query
and mutation
endpoints that rely on an input for the id
(e.g. findById
, updateOne
, deleteOne
).
The @IDField
uses the same options as @FilterableField
.
You may have seen @IDField
in various examples throughout the docs, this is because 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.
If you are using query-typegoose
there is a known "won't fix" bug with class-transformer
, where ObjectIds end up getting new values, instead of the hex value they should be. To remedy this, we have a special decorator called '@ObjectId`. So, DTO's should use this as follow:
import { Field, GraphQLISODateTime, ID, ObjectType } from '@nestjs/graphql'
import { CursorConnection, FilterableField, KeySet, ObjectId, QueryOptions } from '@ptc-org/nestjs-query-graphql'
import mongoose from 'mongoose'
import { AuthGuard } from '../../auth.guard'
import { SubTaskDTO } from '../../sub-task/dto/sub-task.dto'
import { TagDTO } from '../../tag/dto/tag.dto'
@ObjectType('TodoItem')
@KeySet(['id'])
@QueryOptions({ enableTotalCount: true })
@CursorConnection('subTasks', () => SubTaskDTO, { update: { enabled: true }, guards: [AuthGuard] })
@CursorConnection('tags', () => TagDTO, { guards: [AuthGuard], update: { enabled: true }, remove: { enabled: true } })
export class TodoItemDTO {
@ObjectId()
_id: mongoose.Types.ObjectId
Notice the last two lines of code.
Example
A common use case is to obscure an auto-incremented primary key.
In this example we'll do a simple version of that by declaring a new ID
scalar that will base64
encode all ids.
import { Scalar, CustomScalar } from '@nestjs/graphql';
import { Kind, ValueNode } from 'graphql';
@Scalar('CustomID')
export class CustomIDScalar implements CustomScalar<string, number> {
description = 'ID custom scalar type';
private idPrefix = 'id:';
parseValue(value: string): number {
// parse a `base64` encoded id from the client when provided as a variable
return parseInt(Buffer.from(value, 'base64').toString('utf8').replace(this.idPrefix, ''), 10);
}
serialize(value: number): string {
// serialize a number into the base64 representation
return Buffer.from(`${this.idPrefix}${value}`, 'utf8').toString('base64');
}
parseLiteral(ast: ValueNode): number | null {
// parse a `base64` encoded id from the client when hardcoded into the query
if (ast.kind === Kind.STRING) {
return this.parseValue(ast.value);
}
return null;
}
}
Now lets register our CustomID
scalar with nestjs
.
import { Module } from '@nestjs/common';
import { CustomIDScalar } from './custom-id.scalar';
@Module({
providers: [CustomIDScalar],
})
export class CommonModule {}
Once your CustomIDScalar
is registered you can use it in your DTOS
.
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, GraphQLISODateTime } from '@nestjs/graphql';
import { CustomIDScalar } from '../../common/custom-id.scalar';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => CustomIDScalar)
id!: string;
@FilterableField()
title!: string;
@FilterableField({ nullable: true })
description?: string;
@FilterableField()
completed!: boolean;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
created!: Date;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
updated!: Date;
}
Now all graphql
endpoints that need to use an id
to query or mutate a TodoItem
will use the CustomIDScalar
type for the input.
Example - Disable Filtering
If you want to disable filtering and sorting on the id
field you can use the disableFilter
option.
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, GraphQLISODateTime } from '@nestjs/graphql';
import { CustomIDScalar } from '../../common/custom-id.scalar';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => CustomIDScalar, { disableFilter: true })
id!: string;
@FilterableField()
title!: string;
@FilterableField({ nullable: true })
description?: string;
@FilterableField()
completed!: boolean;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
created!: Date;
@FilterableField(() => GraphQLISODateTime, { filterOnly: true })
updated!: Date;
}
@QueryOptions
The @QueryOptions
decorator can be used to override any defaults for querying functionality such as sorting,
filtering, paging strategy, etc.
Setting a default Filter
When querying the default filter
is empty. You can specify a default filter by using the QueryOptions
decorator on
your DTO option.
The default filter is only used when a filter is not provided in a query.
In this example we specify the default filter to be completed IS TRUE
.
import { FilterableField, IDField, QueryOptions } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ defaultFilter: { completed: { is: true } } })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Result Page Size
By default all results will be limited to 10 records.
To override the default you can override the default page size by setting the defaultResultSize
option.
In this example we specify the defaultResultSize
to 5 which means if a page size is not specified 5 results will be
returned.
import { FilterableField, IDField, QueryOptions } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ defaultResultSize: 5 })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Limiting Results Size
By default the max number records that can be returned is 50
.
To override the default you can override the following options specifying the maxResultSize
option.
You can disable maxResultSize
by setting the option to -1
.
In this example we specify limit the max number of records an end user can request to 20.
import { FilterableField, IDField, QueryOptions } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ maxResultsSize: 20 })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Paging Strategy
By default nestjs-query
uses a cursor
based paging strategy and returns a connection for all query many endpoints.
For a more in-depth overview of paging check out the paging docs
You can override the default pagingStrategy
to one of the following alternatives
OFFSET
- sets paging to allowlimit
andoffset
fields, and returns anOffsetConnection
.NONE
- turn off all paging and always return anArrayConnection
.
When using the OFFSET
strategy your the paging arguments for a many query will accept a limit
and/or offset
.
This will also change the return type from a CursorConnection
to an OffsetConnection
.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ pagingStrategy: PagingStrategies.OFFSET })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
To disable paging entirely you can use the NONE
pagingStrategy
. When using NONE
an ArrayConnection
will be returned.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ pagingStrategy: PagingStrategies.NONE })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Paging with Total Count
This section ONLY applies to CURSOR
and OFFSET
connections.
Enabling totalCount
can be expensive. If your table is large the totalCount
query may be expensive, use with caution.
The totalCount
field is not eagerly fetched. It will only be executed if the field is queried from the client.
When using the CURSOR
(the default) or OFFSET
paging strategy you have the option to expose a totalCount
field to
allow clients to fetch a total count of records in a connection.
To enable the totalCount
field for connections set the enableTotalCount
option to true
using the
@QueryOptions
decorator.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ enableTotalCount: true })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
When setting enableTotalCount
to true
you will be able to query for totalCount
on cursor
or offset connections
- GraphQL
- Response
{
todoItems {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
title
description
}
}
}
}
{
"data": {
"todoItems": {
"totalCount": 5,
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "YXJyYXljb25uZWN0aW9uOjA=",
"endCursor": "YXJyYXljb25uZWN0aW9uOjQ="
},
"edges": [
{
"node": {
"id": "1",
"title": "Create Nest App",
"description": null
}
},
{
"node": {
"id": "2",
"title": "Create Entity",
"description": null
}
},
{
"node": {
"id": "3",
"title": "Create Entity Service",
"description": null
}
},
{
"node": {
"id": "4",
"title": "Add Todo Item Resolver",
"description": null
}
},
{
"node": {
"id": "5",
"title": "How to create item With Sub Tasks",
"description": null
}
}
]
}
}
}
Default Sort
When querying the default is based on the persistence layer. You can override the default by providing the defaultSort
option.
In this example we specify the default sort to be by title
.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { SortDirection } from '@ptc-org/nestjs-query-core';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ defaultSort: [{ field: 'title', direction: SortDirection.ASC }] })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Allowed Boolean Expressions
When filtering you can provide and
and or
expressions to provide advanced filtering. You can turn off either by
using the allowedBooleanExpressions
option.
In this example we will only allow and
expressions.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ allowedBooleanExpressions: ['and'] })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
To turn off all boolean expressions you can set allowedBooleanExpressions
to an empty array. This is useful if you
only allow filtering on certain fields and you want to disable all complex filtering.
In this example we will only allow eq
comparisons on the id field and disable all and
/or
boolean expressions.
import { FilterableField, IDField, QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
@ObjectType('TodoItem')
@QueryOptions({ allowedBooleanExpressions: ['and'] })
export class TodoItemDTO {
@IDField(() => ID, { allowedComparisons: ['eq'] })
id!: string;
@Field()
title!: string;
@Field()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
Generated filter-type depth
When querying the default filter is one level deep. You can specify the generated filter-type depth by
using the filterDepth
option.
n
-levels deep
To generate a filter-type that is n
-levels deep you can set the filterDepth
option to n
.
Each level will be generated as a new input type with the name from the previous level as a prefix.
In the following example it would generate TodoItemFilter
, TodoItemFilterSubTaskFilter
and TodoItemFilterTagFilter
.
import { FilterableField, IDField, QueryOptions } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
import { SubTaskDTO } from '../../sub-task/dto/sub-task.dto'
import { TagDTO } from '../../tag/dto/tag.dto'
@ObjectType('TodoItem')
// generate a filter-type that is 2 levels deep
@QueryOptions({ filterDepth: 2 })
@CursorConnection('subTasks', () => SubTaskDTO)
@CursorConnection('tags', () => TagDTO)
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}
infinite depth
To generate a filter-type with infinite depth you can set the filterDepth
option to Number.POSITIVE_INFINITY
.
This will generate flat filter-types for each related entity.
In the following example it would generate TodoItemDeepFilter
, SubTaskDeepFilter
and TagDeepFilter
.
import { FilterableField, IDField, QueryOptions } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
import { SubTaskDTO } from '../../sub-task/dto/sub-task.dto'
import { TagDTO } from '../../tag/dto/tag.dto'
@ObjectType('TodoItem')
// generate a filter-type with infinite depth
@QueryOptions({ filterDepth: Number.POSITIVE_INFINITY })
@CursorConnection('subTasks', () => SubTaskDTO)
@CursorConnection('tags', () => TagDTO)
export class TodoItemDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}