graphql入門
graphqlGraphQLを学びだしたのでメモ。
細かいことは、公式を見れば良いので大まかな流れを書いておく。
概要
GraphQLのQLはQuery Language。つまり、Graph QLは「言語」である。Graph QLの中では「スキーマ言語」と 「クエリ言語」の2種類が定義されている。
スキーマ言語によって、
- どのようなクエリが許されるか
- その結果、どのようなオブジェクトが返ってくるか
が記述される。基本的に、返ってくるオブジェクトはJSONでシリアライズされることが想定されている。
あくまで「言語」の定義なので、それが具体的に何を使って、どうやって実装されているかは定義とは独立である。 実際、JS/TSを始めとしてPythonやGoなどの実装が公式では紹介されている。
サンプル
たとえば下のようなスキーマを考えてみる。
type RandomDie {
numSides: Int!
rollOnce: Int!
roll(numRolls: Int): [Int]
}
type Query {
getDie(numSides: Int): RandomDie
}
まず最初にRandomDie
という名前の型が定義されている。
クエリとしてはgetDie
という名前のエンドポイントが用意されており、
こいつがRandomDieを返すことがわかる。
このスキーマを実装したサーバーには、たとえば以下のようなクエリを投げることができる。
{
getDie(numSides: null) {
roll(numRolls: 10)
x: roll(numRolls: 10)
y: roll(numRolls: 20)
}
}
ポイントは以下のようなところである。
getDie
に引数を渡している。ここに与えられた値から、サーバーはオブジェクトを生成する。例ではnullが渡されており、サーバー側でデフォルト値を埋めてくれることを期待しているgetDie
で生成されたRandomDie
オブジェクトの中から、参照したいプロパティ/メソッドを指定しているroll
メソッドを複数回にわたって呼んでいる。さらにx: roll
などによって別名を付けている
リクエストにパラメータを渡すのはRESTでもパラメータやフォームを使って可能だが、それ以上に複雑なことは基本的にはできない。 この辺に、Graph QLの嬉しさがある。
TypeScriptでの例
上のスキーマを実装したexpressサーバーの例を貼っておく
import express from "express";
import bodyParser from "body-parser";
import morgan from "morgan";
import * as portfinder from "portfinder";
import { graphqlHTTP } from "express-graphql";
import { buildSchema } from "graphql";
const app = express();
app.use(bodyParser.json());
app.use(morgan("combined"));
class RandomDie {
numSides: number;
constructor(numSides: number) {
this.numSides = numSides;
}
rollOnce(): number {
return 1 + Math.floor(Math.random() * this.numSides);
}
roll({ numRolls }: { numRolls: number }): number[] {
const ret = [] as number[];
for (let i = 0; i < numRolls; i++) {
ret.push(this.rollOnce());
}
return ret;
}
}
const schema = buildSchema(`
type RandomDie {
numSides: Int!
rollOnce: Int!
roll(numRolls: Int): [Int]
}
type Query {
getDie(numSides: Int): RandomDie
}`);
const root = {
getDie: ({ numSides }: { numSides: number | undefined }) => {
return new RandomDie(numSides || 6);
},
};
(async () => {
const port = await portfinder.getPortPromise({
port: 3000,
});
console.log("port =", port);
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.listen(port);
})();
ポイント
- expressのハンドラとして
graphqlHTTP
を登録 rootValue
としてroot
オブジェクトを渡すroot
の中に、クエリに対応した関数getDie
を定義
基本、サーバー側が実装するのはこれだけ。 GraphQLの実装ライブラリが、先ほどの例のような、どのプロパティ/メソッドがどれだけ必要かってところは面倒を見てくれる。 便利だ。よくできてる。
また、graphiql: true
をしておくと、デバグ用のUIが使えるようになる