Skip to content

Resolvers

The @GqlResolver decorator turns a Relayer entity into a full GraphQL resolver. One decorator generates all queries, mutations, input types, and filter types.

Basic resolver

The simplest resolver needs just the entity class and a name:

import { GqlResolver, RelayerResolver } from '@relayerjs/nestjs-graphql';
@GqlResolver(UserEntity, { name: 'User' })
export class UsersResolver extends RelayerResolver<UserEntity, EM> {
constructor(usersService: UsersService) {
super(usersService);
}
}

This generates 4 queries and 3 mutations with all the input/output types. See the full list in Generated operations.

Config overview

@GqlResolver(PostEntity, {
// GraphQL type name prefix: "Post" -> PostWhereInput, CreatePostInput, etc.
name: 'Post',
// Lifecycle hooks class (NestJS injectable)
hooks: PostHooks,
// Control which queries exist and how they behave
queries: {
list: { name: 'posts', pagination: 'cursor' },
findById: { name: 'post' },
count: { name: 'postsCount' },
aggregate: { name: 'postsAggregate' },
},
// Control which mutations exist
mutations: {
createOne: { name: 'createPost' },
updateOne: { name: 'updatePost' },
deleteOne: { name: 'deletePost' },
},
// Hide fields from the entire GraphQL schema
fields: { exclude: ['password'] },
// Only these fields appear in WhereInput (filtering)
filterable: ['id', 'title', 'published', 'author'],
// Only these fields appear in OrderByInput (sorting)
orderable: ['id', 'title', 'createdAt'],
// Relation mutations (connect/disconnect/set)
relations: {
tags: true,
categories: { add: true, remove: true, set: false },
},
// Primary key config (usually auto-detected)
idField: 'id',
idType: 'number',
})

name

Type: string | Default: derived from entity class name

The prefix used for all generated GraphQL types. This is how you control the naming of everything:

@GqlResolver(PostEntity, { name: 'BlogPost' })

This generates: BlogPost (ObjectType), BlogPostWhereInput, BlogPostOrderByInput, CreateBlogPostInput, UpdateBlogPostInput, BlogPostCursorResult, BlogPostAggregate, etc.

Queries

Each query can be:

  • true (default) - enabled with auto-generated name
  • false - disabled, not in the schema
  • { name?, pagination? } - enabled with custom options

queries.list

The list query with pagination. One of three pagination strategies.

queries: {
list: { name: 'posts', pagination: 'cursor' },
}

Sub-options:

OptionTypeDefaultDescription
namestring{plural}GraphQL operation name
pagination'cursor' | 'offset' | 'cursor-edges''cursor'Pagination strategy

Pagination modes:

'cursor' (default) - flat items array with cursor navigation. Simplest and most ergonomic.

posts(first: Int, after: String, where: ..., orderBy: ...): PostCursorResult!
type PostCursorResult {
items: [Post!]!
pageInfo: PageInfo!
totalCount: Int
}

'offset' - classic limit/offset. Familiar for REST. Degrades on large datasets (the database still scans and discards offset rows) - fine for small-to-medium tables, use cursor pagination for anything resembling an infinite scroll over a large collection.

posts(limit: Int, offset: Int, where: ..., orderBy: ...): PostListResult!
type PostListResult {
items: [Post!]!
totalCount: Int!
hasMore: Boolean!
}

'cursor-edges' - Relay-style connections with edges and per-node cursors. Use when you need to resume pagination from an arbitrary item mid-page.

posts(first: Int, after: String, where: ..., orderBy: ...): PostConnection!
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int
}
type PostEdge {
node: Post!
cursor: String!
}

You can also import the Pagination const for type-safe mode selection:

import { Pagination } from '@relayerjs/nestjs-graphql';
queries: {
list: { pagination: Pagination.CursorEdges },
}

queries.findById

Find a single entity by its primary key.

queries: {
findById: { name: 'post' },
}
OptionTypeDefaultDescription
namestring{singular}GraphQL operation name

Generates:

post(id: ID!): Post

Returns null if not found (throws NotFoundException internally).

queries.count

Count records matching a filter.

queries: {
count: { name: 'postsCount' },
}
OptionTypeDefaultDescription
namestring{plural}CountGraphQL operation name

Generates:

postsCount(where: PostWhereInput): Int!

queries.aggregate

Aggregation with groupBy, count, sum, avg, min, max.

queries: {
aggregate: { name: 'postsAggregate' },
}
OptionTypeDefaultDescription
namestring{plural}AggregateGraphQL operation name

Generates:

postsAggregate(
where: PostWhereInput
groupBy: [String!]
_count: Boolean
_sum: PostAggregateNumericFieldsInput
_avg: PostAggregateNumericFieldsInput
_min: PostAggregateAllFieldsInput
_max: PostAggregateAllFieldsInput
): PostAggregate!

Mutations

Each mutation can be true, false, or { name? }.

mutations.createOne

mutations: {
createOne: { name: 'createPost' },
}
OptionTypeDefaultDescription
namestringcreate{Name}GraphQL operation name

Generates:

createPost(data: CreatePostInput!): Post!

mutations.updateOne

mutations: {
updateOne: { name: 'updatePost' },
}
OptionTypeDefaultDescription
namestringupdate{Name}GraphQL operation name

Generates:

updatePost(id: ID!, data: UpdatePostInput!): Post!

Throws NotFoundException if the entity does not exist.

mutations.deleteOne

mutations: {
deleteOne: { name: 'deletePost' },
}
OptionTypeDefaultDescription
namestringdelete{Name}GraphQL operation name

Generates:

deletePost(id: ID!): Post!

Throws NotFoundException if the entity does not exist.

Disabling operations

Set any operation to false:

@GqlResolver(PostEntity, {
mutations: { deleteOne: false },
queries: { aggregate: false },
})

Read-only resolver (no mutations):

@GqlResolver(PostEntity, {
mutations: { createOne: false, updateOne: false, deleteOne: false },
})

Generated operations

For name: 'Post', this is the full default output:

TypeOperationDescription
QuerypostsCursor-paginated list
Querypost(id: ID!)Find by ID
QuerypostsCount(where: ...)Count matching records
QuerypostsAggregate(...)Aggregation with groupBy
MutationcreatePost(data: ...)Create one
MutationupdatePost(id: ID!, ...)Update one
MutationdeletePost(id: ID!)Delete one

Naming pattern:

OperationPatternExample
List{plural}posts
Find{singular}post
Count{plural}CountpostsCount
Aggregate{plural}AggregatepostsAggregate
Createcreate{Name}createPost
Updateupdate{Name}updatePost
Deletedelete{Name}deletePost

fields

Type: { include?: string[], exclude?: string[] }

Controls which entity fields appear in the GraphQL schema. Affects the ObjectType, CreateInput, UpdateInput, WhereInput, and OrderByInput.

Exclude (blacklist)

@GqlResolver(UserEntity, {
fields: { exclude: ['password', 'refreshToken'] },
})

All fields are included except the listed ones.

Include (whitelist)

@GqlResolver(UserEntity, {
fields: { include: ['id', 'firstName', 'lastName', 'fullName', 'email'] },
})

Only the listed fields are included.

filterable

Type: string[] | Default: all fields

Restricts which fields appear in the generated WhereInput. By default all entity fields (including computed and derived) are filterable.

@GqlResolver(PostEntity, {
filterable: ['id', 'title', 'published', 'author'],
})

Only id, title, published, and author will be available for filtering. The rest won’t appear in PostWhereInput.

orderable

Type: string[] | Default: all fields

Restricts which fields appear in the generated OrderByInput. By default all entity fields are orderable.

@GqlResolver(PostEntity, {
orderable: ['id', 'title', 'createdAt'],
})

hooks

Type: class | Default: none

Lifecycle hooks class. Must extend RelayerHooks. Can be an NestJS injectable.

@GqlResolver(PostEntity, { hooks: PostHooks })

See Hooks & Context for details.

relations

Type: Record<string, boolean | { add?, remove?, set?, include? }> | Default: none

Enable mutation endpoints for managing many-to-many relations. Each relation can be fully enabled (true) or configured with selective operations and extra pivot columns.

@GqlResolver(PostEntity, {
relations: {
tags: true,
postCategories: { include: ['isPrimary'] },
},
})

Generates a per-relation input type and three mutations:

OperationMutation nameWhat it does
addaddTagsToPostConnect items (preserves existing)
removeremoveTagsFromPostDisconnect items
setsetTagsOnPostReplace all links

All mutations accept id: ID! and items: [...]!. Add and set use a per-relation input type ({Parent}{Relation}RelationInput) with _id plus any columns from include. Remove uses the shared RelationIdInput (only _id) since extras are meaningless for disconnect. Return type is RelationMutationResult { success: Boolean! }.

Sub-options:

OptionTypeDefaultDescription
addbooleantrueGenerate the add{Relation}To{Entity} mutation
removebooleantrueGenerate the remove{Relation}From{Entity} mutation
setbooleantrueGenerate the set{Relation}On{Entity} mutation
includereadonly string[][]Extra pivot columns to expose in the relation input

See Mutations for GraphQL examples, extra pivot columns, and hook integration.

idField / idType

Type: string / 'number' | 'string' | Default: 'id' / 'number'

Override primary key detection. Usually auto-detected from the entity.

@GqlResolver(PostEntity, {
idField: 'uuid',
idType: 'string',
})

This changes the id argument in findById, updateOne, and deleteOne to use the specified field and type.

Selection optimization

The resolver automatically reads the GraphQL info tree and translates the client’s selection set into a minimal Relayer select clause. Only the fields the client requests are fetched from the database. Relations are loaded only when selected.

totalCount in both pagination modes is lazy - the count query only runs if the client includes it in the selection.

Module registration

@Module({
imports: [
RelayerGraphqlModule.forRoot({
db,
schema,
entities: [UserEntity, PostEntity, CommentEntity],
defaultRelationLimit: 50,
}),
UsersModule,
PostsModule,
],
})
export class AppModule {}

Module options

OptionTypeRequiredDescription
dbDrizzle instanceYesDrizzle database connection
schemaRecord<string, ...>YesDrizzle schema (tables + relations)
entitiesRelayerEntityClass[]YesEntity classes to register
maxRelationDepthnumberNoMax depth for nested relation loading
defaultRelationLimitnumberNoDefault limit for relation arrays
graphqlRecord<string, ...>NoOverrides for GraphQLModule.forRoot()

The graphql overrides are spread into the Apollo driver config, so you can pass playground, introspection, cors, etc.