{"id":13847239,"url":"https://github.com/tkosminov/nestjs-graphql-easy","last_synced_at":"2025-03-21T07:30:29.858Z","repository":{"id":57687685,"uuid":"373865219","full_name":"tkosminov/nestjs-graphql-easy","owner":"tkosminov","description":"A library for NestJS that implements a dataloader (including for polymorphic relation) for graphql, as well as automatic generation of arguments for filters, sorting and pagination, and their processing in the dataloader.","archived":false,"fork":false,"pushed_at":"2024-07-12T05:32:00.000Z","size":1654,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-12-28T01:39:02.704Z","etag":null,"topics":["cursor-pagination","dataloader","filters","graphql","nestjs","ordering","pagination","polymorphic-relationships","typeorm"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tkosminov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-04T14:20:32.000Z","updated_at":"2024-11-22T12:58:05.000Z","dependencies_parsed_at":"2023-12-26T05:21:16.598Z","dependency_job_id":"1ac74ac4-c5af-4258-9b75-f212870bbc6e","html_url":"https://github.com/tkosminov/nestjs-graphql-easy","commit_stats":null,"previous_names":["timurrk/nestjs-graphql-easy"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkosminov%2Fnestjs-graphql-easy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkosminov%2Fnestjs-graphql-easy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkosminov%2Fnestjs-graphql-easy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tkosminov%2Fnestjs-graphql-easy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tkosminov","download_url":"https://codeload.github.com/tkosminov/nestjs-graphql-easy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244757080,"owners_count":20505324,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cursor-pagination","dataloader","filters","graphql","nestjs","ordering","pagination","polymorphic-relationships","typeorm"],"created_at":"2024-08-04T18:01:14.215Z","updated_at":"2025-03-21T07:30:29.524Z","avatar_url":"https://github.com/tkosminov.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# NestJS-GraphQL-Easy\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/nestjs-graphql-easy\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/nestjs-graphql-easy.svg\" alt=\"NPM Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/nestjs-graphql-easy\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/nestjs-graphql-easy.svg\" alt=\"Package License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/nestjs-graphql-easy\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/nestjs-graphql-easy.svg\" alt=\"NPM Downloads\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Overview\n\n- [NestJS-GraphQL-Easy](#nestjs-graphql-easy)\n  - [Overview](#overview)\n  - [Description](#description)\n  - [Introduction](#introduction)\n  - [Installation](#installation)\n  - [Note](#note)\n  - [Important!](#important)\n  - [Datasource](#datasource)\n  - [Dataloader (n + 1 problem solver)](#dataloader-n--1-problem-solver)\n    - [many](#many)\n    - [one-to-many](#one-to-many)\n    - [many-to-one](#many-to-one)\n    - [one-to-one](#one-to-one)\n    - [polymorphic](#polymorphic)\n  - [Filtering](#filtering)\n    - [Scalar](#scalar)\n  - [Ordering](#ordering)\n  - [Pagination](#pagination)\n  - [Cursor pagination](#cursor-pagination)\n  - [Permanent filters](#permanent-filters)\n\n## Description\n\nA library for NestJS that implements a dataloader (including for polymorphic relation) for graphql, as well as automatic generation of arguments for filters, sorting and pagination, and their processing in the dataloader.\n\n## Introduction\n\nWith this library you will be able to easily create complex queries\n\n```gql\n{\n  authors(\n    ORDER: { name: { SORT: ASC } }\n    PAGINATION: { page: 0, per_page: 10 }\n  ) {\n    id\n    name\n    gender\n    books(\n      WHERE: { is_private: { EQ: false } }\n      ORDER: { created_at: { SORT: DESC } }\n    ) {\n      id\n      author_id\n      title\n      created_at\n    }\n  }\n}\n```\n\n## Installation\n\n```bash\nnpm i nestjs-graphql-easy\n```\n\n## Note\n\n**This library requires**:\n* NestJS 9 or higher version\n* TypeORM 0.3 or higher version\n\n**A fully working example with all the functionality is located in the `src` folder**\n\n**The library itself is located in the `lib` folder**\n\n`If you have questions or need help, please create GitHub Issue in this repository `[https://github.com/tkosminov/nestjs-graphql-easy](https://github.com/tkosminov/nestjs-graphql-easy)\n\n## Important!\n\n1. The typeorm model and the graphql object must be the same class.\n2. Decorators `PolymorphicColumn`, `Column`, `Entity`, `CreateDateColumn`, `UpdateDateColumn`, `PrimaryColumn`, `PrimaryGeneratedColumn` from `typeorm` must be imported from `nestjs-graphql-easy`\n3. Decorators `Field` (only for columns from tables), `ObjectType`, `Query`, `Mutation`, `ResolveField` from `graphql` must be imported from `nestjs-graphql-easy`\n\n* Points 2 and 3 are caused by the fact that it is necessary to collect data for auto-generation of filters and sorts, as well as not to deal with casting the names `graphql field \u003c-\u003e class property \u003c-\u003e typeorm column` and `graphql object \u003c-\u003e class name \u003c -\u003e typeorm table` (imported decorators from `nestjs-graphql-easy` removed the ability to set a name)\n\n4. Decorators `Filter`, `Order` from `nestjs-graphql-easy` work only with loader types `ELoaderType.MANY` and `ELoaderType.ONE_TO_MANY`\n5. Decorators `Pagination` from `nestjs-graphql-easy` work only with loader types `ELoaderType.MANY`\n\n## Datasource\n\nNeed to pass `DataSource` to `GraphQLExecutionContext`.\n\nI do this by creating a `GraphQLModule` using a class\n\n```ts\nimport { GraphQLModule } from '@nestjs/graphql';\nimport { ApolloDriver } from '@nestjs/apollo';\n\nimport { GraphqlOptions } from './graphql.options';\n\nexport default GraphQLModule.forRootAsync({\n  imports: [],\n  useClass: GraphqlOptions, // \u003c-- ADD\n  inject: [],\n  driver: ApolloDriver,\n});\n```\n\n```ts\nimport { Injectable } from '@nestjs/common';\nimport { GqlOptionsFactory } from '@nestjs/graphql';\nimport { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';\n...\nimport { Request } from 'express';\nimport { DataSource } from 'typeorm';\nimport { setDataSource } from 'nestjs-graphql-easy' // \u003c-- ADD\n\n@Injectable()\nexport class GraphqlOptions implements GqlOptionsFactory {\n  constructor(private readonly dataSource: DataSource) { // \u003c-- ADD\n    setDataSource(this.dataSource); // \u003c-- ADD\n  }\n\n  public createGqlOptions(): Promise\u003cApolloDriverConfig\u003e | ApolloDriverConfig {\n    return {\n      ...\n      driver: ApolloDriver,\n      context: ({ req }: { req: Request }) =\u003e ({\n        req,\n      }),\n      ...\n    };\n  }\n}\n```\n\n## Dataloader (n + 1 problem solver)\n\nLoader usage guide:\n\n1. Add the `@Loader` parameter\n   1. Specify the type of relationship `loader_type`\n   2. Specify field name `field_name`\n   3. Specify entity `@Entity()` that is also an `@ObjectType()` using the return type function\n   4. Specify the name of the key in the entity for which the selection should be\n2. Add the `@Context` parameter\n3. In the body of the resolver, use the loader by passing the value of the key to fetch into it\n\n### many\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @Query(() =\u003e [Author])\n  public async authors(\n    @Loader({ // \u003c-- ADD\n      loader_type: ELoaderType.MANY, \n      field_name: 'authors',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    }) field_alias: string,\n    @Context() ctx: GraphQLExecutionContext // \u003c-- ADD\n  ) {\n    return await ctx[field_alias]; // \u003c-- ADD\n  }\n  ...\n}\n```\n\n### one-to-many\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @ResolveField(() =\u003e [Book], { nullable: true })\n  public async books(\n    @Parent() author: Author, // \u003c-- ADD\n    @Loader({ // \u003c-- ADD\n      loader_type: ELoaderType.ONE_TO_MANY,\n      field_name: 'books',\n      entity: () =\u003e Book,\n      entity_fk_key: 'author_id',\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext // \u003c-- ADD\n  ): Promise\u003cBook[]\u003e {\n    return await ctx[field_alias].load(author.id); // \u003c-- ADD\n  }\n  ...\n}\n```\n\n### many-to-one\n\n```ts\n@Resolver(() =\u003e Book)\nexport class BookResolver {\n  ...\n  @ResolveField(() =\u003e Author, { nullable: false })\n  public async author(\n    @Parent() book: Book, // \u003c-- ADD\n    @Loader({ // \u003c-- ADD\n      loader_type: ELoaderType.MANY_TO_ONE,\n      field_name: 'author',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext // \u003c-- ADD\n  ): Promise\u003cAuthor\u003e {\n    return await ctx[field_alias].load(book.author_id); // \u003c-- ADD\n  }\n  ...\n}\n```\n\n### one-to-one\n\n```ts\n@Resolver(() =\u003e Section)\nexport class SectionResolver {\n  ...\n  @ResolveField(() =\u003e SectionTitle, { nullable: true })\n  public async section_title(\n    @Parent() section: Section, // \u003c-- ADD\n    @Loader({ // \u003c-- ADD\n      loader_type: ELoaderType.ONE_TO_ONE,\n      field_name: 'section_title',\n      entity: () =\u003e SectionTitle,\n      entity_fk_key: 'section_id',\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext // \u003c-- ADD\n  ): Promise\u003cBook\u003e {\n    return await ctx[field_alias].load(section.id); // \u003c-- ADD\n  }\n  ...\n}\n\n@Resolver(() =\u003e SectionTitle)\nexport class SectionTitleResolver {\n  ...\n  @ResolveField(() =\u003e Section, { nullable: false })\n  public async section(\n    @Parent() section_title: SectionTitle, // \u003c-- ADD\n    @Loader({ // \u003c-- ADD\n      loader_type: ELoaderType.ONE_TO_ONE,\n      field_name: 'section',\n      entity: () =\u003e Section,\n      entity_fk_key: 'id',\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext // \u003c-- ADD\n  ): Promise\u003cSectionTitle\u003e {\n    return await ctx[field_alias].load(section_title.section_id); // \u003c-- ADD\n  }\n  ...\n}\n```\n\n### polymorphic\n\nFor a polymorphic relationship, you need to create a `UnionType`:\n\n```ts\nexport const ItemableType = createUnionType({ // \u003c-- ADD\n  name: 'ItemableType',\n  types: () =\u003e [ItemText, ItemImage],\n  resolveType(value) {\n    if (value instanceof ItemText) {\n      return ItemText;\n    } else if (value instanceof ItemImage) {\n      return ItemImage;\n    }\n  },\n});\n```\n\nIn the model entity, add two columns to indicate the foreign key and the name of the foreign model:\n\n```ts\n@ObjectType()\n@Entity()\nexport class Item {\n  ...\n  /**\n   * For a polymorphic relationship, the relationship in the Entity is not specified.\n   * But you need to create columns for foreign key and table type.\n   */\n\n  @Field(() =\u003e ID)\n  @Index()\n  @Column('uuid', { nullable: false })\n  @PolymorphicColumn() // \u003c-- ADD\n  public itemable_id: string; // foreign key\n\n  @Field(() =\u003e String)\n  @Index()\n  @Column({ nullable: false })\n  @PolymorphicColumn() // \u003c-- ADD\n  public itemable_type: string; // foreign type\n  ...\n}\n```\n\n```ts\n@Resolver(() =\u003e Item)\nexport class ItemResolver {\n  ...\n  @ResolveField(() =\u003e ItemableType, { nullable: true })\n  public async itemable(\n    @Parent() item: Item,\n    @Loader({\n      loader_type: ELoaderType.POLYMORPHIC,\n      field_name: 'itemable',\n      entity: () =\u003e ItemableType, // For a polymorphic relation, it is necessary to specify here not the Entity, but the Union type.\n      entity_fk_key: 'id',\n      entity_fk_type: 'itemable_type',\n    }) field_alias: string,\n    @Context() ctx: GraphQLExecutionContext\n  ) {\n    return await ctx[field_alias].load(item.itemable_id);\n  }\n  ...\n}\n```\n\nPolymorphic query example:\n\n```gql\n{\n  items {\n    id\n    itemable_id\n    itemable_type\n    itemable {\n      __typename\n      ... on ItemText {\n        id\n        value\n      }\n      ... on ItemImage {\n        id\n        file_url\n        created_at\n      }\n    }\n  }\n}\n```\n\n## Filtering\n\nFilters work in tandem with the dataloader and make it possible to filter entities by conditions:\n\n```ts\nenum EFilterOperation {\n  EQ = '=',\n  NOT_EQ = '!=',\n  NULL = 'IS NULL',\n  NOT_NULL = 'IS NOT NULL',\n  IN = 'IN',\n  NOT_IN = 'NOT IN',\n  ILIKE = 'ILIKE',\n  NOT_ILIKE = 'NOT ILIKE',\n  GT = '\u003e',\n  GTE = '\u003e=',\n  LT = '\u003c',\n  LTE = '\u003c=',\n}\n```\n\nDepending on the type of field to be used in the filter, the following operations apply:\n\n* basic (all types) operations:\n  ```ts\n  ['EQ', 'NOT_EQ', 'NULL', 'NOT_NULL', 'IN', 'NOT_IN']\n  ```\n* string (String) operations:\n  ```ts\n  ['ILIKE', 'NOT_ILIKE']\n  ```\n* precision (Number, Int, Float, Date, ID) operations:\n  ```ts\n  ['GT', 'GTE', 'LT', 'LTE']\n  ```\n\nFilters are generated based on the information specified in the `@Field` provided in the model:\n\n```ts\n@ObjectType()\n@Entity()\nexport class Author {\n  @Field(() =\u003e ID, { filterable: true }) // \u003c-- ADD\n  @PrimaryGeneratedColumn('uuid')\n  public id: string;\n  ...\n}\n```\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @Query(() =\u003e [Author])\n  public async authors(\n    @Loader({\n      loader_type: ELoaderType.MANY, \n      field_name: 'authors',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    }) field_alias: string,\n    @Filter(() =\u003e Author) _filter: unknown, // \u003c-- ADD\n    @Context() ctx: GraphQLExecutionContext\n  ) {\n    return await ctx[field_alias];\n  }\n  ...\n}\n```\n\nThis will add arguments to the query for filtering:\n\n```gql\n{\n  authors(WHERE: { id: { EQ: 1 } }) {\n    id\n    name\n  }\n}\n```\n\nWhen working with filters, it is important to remember [point 4 of the important section](#important).\n\n### Scalar\n\nIf the field type is a scalar, then by default only basic filtering operations can be used for such a field.\n\nIf you need to add the use of other operations, you can specify this:\n\n```ts\nimport { DateTimeISOResolver } from 'graphql-scalars';\n\n@ObjectType()\n@Entity()\nexport class Author {\n  @Field(() =\u003e DateTimeISOResolver, {\n    filterable: true,\n    allow_filters_from: [EDataType.PRECISION],\n  })\n  @UpdateDateColumn({\n    type: 'timestamp without time zone',\n    precision: 3,\n    default: () =\u003e 'CURRENT_TIMESTAMP',\n  })\n  public updated_at: Date;\n  ...\n}\n```\n\nIf the field type is not a scalar, then this option will be ignored.\n\n## Ordering\n\nOrdering works in tandem with the data loader and allows you to sort entities. Arguments for the query are created based on the information provided in the model in `@Field`\n\n```ts\n@ObjectType()\n@Entity()\nexport class Author {\n  @Field(() =\u003e ID, { sortable: true }) // \u003c-- ADD\n  @PrimaryGeneratedColumn('uuid')\n  public id: string;\n  ...\n}\n```\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @Query(() =\u003e [Author])\n  public async authors(\n    @Loader({\n      loader_type: ELoaderType.MANY, \n      field_name: 'authors',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    }) field_alias: string,\n    @Order(() =\u003e Author) _order: unknown, // \u003c-- ADD\n    @Context() ctx: GraphQLExecutionContext\n  ) {\n    return await ctx[field_alias];\n  }\n  ...\n}\n```\n\nThis will add arguments to the query for ordering:\n\n```gql\n{\n  authors(ORDER: { id: { SORT: ASC, NULLS: LAST } }) {\n    id\n    name\n  }\n}\n```\n\nWhen working with ordering, it is important to remember [point 4 of the important section](#important).\n\n## Pagination\n\nPagination works in tandem with a dataloader and allows you to limit the number of records received from the database\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @Query(() =\u003e [Author])\n  public async authors(\n    @Loader({\n      loader_type: ELoaderType.MANY, \n      field_name: 'authors',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    }) field_alias: string,\n    @Pagination() _pagination: unknown, // \u003c-- ADD\n    @Context() ctx: GraphQLExecutionContext\n  ) {\n    return await ctx[field_alias];\n  }\n  ...\n}\n```\n\nThis will add arguments to the query for pagination:\n\n```gql\n{\n  authors(PAGINATION: { page: 0, per_page: 10 }) {\n    id\n    name\n  }\n}\n```\n\nWhen working with pagination, it is important to remember [point 5 of the important section](#important).\n\n## [Cursor pagination](https://the-guild.dev/blog/graphql-cursor-pagination-with-postgresql)\n\nPagination works in tandem with a data loader, filters, and sorting and allows you to limit the number of records received from the database\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n  ...\n  @Query(() =\u003e [Author])\n  public async authors(\n    @Loader({\n      loader_type: ELoaderType.MANY, \n      field_name: 'authors',\n      entity: () =\u003e Author,\n      entity_fk_key: 'id',\n    }) field_alias: string,\n    @Filter(() =\u003e Author) _filter: unknown, // \u003c-- ADD\n    @Order(() =\u003e Author) _order: unknown, // \u003c-- ADD\n    @Pagination() _pagination: unknown, // \u003c-- ADD\n    @Context() ctx: GraphQLExecutionContext\n  ) {\n    return await ctx[field_alias];\n  }\n  ...\n}\n```\n\nThen you can get the first page using the query:\n\n```gql\nquery firstPage {\n  authors(\n    ORDER: { id: { SORT: ASC } }\n    PAGINATION: { per_page: 10 }\n  ) {\n    id\n  }\n}\n```\n\nThen you can get the next page using the query:\n\n```gql\nquery nextPage($ID_of_the_last_element_from_the_previous_page: ID!) {\n  authors(\n    WHERE: { id: { GT: $ID_of_the_last_element_from_the_previous_page }}\n    ORDER: { id: { SORT: ASC } }\n    PAGINATION: { per_page: 10 }\n  ) {\n    id\n  }\n}\n```\n\nFields that are planned to be used as a cursor must be allowed for filtering and sorting in the `@Field` decorator, and it is also recommended to index them indicating the sort order.\n\nWith such pagination, it is important to take into account the order in which the fields specified in the sorting are listed.\n\nYou can also use several fields as cursors. The main thing is to maintain order.\n\nThen you can get the first page using the query:\n\n```gql\nquery firstPage{\n  authors(\n    ORDER: { updated_at: { SORT: DESC }, id: { SORT: ASC } }\n    PAGINATION: { per_page: 10 }\n  ) {\n    id\n  }\n}\n\n```\n\nThen you can get the next page using the query:\n\n```gql\nquery nextPage(\n  $UPDATED_AT_of_the_last_element_from_the_previous_page: DateTime!\n  $ID_of_the_last_element_from_the_previous_page: ID!\n) {\n  authors(\n    WHERE: {\n      updated_at: { LT: $UPDATED_AT_of_the_last_element_from_the_previous_page }\n      OR: {\n        updated_at: {\n          EQ: $UPDATED_AT_of_the_last_element_from_the_previous_page\n        }\n        id: { GT: $ID_of_the_last_element_from_the_previous_page }\n      }\n    }\n    ORDER: { updated_at: { SORT: DESC }, id: { SORT: ASC } }\n    PAGINATION: { per_page: 10 }\n  ) {\n    id\n  }\n}\n```\n\nHowever, it is recommended to limit the time columns to milliseconds:\n\n```ts\n@ObjectType()\n@Entity()\nexport class Author {\n  ...\n  @Field(() =\u003e Date, { filterable: true, sortable: true })\n  @UpdateDateColumn({\n    type: 'timestamp without time zone',\n    precision: 3, // \u003c-- ADD\n    default: () =\u003e 'CURRENT_TIMESTAMP',\n  })\n  public updated_at: Date;\n  ...\n}\n```\n\n## Permanent filters\n\nYou can also specify permanent filters that will always be applied regardless of the query\n\nTo do this, you need to pass `entity_wheres` to the data loader:\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n@ResolveField(() =\u003e [Book], { nullable: true })\n  ...\n  public async books(\n    @Parent() author: Author,\n    @Loader({\n      loader_type: ELoaderType.ONE_TO_MANY,\n      field_name: 'books',\n      entity: () =\u003e Book,\n      entity_fk_key: 'author_id',\n      entity_wheres: [ // \u003c-- ADD\n        {\n          query: 'book.is_private = :is_private',\n          params: { is_private: false },\n        },\n      ],\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext\n  ): Promise\u003cBook[]\u003e {\n    return await ctx[field_alias].load(author.id);\n  }\n  ...\n}\n```\n\nSuch a filter can use the columns of entities joined via `entity_joins`:\n\n```ts\n@Resolver(() =\u003e Author)\nexport class AuthorResolver {\n@ResolveField(() =\u003e [Book], { nullable: true })\n  ...\n  public async books(\n    @Parent() author: Author,\n    @Loader({\n      loader_type: ELoaderType.ONE_TO_MANY,\n      field_name: 'books',\n      entity: () =\u003e Book,\n      entity_fk_key: 'author_id',\n      entity_wheres: [ // \u003c-- ADD\n        {\n          query: 'book.is_private = :is_private',\n          params: { is_private: false },\n        },\n        { // \u003c-- ADD\n          query: 'sections.title IS NOT NULL',\n        },\n      ],\n      entity_joins: [ // \u003c-- ADD\n        {\n          query: 'book.sections',\n          alias: 'sections',\n        },\n      ],\n    })\n    field_alias: string,\n    @Context() ctx: GraphQLExecutionContext\n  ): Promise\u003cBook[]\u003e {\n    return await ctx[field_alias].load(author.id);\n  }\n  ...\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkosminov%2Fnestjs-graphql-easy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftkosminov%2Fnestjs-graphql-easy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftkosminov%2Fnestjs-graphql-easy/lists"}