概要

GraphQLはFacebookにより開発されたオープンソースの言語。

  • REST のようにリソース毎に複数のエンドポイントは持たない。(エンドポイントは1つ)
  • 事前定義されたスキーマとリクエスト時のクエリの内容に応じて処理が行われる
    などの特徴がある。

以降では、以下のライブラリを使用してサーバ 及び クライアント処理の作成を行う。

  • サーバ側
    • graphene
    • flask
    • flask-graphql
  • クライアント側
    • vue.js
    • vue-apollo

目次

GraphQLの基礎

クエリとミューテーション

スキーマとタイプ

TODO:

Type System

Type Language

Object Types and Fields

Arguments

The Query and Mutation Types

Scalar Types

Enumeration Types

Lists and Non-Null

Interfaces

Union Types

Input Types

サーバ側の環境構築

pythonライブラリ graphene を使用してサーバ環境を構築する。

準備

pythonの仮想環境を作って graphene をインストールしておく。
※WebAPIとして提供する為、flask と flask-graphql も同時にインストール。
※クロスオリジンを許可する為、flask-cors もインストール。

pip install pipenv
pipenv --python 3.7
pipenv install graphene
pipenv install flask
pipenv install flask-graphql
pipenv install flask-cors

動作確認用に仮想環境のシェルを起動しておく

pipenv shell

スキーマ 及び リゾルバ関数の定義

schema.py

# サンプルデータ
sample_users = { 
    "1": {"id":1, "name":"Ichiro", "age":10},
    "2": {"id":2, "name":"Jiro", "age":20},
    "3": {"id":3, "name":"Saburo", "age":30}
}


class User(graphene.ObjectType):
    id = graphene.ID()
    name = graphene.String()
    age = graphene.Int()


class Query(graphene.ObjectType):

    user = graphene.Field(User, id=graphene.ID())
    users = graphene.Field(graphene.List(User))

    def resolve_user(self, info, id): # resolve_変数名
        """ID指定のユーザ取得."""
        user_info = None
        if id:  # if info.variable_values["id"] でも可
            user_info = sample_users.get(id)  # sample_users.get(info.variable_values["id"])
        if user_info:
            return User(**user_info)
        else:
            return None

    def resolve_users(self, info, **kwargs):
        """ユーザ一覧の取得."""
        return [ User(**sample_users[id]) for id in sample_users]


class CreateUser(graphene.Mutation):
    class Arguments:
        name = graphene.String()
        age = graphene.Int()

    created = graphene.Boolean()
    user = graphene.Field(lambda: User)

    def mutate(root, info, name, age):
        id = len(sample_users) + 1 
        sample_users[str(id)] = {"id":id, "name":name, "age":age}
        user = User(id=id, name=name, age=age)
        return CreateUser(user=user, created=True)


class MyMutations(graphene.ObjectType):
    create_user = CreateUser.Field()


schema = graphene.Schema(query=Query, mutation=MyMutations)

スキーマ単体での動作確認

test_schema.py

from schema import schema

if __name__ == "__main__":

    # ユーザを作成
    query = """ 
        mutation createMutation {
            createUser(name:"Shiro", age:40) {
                user {
                    id
                    name
                    age
                }
                created
            }
        }
    """
    result = schema.execute(query)
    print(result.data)

    # 一覧を取得
    query = """ 
        query ListQuery {
          users {
            id,
            name,
            age
          }
        }
    """
    result = schema.execute(query)
    print(result.data)

    # IDを指定して取得
    query = """ 
        query SampleQuery($id: ID) {
          user(id: $id) {
            id,
            name,
            age
          }
        }
    """
    result = schema.execute(query, variable_values={"id": 3})
    print(result.data)

動作確認の実行

python test_schema.py
OrderedDict([('createUser', OrderedDict([('user', OrderedDict([('id', '4'), ('name', 'Shiro'), ('age', 40)])), ('created', True)]))])
OrderedDict([('users', [OrderedDict([('id', '1'), ('name', 'Ichiro'), ('age', 10)]), OrderedDict([('id', '3'), ('name', 'Saburo'), ('age', 30)]), OrderedDict([('id', '2'), ('name', 'Jiro'), ('age', 20)]), OrderedDict([('id', '4'), ('name', 'Shiro'), ('age', 40)])])])
OrderedDict([('user', OrderedDict([('id', '3'), ('name', 'Saburo'), ('age', 30)]))])

Webサーバの実装

app.py

from schema import schema
from flask import Flask
from flask_graphql import GraphQLView
from flask_cors import CORS

app = Flask(__name__)
app.debug = True
CORS(app)  # ローカルでの動作確認用にクロスオリジンを許可

app.add_url_rule(
    '/',
    view_func=GraphQLView.as_view(
        'graphql',
        schema=schema,
        graphiql=True  # テスト時などにブラウザから利用できるAPIコンソールをONにしておく
    )   
)

if __name__ == '__main__':
    app.run()

サーバ起動

python app.py

WebAPIとしての動作確認

# 一意検索
curl http://127.0.0.1:5000/ --data query='query { user(id: "1") { id, name, age}}'
{"data":{"user":{"id":"1","name":"Taro","age":20}}}

# 一覧検索
curl http://127.0.0.1:5000/ --data query='query { users{ id, name}}'
{"data":{"users":[{"id":"3","name":"Saburo"},{"id":"1","name":"Ichiro"},{"id":"2","name":"Jiro"}]}}

# 追加
curl http://127.0.0.1:5000/ --data query='mutation { createUser(name:"Shiro", age:40) { user {id, name, age}, created }}'
{"data":{"createUser":{"user":{"id":"4","name":"Shiro","age":40},"created":true}}}

尚、graphiql=True で起動している場合は、ブラウザから任意のQueryを発行する為のコンソール画面も提供されるので、そちらから確認する事も可能。

クライント処理の作成

実際にAPIを利用するクライアント側の処理を Vue.js 及び Vue Apollo で作成する。

プロジェクトの作成

vue create sample-project
? Please pick a preset: Manually select features
? Check the features needed for your project: TS, Router, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? No
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a linter / formatter config: Basic
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

GraphQLクライアントのインストール

GraphQLクライアントとして Vue Apollo を使用する。
https://apollo.vuejs.org/

cd sample-project
vue add apollo

クライアント処理の作成

src/main.ts

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { createProvider } from './vue-apollo'

Vue.config.productionTip = false

new Vue({
  router,
  apolloProvider: createProvider(),
  render: h => h(App)
}).$mount('#app')

※ vue add apollo した時点で自動的に更新されている為、特に自分で変更はしていない。

src/App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/graphql">GraphQL Sample</router-link>  <!-- 追加 -->
    </div>
    <router-view/>
  </div>
</template>
 :
 :

src/router/index.ts

 :
 :
const routes = [
   :
   :
  // 以下を追加
  {
    path: '/graphql',
    name: 'graphql',
    component: () => import('../views/GraphQL.vue')
  }
]

src/views/GraphQL.vue

<template>
  <div class="graphql">
    GraphQL Example!
    <div>
      <table id="users_table">
        <thead>
          <tr>
            <th>id</th><th>name</th><th>age</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="u in users" v-bind:key="u.id">
            <td>{{ u.id }}</td><td>{{ u.name }}</td><td>{{ u.age }}</td>
          </tr>
        </tbody>
      </table>
      <hr>
      <button v-on:click="addUser()">追加</button>
      <div>
        名前: <input type="text" v-model="newName"><br />
        年齢: <input type="text" v-model="newAge"><br />
      </div>
    </div>
  </div>
</template>

<script>
import gql from 'graphql-tag'

export default {
  name: 'graphql',
  data: function () {
    return {
      users: [],
      newName: "Sample",
      newAge: 10
    }
  },
  apollo: {
    users: gql`query getUsers {
      users {id, name, age}
    }`,
  },
  methods: {
    addUser() {
      this.$apollo.mutate({
        mutation: gql`mutation createMutation($name: String, $age: Int) {
            createUser(name: $name, age: $age) {
                user {
                    id,
                    name,
                    age
                }
                created
            }
          }
        `,
        variables: {
          name: this.newName,
          age: this.newAge
        },
        refetchQueries: ["getUsers"]
      }).then((data) => {
        console.log(data)
      }).catch((error) => {
        console.error(error)
      })
    }
  }
}
</script>

<style>
#users_table {
  margin: 0 auto;
  border-collapse: collapse;
  border-spacing: 0;
}
#users_table th, #users_table td {
  border: 1px solid #333;
  padding: 2px 4px;
}
#users_table th {
  background: #ddd;
}
#users_table td {
  background: #fff;
}
</style>

設定ファイルの作成

.env.development

NODE_ENV="development"
VUE_APP_GRAPHQL_HTTP="http://localhost:5000/graphql"
VUE_APP_GRAPHQL_WS=

.env.production

NODE_ENV="production"
VUE_APP_GRAPHQL_HTTP="/graphql"
VUE_APP_GRAPHQL_WS=

vue.config.js

module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/static/' : '/'
}

動作確認用のVueのサーバを起動

cd sample-project
npm run serve

あとは http://localhost:8080/ にアクセスして画面からAPI が利用できる事を確認していく。

ビルド

クライアント処理のビルド

tsconfig.json

{
  "compilerOptions": {
      :
    "allowJs": true,
      :

※ apolloProvider というプロパティがダメだと怒られるので、allowJs: true にしておく。

ビルド

cd sample-project
npm run build

サーバ側(flask)のstatic フォルダにコピー

rm -rf path_to/static
cp -R dist path_to/static

※path_to は flask側のフォルダ

以上で flask のみ起動した状態で http://localhost:5000/static/index.html から動作確認可能。


トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-11-13 (水) 20:28:34 (28d)