GraphQLに入門する

2.3K Views

September 05, 23

スライド概要

GraphQLの基本、メリット/デメリットなど

profile-image

LIFULL HOME'Sを運営する株式会社LIFULLのアカウントです。 LIFULLが主催するエンジニア向けイベント「Ltech」等で公開されたスライド等をこちらで共有しております。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

GraphQLに入門する 株式会社LIFULL 社内勉強会資料 プラットフォームG 寺井輝 1 1 © LIFULL Co.,Ltd. 本書の無断転載、複製を固く禁じます。

2.

今日の内容 - GraphQLって何 - RESTの話 - RESTと比較したGraphQL - GraphQL知識編 - GraphQL実践編 - 実際に導入する際に - 補足: スキーマ定義をクライアント側から知る - まとめ 2

3.

GraphQLって何 - API向けのクエリ言語とサーバーサイドランタイムの両方を指す言葉 - エンドポイントに対するクエリを行うREST APIとは異なり、クライアント側で必要なデータを具体的に指 定 - ネットワーク使用効率が高く、パフォーマンス的によい - g が開発 - 階層構造を持つグラフとして表現できるのでGraphQL 3

4.

RESTの話 - REST(Representational State Transfer): Webサービスの設計モデルの一つ - リソース指向: ウェブ上のリソースに一意のURIを割り当てて操作 - クライアント側: HTTPメソッド(GET, POSTとか)を使った操作でリクエスト - 飼い主情報問い合わせエンドポイント(/owners) - 飼い主情報確認(/owners/{id} - GET) - 飼い主新規登録(/owners/{id} - POST) - 飼い主情報更新(/owners/{id} - PUT) - 飼い主登録削除(/owners/{id} - DELETE) - ペット情報問い合わせエンドポイント(/owners/{id}/pets) - 飼い主情報確認(/owners/{id}/pets/{id} - GET) - 飼い主新規登録(/owners/{id}/pets/{id} - POST) - 飼い主情報更新(/owners/{id}/pets/{id} - PUT) - 飼い主登録削除(/owners/{id}/pets/{id} - DELETE) 参考: https://qiita.com/jintz/items/c9105dca1725224d36a8 ↑ よく見るRESTなAPIのエンドポイント メリット: - アクセスするリソースがわかりやすい - メソッド名で操作もわかりやすい デメリット: - 1リクエスト1リソースの取得 - エンドポイントがふえる - オーバーフェッチ、アンダーフェッチが起きる 4

5.
[beta]
RESTと比較したGraphQLの話
- クライアントが必要なデータだけを指定して取得できる
{

}

{
hero {
name
height
}

}

リクエストクエリ

"hero": {
"name": "Luke Skywalker",
"height": 1.72
}

レスポンス(data部分を抽出)

- 単一のエンドポイントで行う -> 問い合わせエンドポイント(/graphql - POST)
- サーバ側でデータ構造の型定義が可能
type Query {
hero: Character
}
type Character {
name: String
friends: [Character]
homeWorld: Planet
species: Species
}
type Planet {
name: String
climate: String
}
type Species {
name: String
lifespan: Int

一回のリクエストでいい感じに欲しいデータが全部取れる!

origin: Planet
}

5

6.

GraphQL知識編: スキーマ(Schema) スキーマ: GraphQL API の全ての可能な操作と返すデータの形を定義 type Person { name: String! age: Int! } name, ageという2つのフィールドを持つPerson型を定義 ! をつけると入力必須のフィールドになる type Person { name: String! age: Int! posts: [Post!]! } type Post { title: String! author: Person! } 定義したデータ型をくっつけることも可能 Person型はPost型の配列を持つ 6

7.

GraphQL知識編: クエリ(Query) GraphQLは単一のエンドポイント構造のため、必要なデータの情報をクエリに全て載せる必要がある type Person { name: String! age: Int! } type query { allPersons: [Person!]! } ↑ まずはスキーマ定義でクエリ型を定義する必要がある(サーバ側) query { allPersons { name age } } { } "allPersons": [ { "name": "Johnny", "age": 40 }, { "name": "Sarah", "age": 23 }, { "name": "Alice", "age": 34 } ] 欲しいデータを書いたクエリを投げるとJSONで返ってくる ※ 必要なデータのみを取得する思想なので `SELECT *` みたいなことはできません 7

8.

GraphQL知識編: ミューテーション(Mutation) データを変更するためのもの # 出力するデータ type Query { allPersons(last: Int): [Person!]! } # データの作成、上書きと削除に関する処理 type Mutation { createPerson(name: String!, age: Int!): Person! updatePerson(id: ID!, name: String!, age: String!): Person! deletePerson(id: ID!): Person! } ← この処理の中身は リゾルバ(後述)で設定 type Person { id: ID! name: String! age: Int! } ↑ まずはスキーマ定義でミューテーションを定義 mutation { createPerson(name: "Bob", age: 36) { name age } } ← ミューテーションのクエリ この例だとPersonが登録される 8

9.
[beta]
GraphQL知識編: リゾルバ(Resolver)
クエリやミューテーションの動作を定義する
let persons = [];
const resolvers = {
Mutation: {
createPerson: (_, {name, age}) => {
const newPerson = { id: generatedId(), name, age};
persons.push(newPerson);
return newPerson;
}
}
}

↑ 前ページミューテーションの登録処理はこんな感じになっていた

mutation {
createPerson(name: "Bob", age: 36) {
name
age
}
}

9

10.
[beta]
GraphQL実践編: 実際に動かしてみる
必要なもの
- Apollo Server: GraphQLサーバ構築用のライブラリ
- node: ここは自由
- node-fetch: node使う場合
コード

やっていること
- 今まで見てきたPerson型の定義
- 登録する処理
- クライアント側のエラーハンドリング
// node-fetchをインポート
import fetch from 'node-fetch';
const host = 'http://localhost:4000';
// ランダムで名前と年齢を生成
const randomName = Math.random().toString(36).substring(7);
const randomAge = Math.floor(Math.random() * 100);

// Apollo Serverのインポート
import {ApolloServer} from 'apollo-server';
// スキーマ定義
const typeDefs = `
type Person {
id: ID!
name: String!
age: Int!
}
type Query {
getPersons: [Person]
}
type Mutation {
createPerson(name: String!, age: Int!): Person
}
`;
// リゾルバ定義
const resolvers = {
Query: {
getPersons: () => persons,
},
Mutation: {
createPerson: (_, { name, age }) => {
const newPerson = { id: generateId(), name, age };
persons.push(newPerson);
return newPerson;
},
},
};
// persons配列とID生成関数の例
const persons = [];
const generateId = () => persons.length + 1;
// GraphQLサーバーのセットアップ(例:Apollo Server)
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});

サーバ側

// 新しいPersonを作成
fetch(`${host}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
mutation {
createPerson(name: "${randomName}", age: ${randomAge}) {
id
name
age
}
}`,
}),
})
.then((res) => res.json())
.then((data) => {
const newPerson = data.data.createPerson;
if (newPerson && newPerson.id) {
console.log('Person created:', newPerson);
// すべての Personを取得
return fetch(`${host}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query {
getPersons {
id
name
age
}
}`,
}),
});
} else {
throw new Error('Failed to create person');
}
})
.then((res) => res.json())
.then((data) => {
const persons = data.data.getPersons;
console.log('All persons:', persons);
})
.catch((error) => {
console.error(error);
});

クライアント側

10

11.

GraphQL実践編: 動かした結果 クライアント側実行のたびにランダムなPersonを登録できた! ~/w/graphql() ❯❯❯ node client.js Person created: { id: '4', name: 'u8qql', age: 18 } All persons: [ { id: '1', name: 'avgr9', age: 72 }, { id: '2', name: 'panc9', age: 45 }, { id: '3', name: '01h5ur', age: 62 }, { id: '4', name: 'u8qql', age: 18 } ] 11

12.

実際に導入するにあたって メリット - 必要な分だけ指定できるのでオーバーフェッチ、アンダーフェッチが減る - つまりネットワーク効率が上がってパフォーマンスが上がってうれしい - エンドポイントが1つで済むので煩雑にならない - 大きなプロジェクトになるほどメリットが大きくなる デメリット - 設計が複雑になりがち - 比較的新しいので知見が少なめ - クエリの柔軟性が高い, 1つのエンドポイントで全やりとりを行うのでセキュリティ的に注意必要 - 複雑なクエリを許可するとサーバに大きな負荷をかけることができてしまう 🤔🤔🤔🤔 12

13.
[beta]
補足: スキーマ定義をクライアント側から知るには?
GraphQLのIntrospection Queryを使えばスキーマ設定を取得することが可能
import fetch from 'node-fetch';
const introspectionQuery = `
query {
__schema {
全スキーマを取得
types {
スキーマ内の全ての型
name
各型の名前(Person, String, Int など)
fields {
その型が持つフィールドのリスト
name
フィールドの名前(id, name, ageなど)
type {
フィールドの型に関する情報
name
フィールドの型の名前
kind
フィールドの型の種類
}
}
}
}
}
`;
fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ query: introspectionQuery }),
})
.then(r => r.json())
.then(data => console.log(JSON.stringify(data, null, 2)));

13

14.

補足: Introspection Query結果 スキーマ定義を取得できた ~/w/graphql() ❯❯❯ node introspection.js { "data": { "__schema": { "types": [ { "name": "Person", "fields": [ { "name": "id" }, { "name": "name" }, { "name": "age" } ] }, { "name": "ID", "fields": null }, { "name": "String", "fields": null }, { "name": "Int", "fields": null }, { "name": "Query", "fields": [ { "name": "getPersons" } ] }, … … { "name": "Mutation", "fields": [ { "name": "createPerson" } ] }, { "name": "Boolean", "fields": null }, { "name": "__Schema", "fields": [ { "name": "description" }, { "name": "types" }, { "name": "queryType" }, { "name": "mutationType" }, { "name": "subscriptionType" }, { "name": "directives" } ] }, { "name": "__Type", "fields": [ { "name": "kind" }, { Introspection のクエリの中身を変えて欲しい情報だけ取ることが可能 14

15.
[beta]
補足: Introspection Queryを無効にする
このクエリは強力すぎるので本番環境では無効にするのが普通です
// GraphQLサーバーのセットアップ(例:Apollo Server)
const server = new ApolloServer({ typeDefs, resolvers, introspection: false});

サーバのオプションに `introspection: false` 指定して起動するだけでOK
~/w/graphql() ❯❯❯ node introspection.js
{
"errors": [
{
"message": "GraphQL introspection is not allowed by Apollo Server, but the query
contained __schema or __type. To enable introspection, pass introspection: true to
ApolloServer in production",
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
"exception": {
"stacktrace": [
"GraphQLError: GraphQL introspection is not allowed by Apollo Server, but the
query contained __schema or __type. To enable introspection, pass introspection: true to
ApolloServer in production",
…

15

16.

内容まとめ GraphQLとは... - API向けのクエリ言語とサーバーサイドランタイムの両方を指す言葉 - クライアント側で必要なデータを指定してリクエスト - オーバーフェッチ、アンダーフェッチが減る! - エンドポイントは1つ - リソース増えても煩雑にならないしリクエストも一回で完結 一方で... - 学習コスト高め - ちょっと複雑で流行らないかも 16

17.

参考リンク GraphQL公式 https://graphql.org/ GraphQLに入門する https://qiita.com/jintz/items/c9105dca1725224d36a8 GraphQLを徹底解説する記事 https://zenn.dev/nameless_sn/articles/graphql_tutorial 17