express-graphql + vue-apollo で GraphQLを試してみた

express-graphql + vue-apollo で GraphQLを試してみた

こんにちは。エンジニアの@elcih17です。
この記事を書いた頃はインターンでしたが、現在は新卒エンジニアとして日々奮闘しています。

最近、APIの設計に関することを調べていたら「GraphQL」という言葉をよく見かけました。
API用の技術として注目されているそうで、REST APIの次のパラダイムだという意見もあるようです。

どんな技術なのかとても気になりHOW TO GRAPHQLのチュートリアルと簡単な実装をしてみたら、なんとなくGraphQLのことがわかったので書いてみます。

実装例

GraphQL とは

  • API用のクエリ言語

特徴

  • シングルエンドポイント
  • 複数のリソースの欲しいデータを1リクエストで取得できる
  • スキーマ定義と型システム

機能

Query

RESTにおけるGET

idが"1"のユーザーの名前と年齢を取得するGraphQLは次のように書けます。

query {
  user(id:"1") {
    name
    age
  }
}

response

{
  "data": {
    "user": {
      "name": "elcih17",
      "age": 22
    }
  }
}

Mutation

RESTにおけるPOST、PUT、DELETE

新規ユーザーを作成するGraphQLは次のように書けます。

mutation {
  createUser(id: "2", name: "KARTE", age:2) {
    name
    age
  }
}

response

{
  "data": {
    "user": {
      "id": "2",
      "name": "KARTE",
      "age": 2
    }
  }
}

Subscription

QueryとMutationの実行をsubscribeできる

試してないので詳しくはわかりませんが、データの更新を受け取って何かしたいときに使えそうです。

GraphQLの何が良いのか

  • 不要なデータの取得と無駄なリクエストを防げる
  • スキーマと型定義によってAPIが提供する機能が明確になる
  • GraphiQLが便利

不要なデータの取得と無駄なリクエストを防げる

複数のリソースの欲しいデータを1リクエストで取得できる ので無駄が無くなります。
例えば、あるユーザーの名前と投稿記事タイトルと記事についたコメントを取得するケースを考えてみます。

RESTの場合

  1. GET /users/:id
  2. GET /users/:id/posts
  3. GET /posts/:id/comments
  4. 取得したデータからそれぞれ名前、記事のタイトル、コメントを抽出します

GraphQLの場合

  1. /graphqlに対し下記のようなクエリをリクエストします

必要なデータのみ取得しているのでデータを抽出する必要はありません

query {
  user(id: "1") {
    name
    posts {
      title
        comments {
        content
      }
    }
  }
}

スキーマと型定義によってAPIが提供する機能が明確になる

  • 入出力と型を定義することによって何が出来るかかが分かりやすい
  • 後述するGraphiQLで見ることが出来るAPIドキュメントはスキーマ定義から自動生成される

GraphiQLが便利

  • GraphQLのIDE
  • APIドキュメントの閲覧とクエリの実行ができる
  • デバッグがしやすい
    • クエリをサクサク試せる
    • エラーメッセージがわかりやすい

GraphQLサーバーの例(express-graphql)

Query、Mutationの機能紹介で例示したクエリを実行可能にするための実装はこのようになります

GraphQLスキーマの定義 (GraphQLの入出力と型定義)

  • !required
type Query {
  user(id: String!): User! // relation
}
type Mutation {
  createUser(id: String!, name: String!, age: Int!): User // relation
}
type User {
  id: String!
  name: String!
  age: Int!
}

リゾルバ関数の実装 (GraphQLとバックエンド処理のマッピング)

const users = [
  {
    id: '1',
    name: 'elcih17',
    age: 22
  }
]
const resolvers = {
  Query: {
    user: (root, {id}) => {
      return users.find(user => user.id === id)
    }
  },
  Mutation: {
    createUser: (root, args) => {
      const {id, name, age} = args
      const newUser = {id, name, age}
      users.push(newUser)
      return newUser
    }
  }
};

GraphQLオブジェクトとrelation(関連づけ)を使う

標準のオブジェクトタイプ(QueryMutationSubscription)以外にも独自のオブジェクトを定義できます。
また、定義したオブジェクト同士のrelationが可能です。

スキーマ

type User {
  name: String!
  posts: [Post]
}
type Post {
   author: User!
   title: String!
}

relationを使えば次のようなクエリを受け取った際にデータを構築するのが簡単になります。

query {
  user(id: "1") {
    posts {
      title
    }
  }
}

リゾルバ (データの構築)

Query: {
 user: (root, args) => {
    const {id} = args
    return users.find(user => user.id === id)
 }
},
User: {
  posts: (author) => {
    const {id} = author
    return posts.filter(post => post.author_id === id)
  }
}

GraphQLクエリの実行の流れについてはこの記事が分かりやすいです。

実装例ではMongoDBに接続して実装してみました。

所感

  • GraphiQLのおかげでデバッグがしやすくて良い
  • GraphQLのスキーマ定義とDBのスキーマ定義が二重管理になって面倒
    • DBのスキーマからGraphQLスキーマを生成するようなライブラリが出てくれば楽になるかも

フロントエンドGraphQL

現在、GraphQLを扱うためのClientに ApolloRelay があります。

RelayはReact用に構築されていてApolloはそのほかのフレームワークにも対応しているようです。
React + Apolloという組み合わせもあるので、Reactを使っている人はRelayとApolloを比べてみても面白いかもしれません。

今回はvue-apolloを使うためにApolloを使用してみました。

一部実装例を載せておきます。

src/main.js

import 'isomorphic-fetch'

import Vue from 'vue'
import {ApolloClient, createNetworkInterface} from 'apollo-client'
import VueApollo from 'vue-apollo'
import App from './App.vue'

const uri = 'http://localhost:3000/graphql'
const networkInterface = createNetworkInterface({
  uri,
  transportBatching: true,
})

Vue.use(VueApollo)

const apolloClient = new ApolloClient({networkInterface})
const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
})

new Vue({
  el: '#app',
  apolloProvider,
  render: h => h(App),
})

src/App.vue

<!-- template -->

<div id="app">
  <h1> Vue Apollo Example </h1>
  <p>{{hello}}</p>

  <label>name</label>
  <input type="text" v-model="userName">

  <label>age</label>
  <input type="number" v-model="userAge">

  <a href="#" @click="createUser">createUser</a>

  <div v-if="createdUser">
    <p>User created!</p>
    <p>name: {{createdUser.name}}</p>
    <p>age: {{createdUser.age}}</p>
  </div>
</div>
// script

import gql from 'graphql-tag';

const helloGQL = gql`
  query {
    hello
  }
`;
const createUserGQL = gql`
  mutation ($userName: String!, $userAge: Int!){
    createUser(name: $userName, age: $userAge) {
      _id
      name
      age
    }
  }
`
export default {
  name: 'app',
  data() {
    return {
      hello: '',
      userName: '',
      userAge: '',
      createdUser: null
    };
  },
  apollo: {
    hello: {
      query: helloGQL,
    }
  },
  methods: {
    createUser() {
      this.$apollo.mutate({
        mutation: createUserGQL,
        variables: {
          userName: this.userName,
          userAge: this.userAge
        }
      }).then(res => {
        this.createdUser = res.data.createUser
      }).catch(err => {
        console.error(err);
      })
    }
  }
};

詳細は実装例をご覧ください。

所感

  • 欲しいデータだけを1リクエストで取得できるのは確かに良さそう
    • しかし、rootのデータを取得してロジックで整形みたいなことをするとRESTとあまり変わらない
    • 不要なデータ取得と無駄なリクエストを実際に減らせるかは使用者次第という感じ
  • エンドポイント × GraphQLクエリの分だけ管理が必要なので複雑になってくるとツラそう

プロダクションで使えるか?

現在プロダクション環境でGraphQL APIを運用しているサービスはGithub、Coursera、Twitterなどがあるようです。
GithubのGraphQL API v4を見る限り、運用の仕組みを作れば十分使えそうな気がします。

プロダクションで運用・提供するに当たって考慮する必要がありそうなこと

  • バックエンド
    • クエリ(スキーマ)のライフサイクル管理
      • deprecatedフィールドなど
    • クエリのキャッシュ管理
    • セキュリティ
      • 巨大なクエリ、複雑なクエリへの対策
        • タイムアウト処理
        • クエリの実行回数制限
        • 1クエリのfield数制限
        • 1クエリのネスト(深さ)制限
      • GithubのRate limitsが参考になります
  • フロントエンド
    • クエリが自由にかけて属人性が高い
    • 規約が必要になることもあるかも?

まとめ

  • クエリが直感的に書ける
    • 必要なデータだけをリクエストできるので分かりやすい
    • 自由に書けるので複雑なクエリをリクエストされるリスクもある
  • GraphiQLが便利
  • 不要なデータ取得、無駄なリクエストを減らせるのは大きなメリット
  • プロダクションで運用する際にはセキュリティ対策は必須

以上で説明を終わります。

GraphQLについて少しはわかっていただけたでしょうか?
今回紹介できなかった機能を使えばもっと色々とできて面白そうなので興味のある方はぜひ試してみてください。

最後に

ウェブ接客プラットフォーム「KARTE」を運営するプレイドでは、 KARTEを支える技術に興味を持つエンジニア(インターンも!)を募集しています。
詳しくはプレイドの採用ページか、Wantedlyをご覧ください。

記事をシェア