#mynavi(); #setlinebreak(on); * 概要 [#h3339617] #html(<div style="padding-left: 10px;">) GraphQLはFacebookにより開発されたオープンソースの言語。 - REST のようにリソース毎に複数のエンドポイントは持たない。(エンドポイントは1つ) - 事前定義されたスキーマとリクエスト時のクエリの内容に応じて処理が行われる などの特徴がある。 以降では、以下のライブラリを使用してサーバ 及び クライアント処理の作成を行う。 -サーバ側 -- graphene -- flask -- flask-graphql - クライアント側 -- vue.js -- vue-apollo #html(</div>) * 目次 [#zefea870] #contents - 参考 -- https://graphql.org/learn/ -- https://docs.graphene-python.org/en/latest/quickstart/ -- https://github.com/graphql-python/graphene/blob/master/examples/simple_example.py - 関連 -- [[Vue.js]] -- [[AWS AmplifyでSPA構築]] * GraphQLの基礎 [#ia8c2b77] #html(<div style="padding-left: 10px;">) ** クエリとミューテーション [#ybc8a2b8] #html(<div style="padding-left: 10px;">) #TODO *** Fields [#pfa3b570] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#fields #html(</div>) *** Arguments [#sff1c3fc] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#arguments #html(</div>) *** Aliases [#cff67c85] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#aliases #html(</div>) *** Fragments [#e76c2dce] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#fragments #html(</div>) *** Operation Name [#b72b325a] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#operation-name #html(</div>) *** Variables [#m597b6b7] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#variables #html(</div>) *** Directives [#r00f8674] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#directives #html(</div>) *** Mutations [#a59ea6e6] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#mutations #html(</div>) *** Inline Fragments [#y9b73e9f] #html(<div style="padding-left: 10px;">) https://graphql.org/learn/queries/#inline-fragments #html(</div>) #html(</div>) // クエリとミューテーション ** スキーマとタイプ [#c745f6f1] #html(<div style="padding-left: 10px;">) #TODO *** Type System [#ze68b00b] #html(<div style="padding-left: 10px;">) #html(</div>) *** Type Language [#x3b3e0e0] #html(<div style="padding-left: 10px;">) #html(</div>) *** Object Types and Fields [#d5dacea4] #html(<div style="padding-left: 10px;">) #html(</div>) *** Arguments [#c5372518] #html(<div style="padding-left: 10px;">) #html(</div>) *** The Query and Mutation Types [#l8968084] #html(<div style="padding-left: 10px;">) #html(</div>) *** Scalar Types [#db6de978] #html(<div style="padding-left: 10px;">) #html(</div>) *** Enumeration Types [#ze5273d0] #html(<div style="padding-left: 10px;">) #html(</div>) *** Lists and Non-Null [#h9b08390] #html(<div style="padding-left: 10px;">) #html(</div>) *** Interfaces [#ze6d58f3] #html(<div style="padding-left: 10px;">) #html(</div>) *** Union Types [#wbc638e2] #html(<div style="padding-left: 10px;">) #html(</div>) *** Input Types [#ycab3418] #html(<div style="padding-left: 10px;">) #html(</div>) #html(</div>) #html(</div>) * サーバ側の環境構築 [#m4f52d38] #html(<div style="padding-left: 10px;">) pythonライブラリ graphene を使用してサーバ環境を構築する。 ** 準備 [#j4dcd023] #html(<div style="padding-left: 10px;">) pythonの仮想環境を作って graphene をインストールしておく。 ※WebAPIとして提供する為、flask と flask-graphql も同時にインストール。 ※クロスオリジンを許可する為、flask-cors もインストール。 #myterm2(){{ pip install pipenv pipenv --python 3.7 pipenv install graphene pipenv install flask pipenv install flask-graphql pipenv install flask-cors }} 動作確認用に仮想環境のシェルを起動しておく #myterm2(){{ pipenv shell }} #html(</div>) ** スキーマ 及び リゾルバ関数の定義 [#e2398928] #html(<div style="padding-left: 10px;">) schema.py #mycode2(){{ # サンプルデータ 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) }} #html(</div>) ** スキーマ単体での動作確認 [#b2280a8e] #html(<div style="padding-left: 10px;">) test_schema.py #mycode2(){{ 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) }} 動作確認の実行 #myterm2(){{ 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)]))]) }} #html(</div>) ** Webサーバの実装 [#n51fc439] #html(<div style="padding-left: 10px;">) app.py #mycode2(){{ 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) CORS(app) # ローカルでの動作確認用にクロスオリジンを許可 app.add_url_rule( '/', view_func=GraphQLView.as_view( 'graphql', schema=schema, graphiql=True # テスト時などにブラウザから利用できるAPIコンソールをONにしておく ) ) if __name__ == '__main__': app.run() }} サーバ起動 #myterm2(){{ python app.py }} #html(</div>) ** WebAPIとしての動作確認 [#td188c56] #html(<div style="padding-left: 10px;">) #myterm2(){{ # 一意検索 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を発行する為のコンソール画面も提供されるので、そちらから確認する事も可能。 #html(</div>) #html(</div>) * クライント処理の作成 [#tc3db4c7] #html(<div style="padding-left: 10px;">) 実際にAPIを利用するクライアント側の処理を Vue.js 及び Vue Apollo で作成する。 ** プロジェクトの作成 [#b6a77a71] #html(<div style="padding-left: 10px;">) #myterm2(){{ 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 }} #html(</div>) ** GraphQLクライアントのインストール [#ea2f88a6] #html(<div style="padding-left: 10px;">) GraphQLクライアントとして Vue Apollo を使用する。 https://apollo.vuejs.org/ #myterm2(){{ cd sample-project vue add apollo }} #html(</div>) ** クライアント処理の作成 [#aade03d9] #html(<div style="padding-left: 10px;">) src/main.ts #mycode2(){{ 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 #mycode2(){{ <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 #mycode2(){{ : : const routes = [ : : // 以下を追加 { path: '/graphql', name: 'graphql', component: () => import('../views/GraphQL.vue') } ] }} src/views/GraphQL.vue #mycode2(){{ <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> }} #html(</div>) ** 設定ファイルの作成 [#b9dde737] #html(<div style="padding-left: 10px;">) .env.development #mycode2(){{ NODE_ENV="development" VUE_APP_GRAPHQL_HTTP="http://localhost:5000/graphql" VUE_APP_GRAPHQL_WS= }} .env.production #mycode2(){{ NODE_ENV="production" VUE_APP_GRAPHQL_HTTP="/graphql" VUE_APP_GRAPHQL_WS= }} vue.config.js #mycode2(){{ module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/static/' : '/' } }} #html(</div>) // 設定ファイルの作成 #html(</div>) // クライント処理の作成 ** 動作確認用のVueのサーバを起動 [#w7b6006a] #html(<div style="padding-left: 10px;">) #myterm2(){{ cd sample-project npm run serve }} あとは http://localhost:8080/ にアクセスして画面からAPI が利用できる事を確認していく。 #html(</div>) * ビルド [#ha58e73e] #html(<div style="padding-left: 10px;">) ** クライアント処理のビルド [#l3981a20] tsconfig.json #mycode2(){{ { "compilerOptions": { : "allowJs": true, : }} ※ apolloProvider というプロパティがダメだと怒られるので、allowJs: true にしておく。 ビルド #myterm2(){{ cd sample-project npm run build }} サーバ側(flask)のstatic フォルダにコピー #myterm2(){{ rm -rf path_to/static cp -R dist path_to/static }} ※path_to は flask側のフォルダ 以上で flask のみ起動した状態で http://localhost:5000/static/index.html から動作確認可能。 #html(</div>)