0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

フォルダ構成

image.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ってのが必要そう。

参考

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ですが、それは下記の公式?サンプルを見てもらえればって感じです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?