GraphQLBundleを使って初めてのGraphQLサーバーを実装してみる
Symfony Advent Calendar 2018 16日目の記事です。
GraphQLはFacebookが開発しているAPIへの問い合わせで使われるクエリ言語です。
ここでは GraphQLBundle を利用して簡単なGraphQLサーバーを実装する方法について書きます。
そもそもGraphQLって何?というところについては、この記事ではあまり触れませんので、この辺りを読んでみると良いかもです。
GraphQLBundleのインストール
サクッとSymfonyアプリを作成、ビルトインサーバーを起動して http://127.0.0.1:8000/
にアクセスできることを確認しておきましょう。
※本記事中でのSymfonyアプリケーションのバージョンは4.2.1です。
$ composer create-project symfony/website-skeleton app $ cd app $ php bin/console server:run
ではGraphQLBundleをインストールします。
$ composer require overblog/graphql-bundle
途中に出てくる選択肢は Yes
で。
インストール後 http://127.0.0.1:8000/
にアクセスすると500エラーが返ってくるようになります。
{"error":{"code":500,"message":"Internal Server Error","exception":[{"message":"Could not found type with alias \"Query\". Do you forget to define it?","class":"Overblog\\GraphQLBundle\\Resolver\\UnresolvableException","trace":[{"namespace":"","short_class":"","class":"","type":"","function":"","file":"\/Users\/tomcky\/Work\/personal\/tmp\/graphql-on-symfony\/app\/vendor\/overblog\/graphql-bundle\/src\/Resolver\/TypeResolver.php","line":43,"args":[]}, ...
一旦ここで、エンドポイントをルートから http://127.0.0.1:8000/graphql/
に変更してみます。
エンドポイントを変更するには config/routes/graphql.yml
に prefix
の項目を追加します。
overblog_graphql_endpoint: resource: "@OverblogGraphQLBundle/Resources/config/routing/graphql.yml" # 以下を追加 prefix: graphql
これで http://127.0.0.1:8000/graphql/
がエンドポイントになり、ルートにアクセスすると再びウェルカムページが表示されるようになります。
GraphQLサーバーの実装
無事インストールが完了しましたが、まだサーバーは500エラーを返す状態なので、正常なレスポンスを返すGraphQLサーバーを実装していきます。
サーバー実装にあたり、まずはGraphQLの重要な要素であるスキーマを定義しなければなりません。
スキーマの定義
GraphQLBundleでのスキーマ定義はデフォルトでyamlで行うようになっていますが、graphqlのフォーマットで行えるように設定しておきます。
具体的には config/packages/graphql.yaml
を開き、 type: yaml
を type: graphql
に変更します。
overblog_graphql: definitions: schema: query: Query mappings: auto_discover: false types: - # 以下の箇所を修正 #type: yaml type: graphql dir: "%kernel.project_dir%/config/graphql/types" suffix: ~
それではスキーマを定義しましょう。
スキーマは config/graphql/types
に定義ファイルを作成して記述していきます。
GraphQLにおいてリソース取得のエントリーポイントとなる Query
型と、例としてリソースとなる Book
型を定義してみます。
Query
型の定義は config/graphql/types/query.types.graphql
を作成して以下のように記述します。
type Query { books: [Book]! }
そして Book
型の定義は config/graphql/types/book.types.graphql
を作成して以下のように記述します。
type Book { id: ID! title: String! author: String! }
これでスキーマの定義は完了です。
リゾルバの実装
スキーマは定義しました。
ですが、定義したスキーマのフィールドに合わせて実際のデータを返却する処理は未実装です。
そのような処理をするための関数(リゾルバ)を実装していきます。
まずは config/packages/graphql.yaml
を修正してリゾルバを指定します。
overblog_graphql: definitions: schema: query: Query resolver_maps: - App\Resolver\MyResolverMap # この一行を追加 mappings: auto_discover: false types: - type: graphql dir: "%kernel.project_dir%/config/graphql/types" suffix: ~
次に src/Resolver/MyResolverMap.php
を作成してリゾルバを実装します。
以下は、定義したスキーマに沿った固定値を返すだけの簡単なものです。
<?php namespace App\Resolver; use Overblog\GraphQLBundle\Resolver\ResolverMap; use Overblog\GraphQLBundle\Definition\Argument; use GraphQL\Type\Definition\ResolveInfo; class MyResolverMap extends ResolverMap { protected function map() { return [ 'Query' => [ self::RESOLVE_FIELD => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) { return [ ['id' => 1, 'title' => 'Harry Potter and the Prisoner of Azkaban', 'author' => 'J.K. Rowling'], ['id' => 2, 'title' => 'The Lord of the Rings', 'author' => 'J.R.R. Tolkien'], ]; } ], ]; } }
これでリゾルバの実装は完了です。
curlでリソースを取得してみます。
curl 'http://127.0.0.1:8000/graphql/' --get --data-urlencode 'query={books{id, title, author}}'
{"data":{"books":[{"id":"1","title":"Harry Potter and the Prisoner of Azkaban","author":"J.K. Rowling"},{"id":"2","title":"The Lord of the Rings","author":"J.R.R. Tolkien"}]}}
取得できました。
試しに title
フィールドのみを取得してみます。
$ curl 'http://127.0.0.1:8000/graphql/' --get --data-urlencode 'query={books{title}}'
{"data":{"books":[{"title":"Harry Potter and the Prisoner of Azkaban"},{"title":"The Lord of the Rings"}]}}
良いですね。
GraphiQLの導入
curlで動作確認することはできますが、GraphQLのクエリを記述するのは辛いものがあります。
そこで GraphiQL
と呼ばれるGraphQL IDEを導入します。
※Graph"i"QLです。わかりづらい。。グラフィクルと発音するようです。
GraphiQL導入にはGraphiQLBundleをインストールすればOKです。
$ composer require --dev overblog/graphiql-bundle
インストールすると http://127.0.0.1:8000/graphiql
にアクセスできるようになります。
最後に
リゾルバは、配列でなくてもDoctrineのエンティティオブジェクトをそのまま返すこともできます。
もちろん、その場合は、エンティティのフィールドがスキーマ定義に沿っている必要があります。
本当はそこまで書きたかったのですが、ちょっと長くなりそうなので。。
本記事では、固定値を返すだけの非常に簡単なGraphQLサーバーの実装まででしたが、実際に固定値を返すだけで済むことは、ほぼないはずです。
また、リソース取得だけでなく更新するためのMutationも使う必要があるでしょうし、複数のリソース取得に対応するにはどのように実装するか考えなくてはいけません。
認証などの実装も考える必要があるでしょう。
その辺りについても、また書けたらと思っています(やる気が、でれば。。