この記事では、GraphQL(グラフQL)の概要やメリット、クエリのしくみや解決方法、サンプルでわか GraphQL のサーバー構築方法などをわかりやすく解説していきます。
ガイドの目次
- GraphQLとは?
- GraphQL のメリット
- GraphQL クエリのしくみ
- GraphQL クエリの解決方法
- GraphQL を既存のバックエンドと組み合わせて使用する
- Node.js でシンプルな GraphQL サーバーを構築する
- まとめ
GraphQLとは?
GraphQL(グラフQL)とは API のために作られたクエリ言語であり、既存のデータに対するクエリを実行するランタイムです。理解できる完全な形で API 内のデータについて記述します。GraphQL を利用すれば、クライアント側から必要な内容だけを問い合わせられると共に、漸次的に API を進化させることが容易になり、強力な開発者ツールを実現できます。
GraphQL の公式サイトには上記のように説明されていますが、もっと簡潔にまとめることもできそうです。そこで GraphQL を 1 文で表現することに挑戦してみました。ただし、これは公式の定義ではなく、あくまでも私の理解に基づいていますのでご注意ください。
-
GraphQL は、クライアント アプリケーションが必要なデータを API からフェッチするために設計された言語です。
-
GraphQL を使用すると、クライアント アプリケーションはバックエンド API から必要なデータの型とシェイプを取得できます。
-
GraphQL では、どんなタイプのリクエストでも、クライアント アプリケーションが呼び出すエンドポイントは 1 つだけです。
-
GraphQL は SQL とよく似ていますが、フロントエンドで機能します。
私なりにシンプルな表現を目指してみましたが、この洗練されたテクノロジーは端的に言い表せるようなものではないようです。とはいえ、それぞれの文は GraphQL の特徴をよく捉えていると思います (もし面接だったら、最後の 1 つは言わないほうがよさそうですね)。これが GraphQL の大まかな機能です。この記事を最後まで読めば、GraphQL についてさらに深く理解し、実際に活用できるようになるでしょう。
さて、まず GraphQL はどのようにして生まれたのでしょうか。このパラダイム シフトとも呼べるイノベーションの背景を見ていきます。
GraphQL は、Lee Byron 氏、Dan Schafer 氏、Nick Schrock 氏のエンジニア 3 名による Facebook のプロジェクトとして始動しました。
2012 年 2 月に最初のプロトタイプが完成し、同年 8 月ころに Facebook iOS アプリケーションに組み込まれました。このテクノロジー自体は 2015 年までオープンソースではありませんでした。
このモバイル アプリケーションを担当する開発者は、ネストされたデータや相互にリンクされたデータを大量に扱います。アプリケーションのパフォーマンスを高めるには、ニュース フィード、メッセージング、Facebook ウォール投稿とそのコメントや「いいね!」、投稿へのコメントの「いいね!」などのモジュールを提供するために必要なデータ シェイプだけを照会しなければなりません。これは厄介な状況ですよね。
そこで同社は GraphQL の原型となるテクノロジーによって、上記のような問題を解決したのです。Facebook にとっては有効な手段だったようですが、では私たちにはどんなメリットがあり、どのように役立つのでしょうか。
GraphQL のメリット
たとえば、複数のユーザーが単一のブログ API を使用して記事を投稿している場合を例に挙げてみます。この API では以下のコレクションとよく似たデータを取得することができます。
[
{
id: 1,
name: "Fikayo Adepoju",
email: "fik4christ@yahoo.com",
posts: [
{
id: 1,
title: "Debugging an Ionic Android App Using Chrome Dev Tools",
published: true,
link:
"https://medium.com/@coderonfleek/debugging-an-ionic-android-app-using-chrome-dev-tools-6e139b79e8d2",
author: 1
},
{
id: 2,
title: "Hosting a Laravel Application on Azure Web App",
published: true,
link:
"https://medium.com/@coderonfleek/hosting-a-laravel-application-on-azure-web-app-b55e12514c46",
author: 1
}
]
},
{
id: 3,
name: "Jane Paul",
email: "jane@company.com",
posts: []
}
];
ユーザー データには以下のプロパティが定義されています。
- id
- name
それぞれの記事には以下のプロパティが定義されています。
- id
- title
- published (記事が公開されたかどうかを示すブール値)
- link (記事へのリンク)
- author (ユーザーの ID)
さて、ここで以下の 3 つのフロントエンド コンポーネントを構築する必要があるとしましょう。
- ユーザーの情報を表示する Profile コンポーネント
- 記事、記事リンク、著者名を表示する Post コンポーネント
- ユーザーの詳細情報とそのユーザーが投稿した記事のタイトルを表示する Author コンポーネント
これらのコンポーネントには、それぞれ異なったデータ シェイプが必要です。従来の REST API では、データ シェイプごとに固有のエンドポイントが必要でした。それがない場合は、ごちゃごちゃと多数のクエリ パラメーターをエンドポイントに追加する必要がありました。たとえば、フロントエンドでユーザー データをリクエストするとき、ユーザーの id と email だけ (name は除外)、またはユーザーの name だけを返すように指定することはできませんでした。REST API はユーザー データをすべて返します。このため、ユーザー データに上記の例よりも多くのパラメーターが含まれている場合には、その分だけ処理が面倒になってしまいます。
「ユーザー データがすべて返されるだけで、そんなに手間になるかな?」と首を傾げている方もいらっしゃるかもしれません。ありきたりな回答になってしまいますが、それはケースバイケースです。
今度は、ブログの著者全員の名前だけを確認したい場合を考えてみます。著者が 50 人いて、そのページをモバイル アプリケーションで閲覧するとしたらどうなるでしょう。著者名を確認するためだけに、50 人分のユーザー データのすべてがアプリケーションにダウンロードされてしまい、かなりのコストがかかります。
GraphQL なら、クエリのデータ シェイプをクライアントで制御できます。ユーザーは以下のように指示するだけでよいのです。
ユーザー データから名前だけを取得して。よろしく。
すると GraphQL API は著者の名前だけを含むユーザー データのコレクションを返します。この処理は単一のエンドポイントで実行されます。REST と異なり、GraphQL API がエクスポーズ するエンドポイントは 1 つだけです。このエンドポイントが、クライアントからのすべてのリクエストに対応します。
こんな魅力的な説明を読んだら、すべての API を GraphQL で書き直したい衝動に駆られるでしょう。フロントエンド開発者の皆さんは、API 開発者に今後 GraphQL API だけを作るように迫りたくなっているかもしれません。でもいったん落ち着いて。もう少し詳しく学んでいきましょう。
GraphQL クエリのしくみ
ここまでで GraphQL がクライアントの言語であることはご理解いただけたと思います。では、クライアントはどのようにこの言語を処理するのでしょう。GraphQL のリクエスト送信にはどういったフォーマットが使用されるのでしょうか。
リクエストには、データを記述するための特別なフォーマットが使用されます。このフォーマットについて説明するには、実際に GraphQL クエリを作成してみるのが一番の早道です。そこで、前のセクションで紹介した 3 つのコンポーネントに関するデータをリクエストするクエリを作成してみましょう。
ユーザーの情報を表示する Profile コンポーネント
このコンポーネントにはユーザーの情報が必要です。GraphQL でのリクエストは以下のようになります。
{
user(id : 1){
Id
name
email
}
}
このリクエストでは、ユーザーの ID を指定して GraphQL エンドポイントに問い合わせているため、そのユーザーの ID、名前、メール アドレスが返されます。
記事、記事リンク、著者名を表示する Post コンポーネント
このコンポーネントには、特定の記事とその著者名が必要になります。
{
post(id : 2){
title
link
author {
name
}
}
}
上記のクエリでは、記事 ID を送信することで、1 件の記事のデータをリクエストしています。結果として、その記事のタイトル、リンク、著者名が返されます。
ユーザーの詳細情報とそのユーザーが投稿した記事のタイトルを表示する Author コンポーネント
このコンポーネントには、ユーザーの情報と、そのユーザーが投稿した記事のタイトルが必要になります。
{
user(id : 2){
email
name
posts {
title
}
}
}
このクエリは、あるユーザーのデータと、そのユーザーが投稿したすべての記事のデータを要求します。ただし、記事データのうち取得するのはタイトルのです。そのユーザーが投稿した記事の行列が返されますが、各 post オブジェクトには記事タイトルのみが含まれます。
ここで 、私がひいきにしているライターの Andrew Mead 氏が作成した GraphQL デモを使用して、クエリを試してみましょう。このデモも、ユーザーとその投稿記事をエクスポーズする API に基づいています。
ユーザー全員のデータと、各ユーザーが投稿した記事のタイトルを照会します。解答を見る前に、まず自分でクエリを書いてみてください。
{ users{ id name email posts{ title } } }
いかがでしたか? GraphQL API は自己文書化されていて、不正なクエリにフラグを立て、参考になるエラー メッセージを返してくれるので便利です。
このクエリを GraphQL プレイグラウンド https://graphql-demo.mead.io/ の左側のウィンドウに貼り付け、再生ボタンをクリックしてクエリを実行します。以下のような結果が返されるはずです。
うまく行きましたね!
これで、GraphQL の大まかなしくみをご理解いただけたと思います。クエリ ウィンドウにさまざまなクエリを貼り付けて、GraphQL でどのようにデータが取得されるのか試してみてください。画面の右端に [SCHEMA] という緑色の小さなタブがあります。このタブを開くと、API がエクスポーズするデータの構造を確認できます。
GraphQL クエリの解決方法
この記事の冒頭で、GraphQL の特徴について簡単に説明したのを覚えていますか。その中で以下の特徴についても軽く触れました。
GraphQL は、バックエンド API とやり取りできる
つまり、私たちの API は GraphQL クエリを読み、理解できるということです。
そして嬉しいお知らせがあります。
GraphQL API の実装に必要な処理は、ほとんどがバックエンドで行われます。
バックエンド開発者の皆さんにはごめんなさい。
必要に応じてクライアントからクエリを実行してデータを取得できるようにするには、バックエンドから GraphQL エンドポイントをエクスポーズしなければなりません。
これは、どのように行えばよいのでしょうか。
バックエンドで、クライアントに提供するデータをエクスポーズするインターフェイスを作成する必要があります。
ブログ API の例で考えてみましょう。ユーザーに関するデータと記事に関するデータがあります。この 2 つは別々のエンティティです。GraphQL では、スキーマを作成してこの 2 つを定義します。
GraphQL では、エンティティ (独立したデータの断片のこと。モジュールと言ってもかまいません) を型で表します。そこで、スキーマ内に User 型と Post 型を定義します。型を定義するには、その型のクライアントに提供されるプロパティをリストにします。以下に User 型と Post 型を定義します。
type User {
id: Int!
name: String!
email: String
posts: [Post!]
}
type Post {
id: Int!
title: String!
published: Boolean!
link: String
author: User!
}
詳しく見ていきましょう。型はキーと値のペアで定義します。キーはエクスポーズしたいプロパティ、値は標準の GraphQL データ型またはカスタム型です。
GraphQL には複数のデフォルト型が付属しています。最も一般的なのはスカラー型です。以下の 5 つのスカラー型を使って、API が返すプロパティのデータ型を定義することができます。
- ID: 一意の識別子を使ってフィールドを定義する
- Int: 符号付き 32 ビット整数
- Float: 符号付き倍精度浮動小数点数
- String: UTF-8 文字シーケンス
- Boolean: true または false
カスタムのスカラー型も定義できますが、高度な内容になるので今回は取り上げません。
先ほど定義した User 型と Post 型はカスタム型です。User 型の posts プロパティが、カスタム型である Post 型に設定されています。これは、ユーザーが投稿した記事の行列を返します。また Post 型の author プロパティは、記事を投稿した著者の詳細を返します。
感嘆符 (!) は null を許容しないフィールドであることを示します。感嘆符が付いていないフィールドは null を返すことができます。
これらの型を照会するには、GraphQL のデフォルト型の 1 つである Query 型を定義する必要があります。
Query 型を使って、照会するデータ ポイントを定義できます。たとえば、次のようなクエリを作成するとします。
{
users {
name
email
}
}
Query 型の内部に users のデータ照会ポイントが定義されています。Query 型は一般的に以下のように定義します。
type Query {
users: [User!]!,
user(id: Int!): User!
}
この定義では、2 つの照会ポイントをエクスポーズします。まず、users によってユーザーのコレクションがフェッチされます。そして user によって、指定した ID のユーザー 1 名がフェッチされます。
この説明についてはひとまず問題ないとしても、どうやってこれらの照会ポイントをデータ ソースに接続するのか、クライアントがリクエストしたプロパティだけを返すにはどうしたらよいのかといった疑問が湧いてきていることでしょう。照会ポイントをデータ ソースに接続するには、GraphQL のもう 1 つのコンセプト「リゾルバ」を使用します。そして、クライアントがリクエストしたプロパティだけを返す処理は、GraphQL が自動的に行うため、心配する必要はありません。
リゾルバは、照会ポイントをマッピングして、リクエストされたエンティティを返す関数です。
たとえば、データ ソースがユーザーの行列である場合、users 照会ポイントのリゾルバは次のようになります。
function () {
return usersArray;
}
user 照会ポイントのリゾルバは次のようになります。
function ({ id }){
return usersArray.find((user) => user.id == id);
}
構文にはそれほどこだわる必要はありません。リゾルバ関数の書き方はプログラム言語によって異なります。
ここまでの内容をまとめておきます。
- データ エンティティ/モデルごとにカスタム型を定義する。
- Query 型を使うと、必要に応じてさまざまな照会ポイントをエクスポーズできる。
- リゾルバを使うと、各照会ポイントのクエリを解決できる。
そろそろ皆さんは「これを PHP、Node.js、Python、その他のバックエンド言語で実行するにはどうしたらよいの?」と思っているころでしょう。安心してください。次のセクションで詳しくご説明します。
GraphQL を既存のバックエンドと組み合わせて使用する
現時点ではまだ以下についての説明が完了していません。
- これらのコンセプトをバックエンド言語で実装する方法
- 単一の GraphQL API で各種の型、Query 型、リゾルバを使用する方法
- すべてのクエリに答える魔法のような単一のエンドポイントをエクスポーズする方法
GraphQL は言語に依存しません。ここまでのセクションで紹介したコンセプトは、特定の言語に依存しないため、GraphQL をサポートするあらゆる言語に適用できます。
実装に関しては、一般的なバックエンド言語向けに、GraphQL API の実装用のライブラリが用意されています。
C#、Node.js (JavaScript)、Go、PHP、Java のようなバックエンド言語/フレームワークに対応するライブラリは、GraphQL 公式サイトの サーバー ライブラリ ページに記載されています。
これらのライブラリと、ここまでのセクションで学んだコンセプトを組み合わせることで、GraphQL サーバーをすぐに利用できるようになります。
Node.js でシンプルな GraphQL サーバーを構築する
Node.js を使用してシンプルな GraphQL サーバーを構築しましょう。Node.js は静的データ ストアを使用します (本番環境ではほとんどの場合、データベースのデータです)。
この演習の要件は、システムに Node.js (NPM に付属) がインストールされていることだけです。
さっそく始めましょう。まず、プロジェクトのディレクトリを作成します。
mkdir graphql-server
次に、プロジェクトのルートに移動し、以下のコマンドを実行して、package.json ファイルのスキャフォールディングをすばやく行います。
npm init -y
npm の以下の 3 つのパッケージが必要です。
- Express: シンプルな Node.js サーバーを作成する
- GraphQL: Node.js の GraphQL サーバー ライブラリ
- Express-GraphQL: GraphQL サーバーを作成するための Express ミドルウェア
以下のコマンドを実行してこれらのパッケージをまとめてインストールします。
npm install express graphql express-graphql
インストールが完了したら、GraphQL サーバーを構築します。
まず、静的データ ストアを作成してエクスポートします。プロジェクトのルートに data.js という名前のファイルを作成して、以下のコードを貼り付けます。
/* data.js */
const Users = [
{
id: 1,
name: "Fikayo Adepoju",
email: "fik4christ@yahoo.com",
posts: [
{
id: 1,
title: "Debugging an Ionic Android App Using Chrome Dev Tools",
published: true,
link:
"https://medium.com/@coderonfleek/debugging-an-ionic-android-app-using-chrome-dev-tools-6e139b79e8d2",
author: 1
},
{
id: 2,
title: "Hosting a Laravel Application on Azure Web App",
published: true,
link:
"https://medium.com/@coderonfleek/hosting-a-laravel-application-on-azure-web-app-b55e12514c46",
author: 1
}
]
},
{
id: 3,
name: "Jane Paul",
email: "jane@company.com",
posts: []
}
];
module.exports = {
Users
};
このファイルは、ユーザー データのコレクションと各ユーザーの記事をエクスポートします。
次に、スキーマを構築してエクスポートします。プロジェクトのルートに schema.js という名前のファイルを作成して、以下のコードを貼り付けます。
/* schema.js */
const { buildSchema } = require("graphql");
const schema = buildSchema(`
type Query {
users: [User!]!,
user(id: Int!): User!
}
type User {
id: ID!
name: String!
email: String
posts: [Post!]
}
type Post {
id: ID!
title: String!
published: Boolean!
link: String
author: User!
}
`);
module.exports = schema;
このファイルでは、Node.js の GraphQL ライブラリにある buildSchema メソッドを使用してスキーマをセットアップします。User 型と Post 型という 2 つのカスタム型を作成し、クエリ定義内で users と user の照会ポイントをエクスポーズします。
次に、これらのクエリを処理するリゾルバを作成します。プロジェクトのルートに resolvers.js という名前のファイルを作成して、以下のコードを貼り付けます。
/* resolvers.js*/
const {Users} = require('./data')
const resolvers = {
users: async (_) => {
return Users;
},
user: async ({ id }, context) => {
return Users.find((user) => user.id == id)
}
};
module.exports = resolvers;
このファイルでは、data.js から Users コレクションをインポートして、users と user の照会ポイントのリゾルバに適切なデータを返します。
次に、スキーマをリゾルバに接続して、GraphQL エンドポイントをエクスポーズします。
プロジェクトのルートに index.js という名前のファイルを作成して、以下のコードを貼り付けます。
/* index.js */
const express = require("express");
const graphqlHTTP = require("express-graphql");
const schema = require("./schema");
const resolvers = require("./resolvers");
const app = express();
app.use(
"/graphql",
graphqlHTTP({
schema,
rootValue: resolvers,
graphiql: true
})
);
const port = process.env.PORT || 4200;
app.listen(port);
console.log(`🚀 Server ready at http://localhost:4200/graphql`);
このファイルでは、ExpressJS アプリケーションを作成し、express-graphql ミドルウェア パッケージを使って、スキーマをリゾルバに接続した後、エンドポイント /graphql で GraphQL API をエクスポーズします。
3 つ目のパラメーター graphiql は true に設定します。これで GraphiQL ツールを利用できるようになります。GraphiQL (i が入ったことにお気付きでしょうか) は、GraphQL クエリをテストするための Web ベースの GUI です。このツールは GraphQL パッケージに含まれています。
最後に、サーバーがポート 4200 でリッスンするように設定します。
ここまでの手順が完了したら、いよいよ GraphQL サーバーを実行します。
プロジェクトのルートで以下のコマンドを実行して、サーバーを起動します。
node index.js
ポート 4200 でサーバーが実行中であることを示すコンソール メッセージが表示されます。
任意のブラウザーで http://localhost:4200/graphql
にアクセスします。
完璧です!
次に、クエリ ウィンドウで以下のクエリを実行します。
{
users {
name
email
posts {
title
}
}
}
再生ボタンをクリックすると、次のような画面が表示されます。
機能のしくみをよく理解できるよう、GraphiQL ウィンドウでさまざまなクエリを自由に試してみてください。
画面の右上隅の [Docs] ボタンをクリックすると、ウィンドウが開き、スキーマが表示されます。ここを見ればスキーマについて理解を深められます。スキーマは、コード作成や API の自動文書化などに使用できる、とても便利なツールです。
まとめ
長旅、お疲れ様でした! これで、GraphQL の基礎知識を把握し、シンプルな GraphQL サーバーを構築できるようになりました。GraphQL には、この記事で取り上げた機能以外にも、新規ユーザーの作成、ユーザー データの編集、新規投稿の保存、結果のページネーションなど、さまざまな用途に活用できます。GraphQL の強力な機能をぜひお試しください。また、GraphQL API に継続的インテグレーションのパイプラインを追加する方法についても、別の記事で紹介しています。GraphQL API の自動テストと Express GraphQL サーバーの Heroku への継続的デプロイメントの記事をご覧ください。
GraphQL の詳細については、GraphQL 公式サイトや、バックエンド言語のサーバー ライブラリをご覧ください。
すばらしいコーディングができますように!
この記事は、The New Stack に掲載された Fikayo Adepoju 氏の寄稿記事です。