「WPGraphQL」を使用してWordPressでGraphQLを使う
WordPressでGraphQLを使用する方法を紹介します。
WordPressはRESTをサポートしていますが、GraphQLはサポートしていません。(ver6.2現在)そのため、GraphQLを使用するには自身で構築するかプラグインを使用する必要があります。
プラグインでGraphQLを使用するとなると一番最初に候補に挙がるのが「WPGraphQL」。今回はこの「WPGraphQL」を使用してデータを取得する所まで確認してみます。
序盤GraphQLについての説明を入れますので、さっさとコードを書きたい方は読み飛ばしてください。ノードとエッジについて軽く復習したい人は最初から見てください。
GraphQL基礎
GraphQLはAPIのための問い合わせ言語です。RESTと比較した際のGraphQLの特徴は必要なデータのみ指定して取得できるところです。
下記例はクエリ(左)と取得結果(右)です。
RESTではエンドポイントにパラメーターを付与してデータを取得します。そのため、タイトルのみ取得したいのに他の情報までいっぱい取得してしまうことや、一度目の取得結果を利用してもう一度結果を取得しなければいけないことがあります。
GraphQLでこういった点を解決するのに向いています。
コンセプト
「GraphQL」は「Graph」+「QL(Query Language)」です。このGraphはGraphQLのコンセプトがグラフ理論にあるところから来ています。
グラフ理論は名前からも察しが付きますが、数学の学問です。数学の学問と聞くと難しそうですが、グラフ自体は身近なものです。
グラフとは
グラフはオブジェクトとそのつながりから構成されます。文字でみるとわかりにくいので、具体例を見てみましょう。
下の図はグラフです。
下の図もグラフです。
グラフ理論
数学的な表現でグラフは「G=(V,E)」となります。Gはグラフ、Vは頂点(Vertex)、Eはエッジ(Edge)です。頂点はノード(Node)とも呼ばれます。
下記の例のグラフを見てみましょう。
(a,b画像)
2つの頂点とエッジが一つでグラフを構築しています。
もう一つ例を見てみます。
(a,b,c,d,e画像)
こちらは先ほどのグラフより複雑ですが、一つずつ見ていけばそこまで難しくありません。
エッジについてaから順に書いていますが、これを入れ替えるとどうなるでしょうか。グラフから図を起してみるとまったく同じものが完成することがわかります。
これを無向グラフと呼びます。対して有向グラフも存在します。
無向グラフ
無向グラフではエッジとエッジの組み合わせは並び順に関連付けられません。
無向グラフのわかりやすい例はFacebookが挙げられます。
下図の黄色の線が友達を表します。
有向グラフ
有向グラフにおけるエッジは頂点の順序付けられた組み合わせに関連付けられます。
有向グラフのわかりやすい例はtwitterです。
下図の一郎はインフルエンサーです。みんなからフォローされています。一郎をフォローしている人はいつでもフォローを解除することができます。
WPGraphQLにおけるWordPressのグラフ
WPGraphQLでは「投稿」「固定ページ」「コメント」「ターム」「ユーザー」はすべてノードとみなされます。また、それぞれのノードは「title」や「name」などフィールドをもっています。
ノードが他のノードとつながっている場合もあります。
投稿の場合「title」「date」「slug」などのフィールドを持っています。投稿を投稿した人がいるため「ユーザー」ノードと接続しています。投稿に紐づいたコメントがあるため「コメント」と紐づいています。タグやカテゴリーとも紐づいているため「ターム」ノードとも接続しています。
この例のグラフを図にするとこうなります。
プラグインの設置
実際にプラグインをサイトに設置してみましょう。GraphQL IDEも使用できるため、実際にクエリを動かしながら確認できます。
インストール
WPGraphQLはWordPressのプラグインから検索することで表示されます。
zip形式は下記でダウンロードできます。WPGraphQL
設定
「管理画面」⇒「WPGraphQL」⇒「Settings」から設定を変更することができます。
「GraphQL Endpoint」でポイントでエンドポイントを変更することや、デバッグモードで動かすことができます。また、悪質なクエリの対象にならないように「クエリの深さ」や「バッチクエリのリクエスト数」などの制御もできます。
ちょっと確認してみようというぐらいであれば、初期設定のままで問題ありません。
WPGraphQLでデータを取得する
グラフの図を念頭に置いてWPGraphQLでデータを取得します。
スラッグが「sample」の投稿を取得する例を見てみましょう。
1query getPost {2 post(id: "sample", idType: SLUG) {3 slug4 content5 date6 modified7 featuredImage {8 node {9 sourceUrl(size: MEDIUM_LARGE)10 altText11 }12 }13 categories {14 edges {15 node {16 name17 link18 }19 }20 }21 enqueuedScripts {22 edges {23 node {24 handle25 src26 }27 }28 }29 enqueuedStylesheets {30 edges {31 node {32 handle33 src34 }35 }36 }37 }38}
3-6行目で「スラッグ」「記事内容」「投稿日」「更新日」を取得しています。
featuredImageはアイキャッチ画像です。エッジが示すノードの「画像URL(画像の大きさはmedium_largeを指定)」「alt」を取得しています。
categoriesはカテゴリーです。紐づけられているカテゴリーの「名前」「リンク」を取得しています。
enqueuedScriptsは指定した投稿が表示される際に読み込まれるスクリプトです。「ハンドル名」「パス」のリストを取得しています。
enqueuedStylesheetsは指定した投稿が表示される際に読み込まれるスタイルです。「ハンドル名」「パス」のリストを取得しています。
クエリを軽く見ただけでも何を取得したいかは大体わかると思います。フィールドを取得するのとノードを取得するのもわかると思います。ではなぜ「featuredImage」と「category」「enqueuedScripts」「enqueuedStylesheets」で書き方が異なるのでしょうか。
それは「featuredImage」はエッジで「category」「enqueuedScripts」「enqueuedStylesheets」はConnectionだからです。
Relay Cursor Connections Specification
ConnectionとはRelayの仕様です。WPGraphQLはこの仕様に準拠しています。
Queryを使用する上での主な特徴として「ノードへの一意なIDの設定」「CursorとConnection」が挙げられます。
ノードへの一意なIDの設定
WordPressのIDはデータベースのIDなので、テーブルが違うとIDが重複する可能性があります。例挙げるとID:1の投稿「Hello, World」とID:1のユーザー「ABC」みたいな感じです。
グラフ上の各ノードを一意に識別できるようにデータベース上のIDと異なるグローバルなIDを設定しています。
CursorとConnection
Connectionはノード間の関係とリストを処理するためのコンセプトです。Cursorは特定のノードを指すポインターでエッジのプロパティです。
投稿のノードに複数のカテゴリー、複数のスクリプトとそれぞれ紐づいていると管理しづらいですよね。カテゴリーのノードを取得するために、すべてのノードを走査しなければいけません。
それでは効率が悪いので、それぞれのノードの種類ごとを管理するコネクションを間に挟むことで管理しやすくしています。カテゴリーはカテゴリーコネクションを介して、スクリプトはスクリプトコネクションを介して取得することで走査するノードを減らすことができます。
さらにcursorとconnectionを合わせることでページネーションを解決することができます。
ページネーション
WPGraphQLはワードプレスのクエリのように「posts_per_page」「page」を指定して取得するページネーションは利用できません。その代わり先ほどのcursorとconnectionを利用して、ページネーションを解決します。
実際のクエリーは下記のようになります。投稿ノードへのエッジリストから10件取得しています。afterで指定したカーソルの後ろから10件です。
query posts {posts(first: 10, after: "endCursor") {pageInfo {hasNextPagehasPreviousPagestartCursorendCursor}edges {cursornode {titleslug}}}}
pageInfoはページの情報を保有しています。
- hasNextPage ・・・ boolean / 次のページはあるか
- hasPreviousPage ・・・ boolean / 前のページはあるか
- startCursor ・・・ String / 取得した最初のエッジのカーソル
- endCursor ・・・ String / 取得した最後のエッジのカーソル
少しイメージがしにくいので「posts_per_page」「page」のような取得方法と比較してみてみましょう。
カーソルを取得したページネーションではstartCursorやendCursorに指定したカーソルは含まれません。指定したカーソルより前/後を取得します。
最初のページではカーソルの指定はしません。(例: first=2 endCursor=null)ページネーションを行う際に取得したカーソルを指定して取得します。
取得サンプル
実際にサイトを作成する上での取得サンプルを紹介します。
WPGraphQLをインストールすると管理画面に「GraphQL IDE」が表示されます。そこに入力して実行してもらうとわかりやすいです。
アーカイブページ用投稿一覧を取得
投稿の「タイトル」「抜粋」「スラッグ」「投稿日」「更新日」「カテゴリー」「タグ」「画像(サムネイルサイズ)」を取得します。
query getPosts( $first: Int, $last: Int, $after: String, $before: String ) {posts(first: $first, last: $last, after: $after, before: $before) {pageInfo {hasNextPagehasPreviousPagestartCursorendCursor}edges {node {titleexcerptslugdatemodifiedcategories {nodes {uriname}}tags {nodes {uriname}}featuredImage {node {sourceUrl(size: THUMBNAIL)}}}}}}
{"first": 10,"last": null,"after": null,"before": null}
デフォルトでは投稿日降順ですので、下記変数を与えると新しいものから10件取得します。
2ページ目を取得する
2ページ目を取得する場合、afterにendCurosrの値を指定します。
{"first": 10,"last": null,"after": "YXJyY〇〇〇=","before": null}
2ページ目から1ページ目に戻る
2ページ目から1ページ目に戻る場合、startCursorをbeforeに設定し、lastに取得数を設定して取得します。
{"first": null,"last": 10,"after": null,"before": "YXJyY△△△="}
投稿ページ用の取得
スラッグを指定して投稿を取得しています。「スラッグ」「生地内容」「投稿日」「更新日」「投稿者情報」「画像(medeum_largeサイズ)」「カテゴリー」「タグ」「コメント」「スクリプト」「スタイル」を取得しています。
idTypeを変更することで、投稿ID・グローバルID・URIでも取得できます。
query getPost {post(id: "(slugを指定)", idType: SLUG) {slugcontentdatemodifiedauthor {node {nameuridescription}}featuredImage {node {sourceUrl(size: MEDIUM_LARGE)}}categories {nodes {nameuri}}tags {nodes {nameuri}}comments(first: 10) {pageInfo {hasNextPageendCursor}nodes {idparentIdauthor {node {avatar {url}name}}datecontent}}enqueuedScripts(first: 100) {nodes {handlesrc}}enqueuedStylesheets(first: 100) {nodes {handlesrc}}}}
メニューを取得
メニューを取得します。
下記では各メニュー名とそのメニューに属するメニューの「ラベル」「uri」を取得します。
query getMenus {menus {nodes {namemenuItems(first: 100) {nodes {labeluri}}}}}
アドオン
WordPressのコアに関する部分は上記の例でおおむね解決できると思います。しかし、コアだけを使用している人は少なく、プラグイン等で拡張していることが多いのではないでしょうか。
多くの人が利用しているプラグインに対応するための拡張プラグインがすでに多く存在しています。自分ですべて書くのは大変ですので、拡張プラグインがあるものは利用していきましょう。
Advanced Custom Fields(ACF)
Advanced Custom Fieldsで作成したカスタムフィールドをWPGrapghQLで利用できるようにするプラグインです。
ACFの設定画面でWPGraphQLの有効化設定とWPGraphQLのフィールド名を入力することで利用可能です。
フィールド名はWordPressでは通常スネーク形式が利用されます。一方GraphQLではキャメル形式を使用します。その辺りも考慮されていて、このプラグインではスネーク形式で設定したフィールドはWPGraphQLで取得する際には自動でキャメル形式で取得できるようになります。
JWT Authentication
JWT認証が可能になるプラグインです。
この記事ではQueryのみで触れていませんが、Mutationを使用する場合やプレビューを利用する場合、その他ログインしないと表示できない情報(ユーザーメールアドレスなど)を利用する場合にはおすすめです。
・利用の流れログイン情報でトークン発行→AuthorizationヘッダーにBearer で指定→クエリ実行
Yoast SEO
Yoast SEO で設定した項目をWPGraphQLで取得できるようにするプラグインです。
投稿ページなどでメタタイトル、メタディスクリプション、各種SNS用表記、canonical、noindex、nofollowなどの設定を取得できます。
offsetによるページネーション拡張
WPGraphQLでoffsetを利用できるようにするプラグインです。
本記事では紹介していませんが、Connectionの引数にwhereを指定できます。このプラグインではwhereの拡張、pageInfoを拡張しています。
総数もpageInfoに含まれるので「△△~××件/〇〇件中」みたいなのを作成する際に便利です。また、総数を任意の取得数で割れば、総ページ数も出せますので、ページネーションボタンのバリエーションも増えます。
Custom Post Type UI(CPTUI)
カスタム投稿タイプやカスタムタクソノミーを作成できるプラグインです。こちらのプラグインをWPGraphQLで利用するために新しくプラグインを導入する必要はありません。
こちらのプラグインはWPGraphQLが有効になっている場合、設定下部に「Show in GraphQL」というメニューが表示されます。それを「True」にするだけでWPGraphQLで取得できるようになります。
フックを利用したカスタマイズ
アドオンではプラグインを利用した部分をWPGraphQLで利用できるようにするプラグインの紹介でした。この項目ではフックを利用して挙動を変えたい場合のカスタマイズ方法を紹介します。
投稿のカスタムフィールドを取得する
ACFを利用せず自分で実装したカスタムフィールド値を取得したい場合、フックを利用してフィールドを拡張することで取得できます。
下記の例ではカスタムフィールドのキー「memo」に入力されたテキストをWPGraphQLで取得できるようにしています。
add_action( 'graphql_register_types', function() {register_graphql_fields( 'Post', ['memo' => ['type' => 'String','description' => 'メモ欄に入力された内容','resolve' => function( $post, $args, $context, $info ) {$memo = get_post_meta( $post->databaseId, 'memo', true );return $memo ? $memo : null;}]] );} );
各タイプが初期化された時に呼び出される「graphql_register_types」フックを利用して登録します。
「register_graphql_fields」関数はフィールド追加できる関数です。第一引数にフィールドを追加したいタイプを指定します。第二引数では登録するフィールドの情報を指定します。
追加する情報の詳細はWPGraphQLの元となるgraphql-phpで確認するとわかりやすいです。
上記を追加した上で投稿の情報を取得するとしっかりと取得できることが確認できます。descriptionに追加した内容もDocsに反映されています。
取得できる数を変更する
デフォルトの設定では一度に最大100件しか取得できません。
下記のように1000件の投稿を取得してみようとします。
query getPosts {posts(first: 1000) {edges {node {title}}}}
しかし、取得できるのは100件までです。
これはサーバーに不可がかかりすぎないようにするための設定です。ページネーション等利用して取得すれば問題ありません。
例外としては、Next.js等でSSG・ISRを利用する際にスラッグを全部取得したい場合などでしょう。そういう時に取得できる最大数を変更します。
下記ではpostsフィールドの時のみ最大数を500にしています。
add_filter( 'graphql_connection_max_query_amount', function ( int $max_amount, $source, array $args, $context, $info ) {if ( empty( $info->fieldName ) ) {return $max_amount;}if ( 'posts' !== $info->fieldName ) {return $max_amount;}return 500;}, 10, 5 );
まとめ
WordPressで運用しているサイトheadlessとして、フロントをjavascriptベースにするなどしたい場合は使用を検討してみると良いと思います。拡張も簡単にできるので、ブログとしてはもちろんのこといろいろなケースで利用できそうです。
vercelにNext.jsとWordPressのサンプルがあるので、試してみてください。個人の利用であればデプロイも無料可能です。
GraphQLについて体系的に学びたい方は書籍「O’REILY 初めてのGraphQL」がおすすめです。