GraphQLを使ってみた
実行環境
パーケッジ名 | バージョン |
Laravel | 7.15.0 |
React | 16.2.0 |
GraphQL | 15.1.0 |
nuwave/lighthouse | 4.14 |
graphql-tag | 2.10.3 |
react-apollo | 3.1.5 |
apollo-boost | 0.4.9 |
react-router-dom | 5.2.0 |
本当は@apollo/react-hooksを使ってhookをやりたかったのですがReactのバージョンが対応していないとエラーがでたためできていません。
とりあえず、LaravelでQraphQLを使うための下準備をします。
lighthouseを使用しています。
lighthouseについては下記URLからどうぞ
https://lighthouse-php.com/2.6/getting-started/installation.html#install-via-composer
composer require nuwave/lighthouse php artisan vendor:publish --provider="Nuwave\Lighthouse\Providers\LighthouseServiceProvider" --tag=schema composer require mll-lab/laravel-graphql-playground php artisan vendor:publish --provider="MLL\GraphQLPlayground\GraphQLPlaygroundServiceProvider"
laravel-graphql-playgroundはなくてもいいのですが、クエリの確認に便利なので入れています。
これでプロジェクトの直下にgraphqlというフォルダができます。その中のschema.graphqlにスキーマを書いていきます。
http://127.0.0.1:8000/graphql-playgroundでクエリの確認ができます。
schema.graphql
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-01-01 13:00:00`." scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") "A date string with format `Y-m-d`, e.g. `2011-05-23`." scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") type Query { users:[User!]! @all(model: "App\\User") user(id: ID @eq): User @find(model: "App\\User") usercount:Int @count(model: "App\\User") } type Mutation { createUser( name: String @rules(apply: ["required"], messages: { required: "名前は必須です" } ) email: String @rules(apply: ["required","email","unique:users,email"], messages: { unique: "このメールアドレスは使われています。" ,required: "メールアドレスは必須です"}, ) password: String @rules(apply: ["required"], messages: { required: "パスワードは必須です" } ) ): User @create(model: "App\\User") updateUser( id:ID! name: String @rules(apply: ["required"]) password: String @rules(apply: ["required"]) ): User @update(model: "App\\User") deleteUser( id:ID! @rules(apply: ["required"]) ): User @delete(model: "App\\User") } type User { id: ID! name: String! email: String! password: String! created_at: DateTime! updated_at: DateTime! }
ここはこんな感じで、GraphQLを使ったことがある人ならわかると思います。
App.js
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import ApolloClient from 'apollo-boost'; import { ApolloProvider } from 'react-apollo'; import Users from './Users'; import User from './User'; const client = new ApolloClient({ uri: 'http://127.0.0.1:8000/graphql', }); function App() { return ( <ApolloProvider client={client}> <Router> <Switch> <Route exact path="/" component={Users} /> <Route path="/user/:id" component={User} /> </Switch> </Router> </ApolloProvider> ) } export default App; ReactDOM.render(<App />, document.getElementById('example'));
clientでエンドポイントを指定。
ApolloProviderでルートを囲むことですべてのルートでQueryやMutationを使うことができます。
Users.js
import React, { useState, useEffect, useRef } from 'react'; import gql from 'graphql-tag'; import { Query, Mutation } from 'react-apollo'; import { Link } from "react-router-dom"; import './style/Users.css'; //playground上で試したクエリ const getUsers = gql` query { usercount users{ id name } } `; const CREAT_USER = gql` mutation CreateUser($name: String $email: String $password: String){ createUser(name:$name, email:$email, password:$password){ id name } } `; const DELETE_USER=gql` mutation DeleteUser($id:ID!){ deleteUser(id: $id){ id } } `; function Users() { const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [email, setEMail] = useState(""); const [password, setPassword] = useState(""); function OpenModal() { setOpen(!open) if (open == true) { document.body.removeAttribute('style', 'overflow: hidden;'); } else { document.body.setAttribute('style', 'overflow: hidden;'); } } function onClick(e) { e.stopPropagation() } function Name(e) { setName(e.target.value); } function EMail(e) { setEMail(e.target.value); } function Password(e) { setPassword(e.target.value); } return ( <div> <h1>ユーザー一覧</h1> <button onClick={OpenModal}>ユーザー登録</button> {open && ( <div className="modal" onClick={OpenModal}> <div className="creat" onClick={onClick}> <h1>新規登録</h1> <Mutation mutation={CREAT_USER} > {(createUser, { data, loading, error }) => { return ( <div> <form onSubmit={e => { e.preventDefault(); e.stopPropagation() createUser({ variables: { name, email, password }, //画面の自働更新 //追加、削除の場合手動で更新をする必要がある refetchQueries: [{ query: getUsers }] }) }}> <p>名前</p> {error && <p style={{ color: "red" }}>{error.graphQLErrors[0].extensions.validation.name}</p>} <input onChange={Name}></input> <p>メールアドレス</p> {error && <p style={{ color: "red" }}>{error.graphQLErrors[0].extensions.validation.email}</p>} <input onChange={EMail} type="email"></input> <p>パスワード</p> {error && <p style={{ color: "red" }}>{error.graphQLErrors[0].extensions.validation.password}</p>} <input onChange={Password} type="password"></input> <button type="submit">登録</button> </form> {error && <p>{error.message}</p>} </div> ) }} </Mutation> </div> </div> )} <Query query={getUsers}> {({ data, loading, error }) => { // データ取得中はローディングを表示 if (loading) return <p>loading...</p>; if (error) return ( <div> <p>{error.toString()}</p> <p>{error.message}</p> </div> ); const { users } = data; return ( <div> <h2>{data.usercount}人</h2> <ul> {users.map(user => ( <li key={user.id}> <Link to={`/user/${user.id}`}> <span>{user.name}</span> </Link> <Mutation mutation={DELETE_USER}> {(DeleteUser, { data, loading, error }) => { return( <button onClick={e=>{ e.preventDefault(); DeleteUser({ variables: { id:user.id }, refetchQueries: [{ query: getUsers }] }); }}> 削除 </button>) }} </Mutation> </li> ))} </ul> </div> ); }} </Query> </div > ); } export default Users;
.modal{ background-color:rgba(207,214,227,0.3); position: absolute; top:0; left:0; width: 100%; height: 100%; text-align: center; } .creat{ background-color: white; width: 50%; margin: 0 auto; } .creat form{ width: 50%; margin: 0 auto; } form p{ margin-bottom: 0; padding-bottom: 0; text-align: left; } form input{ width: 90%; height: 20px; display: block; margin-right: auto } form button{ width: 70px; height: 50px; font-size: 20px; }
ここではユーザー一覧と新規登録を行っています。
queryやmutationを使う場合はQueryコンポーネントやMutationコンポーネントを作りその中で実行します。
refetchQueriesを使うことで手動でブラウザをリロードすることなく自働で更新されるので便利です
他にも自働更新の方法があるので気になる人は下記URLを参考にしてください。
http://tomoima525.hatenablog.com/entry/2019/08/06/164506
https://qiita.com/yasuhiro-yamada/items/3bcb36504fb5aaa0416c
エラー時はgraphQLErrorsでエラー内容を受け取ることができます。
参考URL
https://www.apollographql.com/docs/react/data/error-handling/#network-errors
https://github.com/vuejs/vue-apollo/issues/498
一応、error.toString()とerror.messageでエラーメッセージを受け取れますが、利用者側からしたら意味不明なエラーになるので、使わない方がいいです。
User.js
import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import gql from 'graphql-tag'; import { Query, Mutation } from 'react-apollo'; import './style/User.css'; const getUser = gql` query USER($id: ID!){ user(id: $id){ id name email password } } `; const UPDATE_USER = gql` mutation UpdateUser($id: ID! $name: String $password: String){ updateUser(id:$id, name:$name, password:$password){ name password } } `; function User() { const { id } = useParams(); const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [password, setPassword] = useState(""); function OpenModal() { setOpen(!open) if (open == true) { document.body.removeAttribute('style', 'overflow: hidden;'); } else { document.body.setAttribute('style', 'overflow: hidden;'); } } function onClick(e) { e.stopPropagation() } function Name(e) { setName(e.target.value); } function Password(e) { setPassword(e.target.value); } return ( <div> <h1>ユーザー情報</h1> <Query query={getUser} variables={{ id }}> {({ loading, error, data }) => { if (loading) return <p>loading...</p>; if (error) return ( <div> <p>{error.toString()}</p> <p>{error.message}</p> </div> ); const { user } = data; return ( <div className="user"> <p><label>ID</label>:{user.id}</p> <p><label>名前</label>:{user.name}</p> <p><label>メールアドレス</label>:{user.email}</p> <p><label>パスワード</label>:{user.password}</p> </div> ); }} </Query> <button onClick={OpenModal}>編集</button> {open && ( <div className="modal" onClick={OpenModal}> <div class="update" onClick={onClick}> <h1>プロフィール編集</h1> <Mutation mutation={UPDATE_USER}> {(updateUser, { data, loading, error }) => { return ( <div> <form onSubmit={e => { e.preventDefault(); e.stopPropagation() updateUser({ variables: { id, name, password }, refetchQueries: [{ query: getUser, variables: {id} }] }) }}> <p>名前</p> {error && <p style={{ color: "red" }}>{error.graphQLErrors[0].extensions.validation.name}</p>} <input onChange={Name}></input> <p>パスワード</p> {error && <p style={{ color: "red" }}>{error.graphQLErrors[0].extensions.validation.password}</p>} <input onChange={Password} type="password"></input> <button type="submit">編集</button> </form> {error && <p>{error.message}</p>} </div> ) }} </Mutation> </div> </div> )} </div> ) } export default User;
URLからuseParamsでパラメータを受け取りそれを元にgetUserでDBから情報を取ってきます。
今回はユーザー全員の情報ではなく、一人の情報のみなので以下のように
refetchQueries: [{ query: getUser, variables: {id} }]
variablesに探すユーザーのidを渡します。
まとめ
とりあえず、登録、更新、削除と一通り試してみました。
更新、削除をする時にはPRIMARY KEY(主キー)が必要でした。自分はデフォルトのままなので、idで行いました。
リレーションも試したかったのですが、今回はここまでにしておきます。
QraphQLはRESTと比べて、コードが読みやすいです。