NestJSでGraphQLのモジュール開発2

こちらの記事の派生。
コピペで別モジュールを作りました。

https://qiita.com/aioa/items/9467eb2b957a49989f63

# フォルダ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247263/243226c2-68eb-5681-fafa-d8c5eb0541bb.png)

前回記事で「recipes」を作ったので、それをコピペして「items」って名前に変えてます。

「parts.resolver.ts」と「part.ts」は、同一モジュール内に複数resolverの作成したものです。

# 実装
## item
### item.ts
- クラス名変えただけで、コピペのまんま

```
import { Field, ID, ObjectType} from '@nestjs/graphql';

@ObjectType()
export class Item {
  @Field(type => ID)
  id: string;

  @Field()
  name: string;
}
```

### items.module.ts
- 「autoSchemaFile」をtrueに変更、モジュール別にファイル分けると、別のモジュールの参照ができなかったため
- itemを他で使わせるため、exportsで使いたいサービスを設定

```
import { Module } from '@nestjs/common';
import { ItemsResolver } from './items.resolver';
import { ItemsService } from './items.service';
import { GraphQLModule } from '@nestjs/graphql';
import { PartsResolver } from './parts.resolver';

@Module({
  providers: [ItemsResolver, ItemsService, PartsResolver],
  exports: [ItemsService],
  imports: [
    GraphQLModule.forRoot({
      installSubscriptionHandlers: true,
      autoSchemaFile: true,
    }),
  ],
})
export class ItemsModule {}
```

### items.resolver.ts
- item取得の実装、単純にクエリ用

```
import { Resolver, Args, Query, ResolveField, Parent } from '@nestjs/graphql';
import { ItemsService } from './items.service';
import { Item } from './models/item';

@Resolver(of => Item)
export class ItemsResolver {
  constructor(private readonly itemsService: ItemsService) {}
  
  @Query(returns => Item)
  async item(@Args('id') id: string): Promise<Item> {
    return await this.itemsService.findOneById(id);
  }
}
```

## recipe
### recipe.ts
- 今回追加した、itemsのカラムを追加

```
import { Field, ID, ObjectType} from '@nestjs/graphql';
import { Item } from '../../items/models/item';

@ObjectType()
export class Recipe {
  @Field(type => ID)
  id: string;

  @Field()
  name: string;

  @Field(type => [Item], {nullable: "items"})
  items?: Item[];
}
```

### recipes.module.ts
- importsに「ItemModule」を追加
- autoSchemaFileを修正

```
import { Module } from '@nestjs/common';
import { RecipesResolver } from './recipes.resolver';
import { RecipesService } from './recipes.service';
import { ItemsModule } from '../items/items.module';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  providers: [RecipesResolver, RecipesService],
  imports: [
    ItemsModule,
    GraphQLModule.forRoot({
      installSubscriptionHandlers: true,
      autoSchemaFile: true,
    }),
  ],
})
export class RecipesModule {}

```

### recipes.resolver.ts
- 別モジュールのサービスをコンストラクタでDI
- 追加したitemsカラム用の取得ロジック追加(ResolveField)
    - ResolveFieldに引数ないと、func名が自動で紐づくっぽい
    - function名と分けたい時は、コメント化している指定方法で

```
import { Resolver, Args, Query, ResolveField, Parent } from "@nestjs/graphql";
import { RecipesService } from './recipes.service';
import { Recipe } from './models/recipe';
import { ItemsService } from '../items/items.service';

@Resolver(of => Recipe)
export class RecipesResolver {
  constructor(
    private readonly recipesService: RecipesService,
    private readonly itemService: ItemsService,
  ) {}
  @Query(returns => Recipe)
  async recipe(@Args('id') id: string): Promise<Recipe> {
    return await this.recipesService.findOneById(id);
  }

  // @ResolveField('items', returns => [Item])
  @ResolveField()
  async items(@Parent() parent: Recipe) {
    return [await this.itemService.findOneByRecipeId(parent.id)];
  }
}

```

これで、モジュールの追加および、参照がいける。

ちなみにこの「ResolveField」、大元のクエリ取得件数が多い場合、その件数分呼ばれる。そのため多い場合はとしてDataLoaderってのが必要そう。

参考

https://engineering.mercari.com/blog/entry/20210818-mercari-shops-nestjs-graphql-server/

## parts
- 一応おまけで記載

### part.ts
- 同一モジュールのitemを参照する形

```
import { Field, ID, ObjectType} from '@nestjs/graphql';
import { Item } from "./item";

@ObjectType()
export class Part {
  @Field(type => ID)
  id: string;

  @Field()
  name: string;

  @Field(type => [Item], {nullable: "items"})
  items?: Item[];
}
```

### parts.resolver.ts

```
import { Resolver, Args, Query, ResolveField, Parent } from '@nestjs/graphql';
import { ItemsService } from './items.service';
import { Part } from './models/part';

@Resolver(of => Part)
export class PartsResolver {
  constructor(private readonly itemsService: ItemsService) {}
  @Query(returns => Part)
  async parts(@Args('id') id: string): Promise<Part> {
    return await this.itemsService.findOneById(id);
  }
  // @ResolveField('items', returns => [Item])
  @ResolveField()
  async items(@Parent() parent: Part) {
    return [await this.itemsService.findOneByRecipeId(parent.id)];
  }
}
```

※Resolverが違うのならば、ResolveFieldは同じfunc名で良さげ。

#検証

```
query GetRecipe {
  recipe(id: "1") {
    id
    name
    items {
      id
      name
    }
  }
}

{
  "data": {
    "recipe": {
      "id": "1234",
      "name": "name",
      "items": [
        {
          "id": "recipe12345",
          "name": "recipe_name"
        }
      ]
    }
  }
}
```

# まとめ
こんな感じでResolver毎に作っていけば、開発は進めそう。
ServiceとResolverで開発分けれるし、規模によっていは分担とかもありかも。

あとはMutationとSubscriptionですが、それは下記の公式？サンプルを見てもらえればって感じです。

https://github.com/nestjs/nest/blob/master/sample/23-graphql-code-first/src/recipes/recipes.resolver.ts