From 7fdae8eda670430e305e50cdd734a872401d9f9e Mon Sep 17 00:00:00 2001 From: doug-martin Date: Fri, 3 Jul 2020 23:30:48 -0600 Subject: [PATCH] docs(graphql): Update docs for new filterable relations --- documentation/docs/faq.md | 10 + documentation/docs/graphql/relations.mdx | 269 ++++++++++++------ .../docs/introduction/getting-started.md | 3 + .../migration-guides/v0.15.x-to-v0.16.x.mdx | 23 ++ documentation/docs/persistence/services.mdx | 21 -- documentation/sidebars.js | 1 + 6 files changed, 213 insertions(+), 114 deletions(-) create mode 100644 documentation/docs/migration-guides/v0.15.x-to-v0.16.x.mdx diff --git a/documentation/docs/faq.md b/documentation/docs/faq.md index edce37a8b..5b2340385 100644 --- a/documentation/docs/faq.md +++ b/documentation/docs/faq.md @@ -47,3 +47,13 @@ Yes! You can specify a `pagingStrategy` option to customize how paging is handle For more information and examples check out the following docs * [Resolver Paging Strategy](./graphql/resolvers.mdx#paging-strategy) * [Relations](./graphql/relations.mdx#many-relation) + + +## How can I filter on relations? + +You can filter based on relations if you use the `@FilterableRelation` or `@FilterableConnection` decorators when defining your relations. + +To read more and see examples read the following docs. + +* [`@FilterableRelation`](./graphql/relations.mdx#filterablerelation-decorator) +* [`@FilterableConnection`](./graphql/relations.mdx#filterableconnection-decorator) diff --git a/documentation/docs/graphql/relations.mdx b/documentation/docs/graphql/relations.mdx index a9fb40f5b..c43c027d0 100644 --- a/documentation/docs/graphql/relations.mdx +++ b/documentation/docs/graphql/relations.mdx @@ -11,6 +11,8 @@ When using the `nestjs-query` you can specify relations that should be exposed f * a single value (one-to-one, many-to-one) * an array of values that should use `OFFSET` or `NONE` paging. * `@Connection` - A connection that represents a collection that should use `cursor` based pagination. (e.g, many-to-many, one-to-many) +* `@FilterableRelation` - Same as the `@RelationDecorator` but also enables filtering on the relation. +* `@FilterableConnection` - Same as `@Connection` but also enables filtering on the relation. :::note When loading relations a [dataloader](https://github.com/graphql/dataloader) that is scoped to the request will be used. This prevents the n+1 problem. @@ -243,7 +245,7 @@ export class TodoItemDTO { ``` -Notce how the relation is defined as `[SubTaskDTO]` this lets `nestjs-query` know that it should return an array of `subTasks` +Notice how the relation is defined as `[SubTaskDTO]` this lets `nestjs-query` know that it should return an array of `subTasks` When specifying a many relation a couple of endpoints will automatically be generated. In this example the following are generated. @@ -320,6 +322,81 @@ input RelationsInput { If `disableRemove` was set to `false` or not specified a `removeSubTasksFromTodoItem` mutation would also be exposed with the same arguments as `addSubTasksToTodoItem`. ::: +## @FilterableRelation Decorator + +The `@FilterableRelation` extends the `@Relation` decorator exposing the ability to filter the `DTO` that defines the relation by relation properties. + +:::warning +The `@FilterableRelation` decorator will **only** work with relations defined by the orm used (e.g. `typeorm`, `sequelize`). If your relations are federated you cannot use the `@FilterableRelation` decorator. +::: + +### Example + +In this example we'll use the same Entities defined above to create a graphql endpoint that allows filtering `SubTasks` by `TodoItems`. + +```ts title="sub-task/sub-task.dto.ts" {6} +import { FilterableField, FilterableRelation } from '@nestjs-query/query-graphql'; +import { ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql'; +import { TodoItemDTO } from '../todo-item/todo-item.dto.ts'; + +@ObjectType('SubTask') +@FilterableRelation('todoItem', () => TodoItemDTO, { disableRemove: true }) +export class SubTaskDTO { + @FilterableField(() => ID) + id!: string; + + @FilterableField() + title!: string; + + @FilterableField() + completed!: boolean; + + @FilterableField(() => GraphQLISODateTime) + created!: Date; + + @FilterableField(() => GraphQLISODateTime) + updated!: Date; + + @FilterableField() + todoItemId!: string; +} +``` +Notice the use of `@FilterableRelation` instead of `@Relation`, by using the `@FilterableRelation` version `nestjs-query` will allow filtering on the `todoItem` relation. + +The module definition remains the same. + +```ts title="sub-task/sub-task.module.ts" +import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql'; +import { NestjsQueryTypeOrmModule } from '@nestjs-query/query-typeorm'; +import { Module } from '@nestjs/common'; +import { SubTaskDTO } from './sub-task.dto'; +import { SubTaskEntity } from './sub-task.entity'; + +@Module({ + imports: [ + NestjsQueryGraphQLModule.forFeature({ + imports: [NestjsQueryTypeOrmModule.forFeature([SubTaskEntity])], + resolvers: [{ DTOClass: SubTaskDTO, EntityClass: SubTaskEntity }], + }), + ], +}) +export class SubTaskModule {} +``` + +When querying for `SubTasks` you can now also filter on `todoItem` properties. + +In this example we'll find all subTasks that are related to a `todoItem` with a title that starts with `Created`. +```graphql +{ + subTasks(filter: { todoItem: { title: { like: "Create%" } } }) { + title + completed + } +} + +``` + + ## @Connection Decorator ### DTO Definition @@ -484,6 +561,101 @@ type TodoItemSubTasksConnection { ``` + +## @FilterableConnection Decorator + +The `@FilterableConnection` extends the `@Connection` decorator exposing the ability to filter the `DTO` that defines the relation by relation properties. + +:::warning +The `@FilterableConnection` decorator will **only** work with relations defined by the orm used (e.g. `typeorm`, `sequelize`). If your relations are federated you cannot use the `@FilterableConnection` decorator. +::: + +### Example + +In this example we'll use the same Entities defined above to create a graphql endpoint that allows filtering `TodoItems` by `subTasks`. + +```ts title="todo-item/todo-item.dto.ts" {6} +import { FilterableField, FilterableConnection } from '@nestjs-query/query-graphql'; +import { ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql'; +import { SubTaskDTO } from '../sub-task/sub-task.dto' + +@ObjectType('TodoItem') +@FilterableConnection('subTasks', () => SubTaskDTO, { disableRemove: true }) +export class TodoItemDTO { + @FilterableField(() => ID) + id!: string; + + @FilterableField() + title!: string; + + @FilterableField() + completed!: boolean; + + @FilterableField(() => GraphQLISODateTime) + created!: Date; + + @FilterableField(() => GraphQLISODateTime) + updated!: Date; +} + +``` +Notice the use of `@FilterableConnection` instead of `@Connection`, by using the `@FilterableConnection` version `nestjs-query` will allow filtering on the `subTasks` relation. + +The module definition remains the same. + +```ts title="todo-item/todo-item.module.ts" +import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql'; +import { NestjsQueryTypeOrmModule } from '@nestjs-query/query-typeorm'; +import { Module } from '@nestjs/common'; +import { TodoItemDTO } from './todo-item.dto'; +import { TodoItemEntity } from './todo-item.entity'; + +@Module({ + imports: [ + NestjsQueryGraphQLModule.forFeature({ + imports: [NestjsQueryTypeOrmModule.forFeature([TodoItemEntity])], + resolvers: [{ DTOClass: TodoItemDTO, EntityClass: TodoItemEntity }], + }), + ], +}) +export class TodoItemModule {} +``` + +When querying for `TodoItems` you can now also filter on `subTasks` properties. + +In this example we'll find all `todoItems` that have `subTasks` that are completed. +```graphql +{ + todoItems(filter: { subTasks: { completed: { is: true } } }) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + edges { + node { + id + title + description + completed + subTasks { + edges { + node { + title + description + completed + } + } + } + } + cursor + } + } +} +``` + + ## Options The following options can be passed to the `@Relation` or `@Connection` decorators, to customize functionality. @@ -493,6 +665,7 @@ The following options can be passed to the `@Relation` or `@Connection` decorato * `disableRead` - Set to `true` to disable read operations. * `disableUpdate` - Set to `true` to disable update operations. * `disableRemove` - Set to `true` to disable remove operations. +* `allowFiltering` - Set to `true` to allow filtering on the relation. * `guards=[]` - An array of [guards](https://docs.nestjs.com/guards) to add to `update` and `remove` endpoints. * `interceptors=[]` - An array of [interceptors](https://docs.nestjs.com/interceptors) to add to `update` and `remove` endpoints. * `pipes=[]` - An array of [pipes](https://docs.nestjs.com/pipes) to add to `update` and `remove` endpoints. @@ -686,92 +859,6 @@ We can then add it to our relations Now any requests that go to the `update` or `remove` endpoints will require the guard. -## Advanced - -### Manual Relation Definition - -You also have the option to use the legacy way of defining relations in your resolver directly - -Instead of decorating your DTO with `Relation` or `Connection` you can pass them in as options to your resolver. - - - - -```ts title="todo-item/todo-item.resolver.ts" {9-11} -import { CRUDResolver } from '@nestjs-query/query-graphql'; -import { Resolver } from '@nestjs/graphql'; -import { SubTaskDTO } from '../sub-task/dto/sub-task.dto'; -import { TodoItemDTO } from './todo-item.dto'; -import { TodoItemService } from './todo-item.service'; - -@Resolver(() => TodoItemDTO) -export class TodoItemResolver extends CRUDResolver(TodoItemDTO, { - relations: { - many: { subTasks: { DTO: SubTaskDTO, disableRemove: true } }, - }, -}) { - constructor(readonly service: TodoItemService) { - super(service); - } -} -``` - - - - -```ts title="sub-task/sub-task.resolver.ts" {9-11} -import { CRUDResolver } from '@nestjs-query/query-graphql'; -import { Resolver } from '@nestjs/graphql'; -import { TodoItemDTO } from '../todo-item/dto/todo-item.dto'; -import { SubTaskDTO } from './sub-task.dto'; -import { SubTaskService } from './sub-task.service'; - -@Resolver(() => SubTaskDTO) -export class SubTaskResolver extends CRUDResolver(SubTaskDTO, { - relations: { - one: { todoItem: { DTO: TodoItemDTO, disableRemove: true } }, - }, -}) { - constructor(readonly service: SubTaskService) { - super(service); - } -} -``` - - - - -You can also specify the `pagingStrategy` option to change the default paging behavior for a single relation. - -The `pagingStrategy` can be set to `OFFSET` or `NONE`. - -In this example we'll disable connections and cursor based paging in favor of an `OFFSET` based strategy that returns an array of values. - -```ts title="todo-item/todo-item.resolver.ts" {9-11} -import { CRUDResolver, PagingStrategies } from '@nestjs-query/query-graphql'; -import { Resolver } from '@nestjs/graphql'; -import { SubTaskDTO } from '../sub-task/dto/sub-task.dto'; -import { TodoItemDTO } from './todo-item.dto'; -import { TodoItemService } from './todo-item.service'; - -@Resolver(() => TodoItemDTO) -export class TodoItemResolver extends CRUDResolver(TodoItemDTO, { - relations: { - many: { subTasks: { DTO: SubTaskDTO, pagingStrategy: PagingStrategies.OFFSET, disableRemove: true } }, - }, -}) { - constructor(readonly service: TodoItemService) { - super(service); - } -} -``` - ### Relation Mixin If you are using the [resolvers individually](./resolvers#individual-resolvers) you can use the following mixins to add relations functionality. @@ -780,7 +867,7 @@ If you are using the [resolvers individually](./resolvers#individual-resolvers) When using The `Relatable` mixin adds all relations functionality to a resolver. -In this example we expose on read endpoints for todo items with a readable subtasks realtion. +In this example we expose on read endpoints for todo items with the relations defined on the `TodoItemDTO`. ```ts title="todo-item/todo-item.resolver.ts" {12-14} import { ReadResolver, Relatable } from '@nestjs-query/query-graphql'; @@ -793,11 +880,7 @@ import { TodoItemService } from './todo-item.service'; const guards = [AuthGuard]; @Resolver(() => TodoItemDTO) -export class TodoItemResolver extends Relatable(TodoItemDTO, { - many: { - subTasks: { DTO: SubTaskDTO, disableRemove: true, disableUpdate: true }, - }, -})(ReadResolver(TodoItemDTO)) { +export class TodoItemResolver extends Relatable(TodoItemDTO)(ReadResolver(TodoItemDTO)) { constructor(readonly service: TodoItemService) { super(service); } diff --git a/documentation/docs/introduction/getting-started.md b/documentation/docs/introduction/getting-started.md index 9555ef424..c9ca47133 100644 --- a/documentation/docs/introduction/getting-started.md +++ b/documentation/docs/introduction/getting-started.md @@ -32,6 +32,9 @@ Nestjs-query is composed of multiple packages ## Migration Guides +* [`v0.15.x` to `v0.16.x`](../migration-guides/v0.15.x-to-v0.16.x) +* [`v0.14.x` to `v0.15.x`](../migration-guides/v0.14.x-to-v0.15.x) +* [`v0.13.x` to `v0.14.x`](../migration-guides/v0.13.x-to-v0.14.x) * [`v0.12.x` to `v0.13.x`](../migration-guides/v0.12.x-to-v0.13.x) * [`v0.10.x` to `v0.11.x`](../migration-guides/v0.10.x-to-v0.11.x) * [`v0.5.x` to `v0.6.x`](../migration-guides/v0.5.x-to-v0.6.x) diff --git a/documentation/docs/migration-guides/v0.15.x-to-v0.16.x.mdx b/documentation/docs/migration-guides/v0.15.x-to-v0.16.x.mdx new file mode 100644 index 000000000..9e7e3ba1a --- /dev/null +++ b/documentation/docs/migration-guides/v0.15.x-to-v0.16.x.mdx @@ -0,0 +1,23 @@ +--- +title: v0.15.x to v0.16.x +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Filter on Relations + +In `v0.16.x` we made a big step forward in the functionality of relations by allowing for filtering on their properties. While making this change it was aso decided to unify the way that relations are defined. + +In previous versions of `nestjs-query` you could define relations using the `@Relation` or `@Connection` decorator as well as manually defining them when creating your resolvers, this leads to an in consistent experience while using `nestjs-query`. In the latest version all relations **MUST** be defined using the decorators. + +In `v0.16.x` there are two new decorators (`@FilterableRelation`, `@FilterableConnection`) exposed to allow you to decide if end users should be able to filter on the relation. + +To read more about the new decorators as well as defining relations you can read the [`relations docs`](../graphql/relations.mdx) + + +## Filter Definitions + +In `v0.16.x` filters are defined based on the operation (read, update, delete). In previous versions of `nestjs-query` all filters for a type in graphql were the same. In `v0.16.x` this behavior needed to change in order to support filtering on relations. + +This should be a passive change for most, however the generated graphql schema will contain new types for each filter operation. diff --git a/documentation/docs/persistence/services.mdx b/documentation/docs/persistence/services.mdx index 3cc3da893..b699f1ac2 100644 --- a/documentation/docs/persistence/services.mdx +++ b/documentation/docs/persistence/services.mdx @@ -643,24 +643,3 @@ export class SubTask extends Model { Notice how the `todoItem` is not decorated with a field decorator, instead it is exposed through the `@Relation` decorator. -If you do not decorate the class with `@Connection` or `@Relation` you can alternatively specify them in the resolver. - -```ts {10} -import { QueryService } from '@nestjs-query/core'; -import { CRUDResolver } from '@nestjs-query/query-graphql'; -import { Resolver } from '@nestjs/graphql'; -import { TodoItem } from '../todo-item/todo-item'; -import { SubTask } from './sub-task'; - -@Resolver(() => SubTask) -export class SubTaskResolver extends CRUDResolver(SubTask, { - relations: { - one: { todoItem: { DTO: TodoItem, disableRemove: true } }, - }, -}) { - constructor(@InjectQueryService(SubTask) readonly service: QueryService) { - super(service); - } -} -``` - diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 4ef6de043..ce26e9f7a 100755 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -57,6 +57,7 @@ module.exports = { 'migration-guides/v0.12.x-to-v0.13.x', 'migration-guides/v0.13.x-to-v0.14.x', 'migration-guides/v0.14.x-to-v0.15.x', + 'migration-guides/v0.15.x-to-v0.16.x', ], }, };