GraphQLとは=「データを柔軟に取り出せるAPI」
大崎です。今回はできるだけ簡単にGraphQLを説明して試します。触ってみたいだけという方はライブデモがあります。
GraphQLは、APIの形式です。Facebookが開発、Mercariがマイクロサービス化するためにバックエンドのAPIとして採用しているそうです1。
でも現在のデファクトのAPI形式はRESTです。なので、よくRESTと比べられます。
ですから先に結論を言っておくと、できることはRESTと大きくは違いません。
ただ、ちょっとデータ取得特化型の柔軟性(クエリが書けるという大きな特徴)を持っています。
APIってそもそも
APIができることは、データを取ってくるだけでなく、以下のようにいろんな仕事ができるのですが、、、
- トランザクションを無事こなす
- 非同期処理をスタートする
ですが、今回はデータ取得に話を絞ります。
(GraphQLはデータ更新のしかけMutation
も持っていますがあまり大事ではないので省略します)
RESTの課題:デフォルトでは柔軟性がついてない
さて、ある目的のためにデータが欲しいとき、APIが提供してくれるデータでは過不足が生じます。
-
目的例
「近くのレストランの名前
を評価順に5個
取得する」 -
提供されているREST API
http://<URL>/restaurants/
市内の全レストランの一覧。
それぞれに場所
と名前
とオーナー
と電話番号
と・・・・・・と従業員
を取得する
5個だけ取るつもりが1000個くらいデータが取れるし、無駄な値がくっついてくる。
時間もかかる。
欲しいものだけを取り出す、ということで思い出すのは下記例文にもあるSQLのようなクエリ言語ですが、残念ながらSQLはAPIとして提供されません。
select restaurant_name from all order by grade limit 5;
ODataというSQL over HTTP API形式がありましたが、「危険」という声がありました2。
SOAPで書けるかもしれません。でもXML書くのめんどくさい
GraphQLの柔軟性=「欲しいデータだけを要求できる」
GraphQLなら、柔軟性があるしデータ形式がRESTと同じJSONで使いやすいです。
RESTのPOSTでクエリを入力するやり方しかないかな、、、と思っていたところに出てきたGraphQL。
完璧とはいえないかもしれません。だってクエリの書き方がちょっと見慣れないですし。
{
restaurants(first: 5, sort: "grade") {
name
}
}
でも、このクエリをカスタマイズすると、データの要求を柔軟に変更し、JSONでデータが取れます。
柔軟性の部分は、詳しくは試しながら説明します。
返り値がJSONなので、Nodeなどで充実したいろんなツールのパワーをフル活用できます。
RESTからデータを取ってくるのと同じようにアプリが書けます!
最小構成でいいからまずはGraphQLを使う
最小セットのGraphQLサーバ=「単なるWebサーバ(DBなし)」
今回はほぼ、素のGraphQLサーバです。
データは日本の市のリストを使います。といっても三鷹市と香取市の2つの市しか入っていません。
機能は一覧を取得する、という1個しかありません。
まだクエリらしいクエリは書けません。
でもまず動かします。
サーバの機能は下記3つです。
- データを保持 (1)
- クエリを受け付け (2)
- クエリにあわせたデータを返す (3)
(1)は何もしなくていいです。いったん仮データをJSONで入れるだけ。
(2)は、apollo-server
やgraphql-yoga
などのGraphQLのありがたいサーバが全部やってくれます
(3)が大事で、「どんなクエリにどんな値を返す」という設定を書くのがresolverです。
準備
今回はTypescriptでアプリを作ります。
環境にnpm
とtsc
が入っていることを前提とします。
今回のGraphQLサーバアプリの名前を付けたフォルダを作ります。npm init
でnode.jsの初期化をします。
graphql-yoga
は今回使うGraphQLサーバライブラリです。
ほかにもapollo-server
などがあります。
あとは3つくらい起動用のツールを入れます。
$ mkdir sample_app
$ cd sample_app
$ npm init
### 途中のmain指定では index.jsではなくてindex.tsを指定してください。
$ tsc --init
$ npm install graphql-yoga --save
$ npm install nodemon ts-node typescript --save-dev
package.json
にscripts
にstart
の行を追加します。package.json
が以下のようになるといいです。
{
"name": "simple_json_graphql",
"version": "0.0.1",
"description": "Simple GraphQL Server from JSON data",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --ext ts,yaml --exec 'ts-node' index.ts"
},
"author": "Hiroyuki OSAKI (https://github.com/onelittlenightmusic)",
"license": "ISC",
"dependencies": {
"graphql-yoga": "^1.16.2"
},
"devDependencies": {
"nodemon": "^1.18.4",
"ts-node": "^7.0.1",
"typescript": "^3.1.1"
}
}
index.tsを作ります
さきほど説明した(1)から(3)を一行ずつ書いてみます。
import { GraphQLServer } from 'graphql-yoga'
// (1)データを保持
const locations = [
{Japanese: "三鷹市",
Prefecture: "Tokyo",
Population: 180797},
{Japanese: '香取市',
Prefecture: 'Chiba',
Population: 85193},
]
// (2)クエリを受け付け
const typeDefs = `
type Location {
Japanese: String
Prefecture: String
Population: Int
}
type Query {
locations: [Location]
}
`;
// (3)クエリにあわせたデータを返す
const resolvers = <any>{
Query: {
locations: () => locations,
},
};
const server = new GraphQLServer({ typeDefs, resolvers })
server.start({port: 4040}, () =>
console.log(`Your GraphQL server is running now ...`),
)
順に説明します
(1)は、べた書きでデータの配列を入れています。
だからDBは使っていません。
このデータはWikipedia3からとってきたもので、PythonでスクレイピングしてJSON化したものです。Pythonスクレイピングはこちらを参照
// (1)データを保持
const locations = [
{Japanese: "三鷹市",
Prefecture: "Tokyo",
Population: 180797},
{Japanese: '香取市',
Prefecture: 'Chiba',
Population: 85193},
]
(2)は、データの型Location
とクエリlocations
を作っています。locations
クエリがリクエストされると配列[Location]
が返るということを意味しています。
GraphQLの型とスキーマの詳しい説明は本家ドキュメントがおすすめ。
// (2)クエリを受け付け
const typeDefs = `
type Location {
Japanese: String
Prefecture: String
Population: Int
}
type Query {
locations: [Location]
}
`;
(3)は、クエリで返す値は、locations
、つまり(1)の値全部ですよ、と言っています。
このように、GraphQLサーバ実装のメインの仕事は、resolverに処理を書くことです。
// (3)クエリにあわせたデータを返す
const resolvers = {
Query: {
locations: () => locations,
},
};
サーバを起動します
$ npm start
GraphQLサーバがlocalhost:4040
で待機すると思うので、ブラウザでアクセスしてください。
graphql-yoga
には、便利なクエリ実行GUIであるgraphql playground
がついていて、デフォルトで起動することになっています。
その画面が見えるはずです。
クエリを実行してみます
GraphQLのクエリはこんな風に書きます。
graphql playground
の左側のクエリ編集ペインに打ち込みます。
{
locations {
Japanese
Prefecture
Population
}
}
打っていて気付いたと思いますが、超強力な補完機能がついています。構文ミスも型不整合もチェックしてくれます。
では、真ん中のボタンを押してみます。
結果は出ましたか?
(1)で入れたデータがそのままJSONで出てきましたね!
柔軟性(欲しいデータだけ要求する)を確認する
ではクエリを変えてみます。
人口データなんかいらない、市の名前と県名だけでいい、という場合を想定します。
#
をPopulation
の前にいれてコメントアウトしてみます。
{
locations {
Japanese
Prefecture
# Population
}
}
実行してみます。
前のクエリでは人口データが入って返ってきていたのですが、今回は入っていません。
つまり、要求した2つのフィールドJapanese
(市の名前)とPrefecture
(県名)だけが返ってきている ことがわかると思います。
GUIからではなくそのままAPIとしても使えるので、このGUIで試しながら、APIを使うアプリを実装できます。
ちなみに、全部の日本の市が入ったGist上のデータ(自作)を参照するように改造するとGithubレポジトリのような形になります。
クエリも一部追加自作していますので良ければご覧ください。
ライブデモ
Herokuに公開し、GraphQLPlaygroundをgraphqlbin上で公開しました。
日本の市を一覧化し、人口、設立日など統計値も加えました。
実はtype
にコメントを書くと、自動補完のときや右のschema
ペインを参照したときに型の情報も表示してくれるんですね!便利です。
総務省からは市区町村コードも公開されていますので、これも随時GraphQL化したいと思います。
他の参考文献
https://qiita.com/hassaku_63/items/d12d36d17cce536ade66
Apollo-server版最小構成デモ