Skip to content

Commit

Permalink
docs(graphql): Update docs for new filterable relations
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-martin committed Jul 4, 2020
1 parent 2f852d9 commit 7fdae8e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 114 deletions.
10 changes: 10 additions & 0 deletions documentation/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
269 changes: 176 additions & 93 deletions documentation/docs/graphql/relations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.

<Tabs
defaultValue="todoitem"
values={[
{ label: 'TodoItemResolver', value: 'todoitem', },
{ label: 'SubTaskResolver', value: 'subtask', },
]
}>
<TabItem value="todoitem">

```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);
}
}
```

</TabItem>
<TabItem value="subtask">

```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);
}
}
```

</TabItem>
</Tabs>

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.
Expand All @@ -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';
Expand All @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions documentation/docs/introduction/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions documentation/docs/migration-guides/v0.15.x-to-v0.16.x.mdx
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 0 additions & 21 deletions documentation/docs/persistence/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -643,24 +643,3 @@ export class SubTask extends Model<SubTaskEntity> {

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<SubTask>) {
super(service);
}
}
```

Loading

0 comments on commit 7fdae8e

Please sign in to comment.