このワークショップの目的は参加者に React を使った GraphQL のフロントエンド開発を学んでもらうことです。
いきなりフロントエンドのコードを書く前に、まずは「GraphQL とはどういうものなのか」「どのようなことができるのか」の概要を知ってもらった方が良いと考え、この章を用意しました。GraphQL の概要をご存知の方は読み飛ばして構いません。
GraphQL は Facebook が 2012 年頃から開発を始めたクエリ言語です。2015 年にオープンソース化され、2018 年からは GraphQL Foundation という独立した組織でメンテナンスされるようになっています。
一貫した型システムやそれに付随する静的検証が言語仕様に組み込まれており、 JavaScript や Ruby, Java など様々なプログラミング言語で汎用的に API 通信が行えるように設計されています。
能書きはこれくらいにしておき、実際に GraphQL でクエリを実行しながら見ていきましょう。
まずは GraphQL server を立ち上げてみましょう。本ワークショップでは実装済みの GraphQL Server を使います。
$ cd server
$ npm i
$ npm run seed
$ npm start
上記のコマンドが正常に実行されると、以下のアドレスにアクセスできるようになります:
この画面は GraphQL プレイグラウンドと呼ばれます。
プレイグラウンドの左側はエディタになっています。このエディタ部分に以下のように書いてみて、実行ボタンをクリックしてください。
query {
products {
id
name
}
}
商品のリストが data
として取得できましたか?
query {
products {
id
name
imageURL # <--- 追加
}
}
query {
products {
name
reviews
}
}
これはエラーになってしまいます。
以下の様に、 reviews
に続けて { }
を記述し、その中にフィールド名を書いてから実行してみましょう。
query {
products {
name
reviews {
# オブジェクトについては、下層のフィールド名をきちんと列挙する必要がある
id
commentBody
}
}
}
GraphQL は「どのような検索ができるか」「どのような更新をおこなえるか」について、静的な型と一緒に運用されます。
プレイグラウンドの右側から「Schema」をクリックしてみましょう。
この Server で提供されている GraphQL サービスのスキーマを観ることができます。
type Query {
products: [Product]!
product(id: ID!): Product
}
# 商品。だがし。
type Product {
id: ID!
name: String!
imageURL: String!
description: String!
price: Int!
reviews: [Review!]!
}
# 商品のレビュー
type Review {
id: ID!
star: Int!
commentBody: String!
}
Product
や Review
など type
というキーワードからはじまっている部分はタイプと呼ばれます(正確には "Output Type" だが、通常は「タイプ」と呼ばれることが多い)。type はフィールド名とそのフィールドの値の型で定義されるオブジェクトライクな構造を示しています。
先程ネストの例で見たように、フィールドの type がまた別の type を指すこともありますし、自己参照や循環参照することも許容されています。Query
という名前の type は特殊な意味をもっており「このスキーマで実行可能なトップレベルのクエリ」を意味しています。
ID
や String
は基本型(GraphQL の用語では Scalar 型という)に分類されます。
名前 | 説明 |
---|---|
Boolean |
真偽値 |
Int |
整数値 |
Float |
浮動小数点の数値型 |
String |
文字列型 |
ID |
一意識別子として利用される値の型 |
ID!
のように、型名の後ろに !
をつけると「そのフィールドは絶対に値がある」という意味になります。逆に !
が無い場合は「値が取得できない可能性がある」という意味になります。
[String]
のように、型名を [ ]
で囲むと「囲んだ型を値に持つリスト」という意味になります。 [[String]]
のように二次元リストのようにすることも可能です。
これ以外にも Interface
や Union
, Enum
などの概念も存在しますが、ここでは割愛します。
先程みたように、このサービスでは product
という Query も提供されていることがスキーマから分かりました。今度はこれを実行してみましょう。
type Query {
product(id: ID!): Product
}
query {
product(id: "002") {
name
}
}
product(id: ID!): Product
にあるとおり、 Product
の型名末尾には !
が付いていないことからも「このクエリは null を返すこともある」ということがわかりますね。
存在しない商品 id(e.g. "005"
) に変更すると null となるパターンも確認できます。
実際のアプリケーションでは、クエリ中の商品 id を動的に変更したくなります。例えばアプリケーションの画面 URL /products/:id
から Router Library を使って URL パスパラメータを取り出すようなケースです。
GraphQL には、静的な Query に対して変数を埋め込む機構が用意されています。
query ProductQuery($id: ID!) {
product(id: $id) {
name
}
}
SQL の Prepared Statement と似ていますね。
変数を使う場合、 query ProductQuery
のように、Query に名前をつける必要があります。関数名のようなものです。また変数名は "$" から始まっている必要があります。
Query の変数は JSON として用意します。プレイグラウンドの「Query Variables」をクリックして以下の JSON を記述しましょう。
{
"id": "002"
}
詳細は割愛しますが、以下のように Template String などで動的に GraphQL の Query を組み立てるのは絶対にやめてください 。
const productId = router.query.params.productId;
const query = `
query {
product(id: "${productId}") {
name
}
}
`;
余裕があれば、どのような不都合が生じるか考えてみてください。
ここまでは Query を扱ってきました。
続いて、Server 上の値の変更を扱ってみましょう。
GraphQL では破壊的な操作はすべて Mutation と呼ばれる操作で実行する必要があります。
- Query: 安全な処理. REST では GET
- Mutation: 安全でない、すなわち破壊的な処理. REST での POST/PUT/DELETE/PATCH
文法は Query の場合と同様です。
今回のスキーマには以下で定義された Mutation がありますので、これを実行してみましょう。
type Mutation {
# 指定した商品にレビューを追加する
addReview(productId: ID!, addReviewInput: AddReviewInput!): Review
}
mutation AddReview {
addReview(
productId: "002"
addReviewInput: { commentBody: "すっぱい", star: 1 }
) {
id
commentBody
star
}
}
Mutation 実行後にもう一度 ProductQuery
を実行し、レビューが投稿できたことを確かめてみましょう。