いよいよ始めるGraphQL
アジェンダ
- GraphQLとは
- GraphQL基本API
- DEMO
RESTful APIの問題点
RESTful APIの問題点
- 不要なデータを取ってくる(over-fetching)
- テンプレートに足りないデータが存在する(under-fetching)
- 複雑なデータに対応できない、複数エンドポイントへのリクエストが必要
- 柔軟性がない(場合がある)
- 直感的でない(場合がある)
スキーマファースト
- バックエンド、フロントエンド共通のSchemeを設計する
- 以後は独立して開発できる
- (GraphQLの場合は)RESTより柔軟で、
固有APIより定義をゆるくしたスキーマを定義する
→グラフベースのスキーマ
GraphQL
GraphQL
- http://graphql.org/
- facebook製
- query language
- クエリを実行するためのサーバランタイム
- 強い型付け
- シングルエンドポイントで動作
GraphQLの実装
- JavaScript(Node.js)、Ruby、Python、Scalaなど実装は色々あります
http://graphql.org/code/
GraphQLのAPI
Query and Mutation
Query
- データの取得
- オブジェクトとそのフィールドを取得
- 引数を取ったり、条件文をいれることも(または必須にする)も可能
fields (simple)
{
posts {
title,
summary,
created
}
}
{
"data": {
"posts": [
{
"title": "new blog post",
"summary": "this is new post",
"created": "2017-3-13 20:12:03"
},
{
"title": "second post",
"summary": "this is second post",
"created": "2017-3-14 13:22:35"
},
{...},
{...}
]
}
}
Query
Result
fields
- クエリと結果は同じ形になる
- サーバはクライアントが要求しているフィールドを正確に知っている必要がある(定義されているフィールドしか呼び出せない)
fields (nested)
{
posts {
title,
summary,
created,
author {
name,
mail
}
}
}
{
"data": {
"posts": [
{
"title": "new blog post",
"summary": "this is new post",
"created": "2017-3-13 20:12:03",
"author": {
"name": "ben",
"mail": "ben@blogger.com"
}
},
{
"title": "second post",
"summary": "this is second post",
"created": "2017-3-14 13:22:35",
"author": {
"name": "chris",
"mail": "chris@blogger.com"
}
}
]
}
}
Query
Result
fields
- オブジェクトの内部をさらに参照するクエリ
- RESTだと複数回リクエストが必要な処理も、GraphQLなら1度のリクエストで呼び出し可能
Arguments
- Queryに対して引数をつけることができる
- オプション or 必須
{
posts(count: 1) {
title,
summary,
created
}
}
{
"data": {
"posts": [
{
"title": "new blog post",
"summary": "this is new post",
"created": "2017-3-13 20:12:03"
}
]
}
}
Query
Result
Arguments
- 引数をフィールドに渡すことも可能
- ENUMを渡して、サーバで変換処理を行うこともできる
{
posts(count: 1) {
title,
summary,
created(format: DATE)
}
}
{
"data": {
"posts": [
{
"title": "new blog post",
"summary": "this is new post",
"created": "2017/3/13"
}
]
}
}
Query
Result
Aliases
- 取得したデータに対して別名をつけることが可能
- 同じフィールドを別のデータとして取得したい場合などに利用
{
posts {
title
},
authorNames: authors {
name
},
authorMails: authors {
mail
}
}
{
"data": {
"posts": [
{"title": "new blog post"},
{"title": "second post"}
],
"authorNames": [
{"name": "ben"},
{"name": "chris"}
],
"authorMails": [
{"mail": "ben@blogger.com"},
{"mail": "chris@blogger.com"}
],
}
}
Query
Result
Fragments
- 再利用可能なユニットを定義し、クエリに含めることができる
- 複雑なデータを(UIコンポーネントベースなど)分割して定義することでクエリを容易にするのが目的
- fragment [fragmentName] on Type {} で定義
- ...[fragmentName] で呼び出し
Fragments
{
jan: posts(month: 1) {
...blogData
},
feb: posts(month: 2) {
...blogData
},
mar: posts(month: 3) {
...blogData
}
}
fragment blogData on Posts {
title,
body,
author {
name
}
}
{
"data": {
"jan": [{
"title": "January post",
"body: "...",
"author": {
"name": "ben"
}
}],
"feb": [{
"title": "February post",
"body: "...",
"author": {
"name": "chris"
}
}],
"mar": [{
"title": "March post",
"body: "...",
"author": {
"name": "ben"
}
}],
}
}
Query
Result
Variables
- クエリの外側から(動的な)値を渡す方法
-
queryを使って関数のようなものを定義
-
引数として$を接頭辞にした引数(と、その型)を定義
-
queryの中にある$xxxへ引数が渡される。
-
variablesはキーバリュー型となる
Variables
query getPosts($count: Int!){
posts(count: $count) {
title,
summary,
created
}
}
{
"data": {
"posts": [
{
"title": "new blog post",
"summary": "this is new post",
"created": "2017-3-13 20:12:03"
},
{...},
{...}
]
}
}
Query
Result
Variables
{
"count": 3
}
Variablesの型
- 先頭が$付きの変数で、コロンを挟んで型が入る
- 型はスカラー型、Enum、オブジェクト型のいずれか
Operation name
getPosts の部分
- プログラムの関数名のように名前をつけることができる
query getPosts($count: Int!){
posts(count: $count) {
title,
summary,
created
}
}
Directives
-
Variablesなどを用いて、動的にクエリのFieldsを減らしたり、増やしたりする方法
-
@include(if: Boolean) でifがtrueのときフィールドを追加
-
@skip(if: Boolean) ifがtrueのときフィールドを除去
query getPosts($noSummary: Boolean){
posts {
title,
summary @skip(if: $noSummary),
created
}
}
Inline Fragments
- オブジェクト型によって出力するフィールドを変更する方法
query getComment {
comments {
name,
comment,
... on FacebookUser {
realName
}
... on TwitterUser {
account
}
}
}
{
"data": {
"comments": [
{
"name": "Brett",
"comment": "...",
"realName": "Brett Reed"
},
{
"name": "Matt",
"comment": "...",
"account": "@matt"
}
]
}
}
Query
Result
Mutation
- データの更新・作成・削除
- 更新後のデータを受け取る
Mutation
- mutationキーワードを指定
- サーバで処理された後の結果のフィールドを所得
mutation {
createAuthor(
name: "tim",
mail: "tim@blogger.com"
) {
_id
name
}
}
{
"data": {
"createAuthor": {
"_id": "tim",
"name": "tim"
}
}
}
Query
Result
Mutation
- 複数のmutationも可能
- 処理はシリアルで実行される
mutation {
"tim": createAuthor(
name: "tim",
mail: "tim@blogger.com"
) {
_id
}
"fred": createAuthor(
name: "fred",
mail: "fred@blogger.com"
) {
_id
}
}
{
"data": {
"tim": {
"_id": "tim"
}
"fred": {
"_id": "fred"
}
}
}
Query
Result
Schemas and Types
Schemas and Types
- どのオブジェクトがどのフィールド・型を持っているか
- GraphQL schema languageという共通の仕様
- いわゆるモデルのようなもの
GraphQL Object Type と Field
type Post {
title: String!
summary: String
body: String
created: String
author: Author
}
Scheme
実際のSchemaの定義
GraphQL Object Type と Field
-
Postは GraphQL Object Typeでいわれるオブジェクト型、幾つかのフィールドを持つことができる。
-
title, summary, body, created, author はフィールド
-
StringはGraphQLで定義できる、スカラー型のひとつ。!をつけることでnon-nullableであることを示す
-
[Episode]は Episode型の配列であることを定義。上記同様に!はnon-nullableを示す。ただこの場合は0個またはそれ以上の配列となる
Arguments
type BlogSchema {
posts(count: Int! = 10): [Post]
}
Scheme
Arguments
- GraphQLオブジェクト型のすべてのフィールドは引数を持つことができる。
- 引数にはすべて名前をつける必要がある
必須でない場合はオプションとしてデフォルト値も定義できる - 前項の場合はcountがない場合は10がデフォルト値として利用される
QueryとMutation型
- スキーマの大本にはQueryとMutationが2つある(最上位はこれしかない)
- Queryはすべてのスキーマが持ち、
Mutationはなくてもよい
{
type Query {
...
}
type Mutation {
...
}
}
Types
Scala types
- スカラ型には以下の種類がある
Int, String, Float, String, Boolean, ID - ID... オブジェクトを再フェッチするときや、キャッシュのためのキーとして利用される文字列
Custom Scala types
- 独自の方を定義
- 以下は、カスタムスカラ型として、Date型を定義した例
(内容は実装によって異なる)
scalar Date
Enum
- 値が指定した値のいずれかであることを定義した型
enum Category {
JAVA
JAVASCRIPT
NODEJS
PYTHON
}
List and requirement
- []で括ることにより配列として定義できる
- !で必須として定義できる
type Post {
title: String!
comments: [Comment]!
}
Interface
- 共通のschemeに対しインターフェイスを定義することも可能
- Interface、implements キーワードを使って定義
interface HasAuthor {
author: Author
}
type Comment implements HasAuthor {
comment: String
}
type Post implements HasAuthor {
title: String
body: String
}
Other types...
- Union types
- 複数のオブジェクト型を跨いで値を返す?
- Input types
- mutationなどで利用する複雑なオブジェクト型を定義する
実装とDemo
インストール
$ npm i express graphql body-parser --save
// index.js
const express = require('express');
const app = express();
const PORT = 3000;
const server = app.listen(PORT, function () {
const host = server.address().address;
const port = server.address().port;
console.log('listening at http://%s:%s', host, port);
});
index.js
$ node index.js
const graphql = require('graphql');
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = graphql;
const Query = new GraphQLObjectType({
name: 'baseSchema',
fields : {
ping: {
type: GraphQLString,
resolve: () => 'pong'
}
}
});
// const Mutation = new GraphQLObjectType({...});
const schema = new GraphQLSchema({
query: Query
//, mutation: Mutation
});
module.exports = schema;
schema.js
スキーマを定義
const graphql = require('graphql');
const bodyParser = require('body-parser');
const schema = require('./schema.js');
// POSTのbodyをtextとしてパース
app.use(bodyParser.text({ type: 'application/graphql' }));
app.post('/graphql', (req, res) => {
// GraphQLを実行
graphql.graphql(schema, req.body).then((result) => {
res.send(JSON.stringify(result, null, 2));
});
});
リクエストされたqueryを処理 (index.js)
$ curl -XPOST -H "Content-Type:application/graphql" \
-d "query baseSchema { ping }" http://localhost:3000/graphql
チェック
{
"data": {
"ping": "pong"
}
}
便利なライブラリ
- GraphiQL
-
GraphQLのSchemaを可視化する+Sandboxとして利用できるReactのコンポーネント
-
GraphQL HTTP Server Middleware
-
expressでGraphQLを定義しやすくするミドルウェア
-
上記のGraphiQLを内包している
続きは
まとめ
- メリット
-
Just in Timeなデータを取得できる
-
クライアント側(UI側)の変更に対して、サーバ側でAPIを変えなくてもいい
-
クライアントが定義情報を知っているから、バージョン管理は(あんまり)しなくてもいい
-
APIドキュメントも自動生成できる
-
まとめ
- デメリット
-
学習コスト(特にサーバ側の!)
-
どのくらいの規模に耐えうるか?(複雑なスキーマが発生してメンテナンス性が下がったりするか)
-
開発パラダイムのシフト(特にサーバ側の!)
-
その他
- Relay https://facebook.github.io/relay/
- ReactとGraphQLを繋ぐやつ
- Fluxっぽい
- apollo-client http://dev.apollodata.com/react/
- ReactとGraphQLを繋ぐやつ
- Relayより使いやすいらしい
- React以外にもAngularとかiOSもある
- Falcor http://netflix.github.io/falcor/
- netflix製のGraphQLみたいなもの
END
いよいよ始めるGraphQL
By Kyohei Rampage Tsukuda
いよいよ始めるGraphQL
- 1,606