Resolver(スキーマファースト)
スキーマファーストでGraphQLのResolverを作成する時、SDLでスキーマの型を手動で定義することから始めます。以下のSDL型の定義について詳細に考えてみましょう。
メモ
このセクションでは便宜上、すべてのSDLを一箇所に集約しています。(例えば、以下のように.
gql
ファイルを作成します)実際には、コードをモジュール形式で整理するのが適切でしょう。たとえば、各ドメインエンティティを表す型定義と関連サービス、ResolverコードやNestモジュール定義クラスを含む個別のSDLファイルを、そのエンティティ専用のディレクトリに作成すると便利なことがあります。NestJSでは、実行時に個々のスキーマの型定義をすべて集約します。type Author {
id: Int!
firstName: String
lastName: String
posts: [Post]
}
type Post {
id: Int!
title: String!
votes: Int
}
type Query {
author(id: Int!): Author
}
上記のスキーマは、
author(id: Int!)
という1つのクエリを公開します。メモ
それでは
AuthorsResolver
クラスを作成します。(author
データへのアクセスを行う)authors/authors.resolver.ts
@Resolver('Author')
export class AuthorsResolver {
constructor(
private authorsService: AuthorsService,
private postsService: PostsService,
) {}
@Query()
async author(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}
@ResolveField()
async posts(@Parent() author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
}
メモ
@Resolver
や@ResolveField
、@Args
は@nestjs/graphql
パッケージからインポートできます。注意
AuthorsService
およびPostsService
クラス内のロジックは、必要なだけシンプルにすることも高度にすることも両方できます。この例の主なポイントは、リゾルバの構築方法と、それらが他のプロバイダとどのように相互作用できるかを示すことです。@Resolver()デコレータは必須です。オプションの文字列引数にはクラスを指定します。このクラス名は、クラスが
@ResolveField()
デコレータを含む場合は常に必要で、NestJSにデコレートされたメソッドが親の型(今回の例ではAuthor
型)に関連していることを知らせます。また、クラスの先頭で@Resolver()
を設定する代わりに、各メソッドに対して設定することも可能です。@Resolver('Author')
@ResolveField()
async posts(@Parent() author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
この場合(メソッドレベルでの
@Resolver()
デコレータ)、クラス内に複数の@ResolveField()
デコレータがあると、それらすべてに@Resolver()
を追加しなければなりません。これはあまり推奨されていません。メモ
@Resolver()
に渡されたクラス名の引数は、クエリ(@Query()
デコレータ)やミューテーション(@Mutation()
デコレータ)には影響しません。注意
メソッドレベルでの
@Resolver()
デコレータはコードファーストのアプローチではサポートされていません。上記の例では、
@Query()
と@ResolveField()
デコレータは、メソッド名に基づいてGraphQLスキーマの方と関連付けられています。例えば、上記の例から以下のような構成を考えてみましょう。@Query()
async author(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}
これにより、スキーマの
author
クエリに次のようなエントリが生成されます。type Query {
author(id: Int!): Author
}
逆に、これらを分離してResolverのメソッドに
getAuthor()
やgetPosts()
というような名前を使うのが一般的でした。これを行うには、以下のようにマッピング名をデコレータの引数に渡します。@Resolver('Author')
export class AuthorsResolver {
constructor(
private authorsService: AuthorsService,
private postsService: PostsService,
) {}
@Query('author')
async getAuthor(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}
@ResolveField('posts')
async getPosts(@Parent() author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
}
メモ
Nest CLIはすべての定型的なコードを自動的に生成するジェネレータ(回路図)を提供し、このようなことをしなくても済むようにし、シンプルな開発体験を提供しています。
スキーマファーストのアプローチを活用し、型付け生成機能を有効にした場合、アプリケーションを実行すると以下のファイルが生成されます。(
GraphQLModule.forRoot()
メソッドで指定した場所にあります)。例えば、src/graphql.ts
の場合は以下のようになります。graphql.ts
export class Author {
id: number;
firstName?: string;
lastName?: string;
posts?: Post[];
}
export class Post {
id: number;
title: string;
votes?: number;
}
export abstract class IQuery {
abstract author(id: number): Author | Promise<Author>;
}
クラスを生成する(インターフェイスを生成するデフォルトの手法の代わりに)ことで、宣言型の検証デコレータをスキーマファーストのアプローチと組み合わせて使うことができます。
例えば、以下のように生成されたCreatePostInputクラスにクラス検証デコレータを追加すると、titleフィールドに最小及び最大の文字列長を強制できます。
import { MinLength, MaxLength } from 'class-validator';
export class CreatePostInput {
@MinLength(3)
@MaxLength(50)
title: string;
}
注意
入力(およびパラメータ)の自動検証を有効化するには、
ValidationPipe
を活用します。しかしながら、自動生成されたファイルに直接デコレータを追加すると、ファイルが生成されるたびに上書きされてしまいます。かわりに、別のファイルを作成して生成されたクラスを単純に拡張してください。
import { MinLength, MaxLength } from 'class-validator';
import { Post } from '../../graphql.ts';
export class CreatePostInput extends Post {
@MinLength(3)
@MaxLength(50)
title: string;
}
専用のデコレータを使用して、標準的なGraphQLリゾルバの引数にアクセスできます。以下は、Nestデコレータとそれらが表現するプレーンなApolloパラメータの比較です。
@Root() /@Parent() | root /parent |
@Context(param?: string) | context /context[param] |
@Info(param?: string) | info /info[param] |
@Args(param?: string) | args /args[param] |
これらの引数の意味は以下の通りです。
root
:親フィールドのリゾルバから返された結果、あるいはトップレベルのQuery
フィールドの場合はサーバから渡されたrootValue
を含むオブジェクト。context
:特定のクエリにおいて、すべてのリゾルバに共有されるオブジェクトで、通常、リクエストごとの状態を含むために使用される。info
:クエリの実行状態に関する情報を含むオブジェクト。args
:クエリ内のフィールドに渡された引数を持つオブジェクト
上記の手順が終わ ると、
GraphQLModule
がリゾルバマップを生成するために必要なすべての情報を宣言的に指定したことになります。GraphQLModule
はリフレクションを使ってデコレーター経由で提供されたメタデータをイントロスペクトし、クラスを自動的に正しいリゾルバーマップに変換します。あとはリゾルバクラス(
AuthorsResolver
)を提供し(つまり何らかのモジュールにプロバイダとしてリストし)、そのモジュール(AuthorsModule
)をどこかにインポートして、Nestがそれを利用できるようにするだけでよいのです。例えば、
AuthorsModule
の中でこれを行うことができ、この文脈で必要な他のサービスも提供することができます。AuthorsModule
は必ずどこか(ルートモジュールや、ルートモジュールからインポートされた他のモジュールなど)でインポートしてください。authors/authors.module.ts
@Module({
imports: [PostsModule],
providers: [AuthorsService, AuthorsResolver],
})
export class AuthorsModule {}
メモ
いわゆるドメインモデルによってコードを整理すると便利です(REST APIのエントリポイントを整理する方法と同様です)。このアプローチでは、モデル(
ObjectType
クラス)、リゾルバ、サービスを、ドメインモデルを表すNestモジュール内にまとめておきます。これらのコンポーネントはすべて、モジュールごとに1つのフォルダーに収めます。このようにして、Nest CLIを使用して各要素を生成すると、Nestがこれらの部品をすべて自動的に配線します(適切なフォルダーにファイルを配置し、プロバイダーとインポート配列にエントリを生成するなど)。Last modified 10mo ago