GraphQL の話題を聞くことはあったのですが実際に触ったことがなく、使い方やメリットを知るために公式サイトの React と GraphQL クライアントライブラリ Apollo を使ったチュートリアル をやってみました。
この記事ではチュートリアルでやったことや、やりながら調べたことなどを書き残しています。
事前に公式サイトの How To GraphPL を読んで GraphQL の概要は理解しておきました。動画とテキストの両方を公開しており、丁寧に書かれていて大変わかりやすかったです。GraphQL を初めて触る人はまず読んでみたほうが良いかと思います。
*GraphQLとは
Facebook が OSS として開発している Web API のための問い合わせ(クエリ)言語です。現在広く使われている RESTful Web API の代替として、クライアントがサーバから柔軟にデータ取得や変更をするために開発されました。GitHub や Atlassian、Netflix などの企業がこの GraphQL を導入しています。REST はリソースごとに API を分けるためリクエスト量が多くなるといった問題点がありますが、GraphQL は単一のエンドポイントのみを公開してクライアントが要求したデータのみ取得することができます。クライアントが必要な情報を定義するだけなので、UI が変更されてもバックエンドを修正する必要がなくなります。また、REST だと API ドキュメントと実装に乖離ができてしまいますが、GraphQL はドキュメントを自動生成してくれるため開発工数を削減することができます。
リクエストされたクエリはスキーマ言語(SDL)で記述したスキーマに従って実行され、JSON形式のレスポンスを生成します。JSON形式であるため、画像や動画などの大容量バイナリの扱いが難しいといったデメリットもあります。
<特徴まとめ>
- 複数の API リクエストを1つにまとめられる
- 複数サービスの API をまとめる API Gateway として使える
- 共通したバックエンドを効率的に開発できる
- クライアントとサーバー間のインターフェースがクリーンになる
- API ドキュメントの作成に費やす時間を削減する(自動生成)
- 型システムを定義することで意図しないデータの通信を防ぐ
- クライアントからレスポンス形式を指定できる
subscription
を使ったイベントドリブンなリアルタイム更新を行うことができる- データ構造をモック化することで簡単にテストを行うことができる
*Apollo とは
GraphQL を簡単に操作するための OSS ライブラリです。- Apollo Client
React や Vue.js、ネイティブといったクライアントをサポート - Apollo Server
バックエンドからデータを取得する仕組みを構築(Node.js でGraphQL API を構築) - Apollo Engine
GraphQL の実行状況を監視したり、エラートラッキング、キャッシュの利用状況の監視などを行うことができるクラウドのサービス
*Prisma とは
データベースを GraphQL API に変える OSS ツールです。*参考
- How To GraphPL
- React + Apollo tutotial
- Apollo /公式サイト
- apollo-client /GitHub
- Prisma /公式サイト
- 「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ
*環境
- MacOS
- node 12.4.0
- create-react-app 3.1.0
- react 16.9.0
- graphql 14.4.2
- react-apollo 3.0.0
- apollo-boost 0.4.3
- prisma 1.34.5
*Reactプロジェクトを作成
React のプロジェクトを作成します。今回は、任意の作業ディレクトリ配下で
react-app
という名前の React プロジェクトを作成しました。## インストール
$ sudo npm install -g create-react-app
## プロジェクト作成
$ create-react-app react-app
アプリケーションを起動すると、React のサンプル画面が表示されます。
$ cd react-app/
$ npm start
http://localhost:3000/プロジェクトの構造を修正します。
src
ディレクトリ配下にstyles
とcomponents
ディレクトリを作成し、styles
にindex.css, App.css
、components
にApp.js
を移動します。このファイル移動に伴い、
index.js
とApp.js
の参照を修正します。<react-app/src/index.js>
import React from "react";
import ReactDOM from "react-dom";
// ---- ↓修正 ----
import "./styles/index.css";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
...
<react-app/src/components/App.js>
import React, { Component } from "react";
import logo from "../logo.svg";
// ---- ↓修正 ----
import "../styles/App.css";
...
修正後のディレクトリ構成は下記になります。
react-app
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src
├── App.test.js
├── components
│ └── App.js
├── index.js
├── logo.svg
├── serviceWorker.js
└── styles
├── App.css
└── index.css
*スタイルの修正
今回はチュートリアルに従ってTachyonsというCSSライブラリを使うので、index.html
にlink
を追加します。<react-app/public/index.html>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!---- ↓追加 ---->
<link
rel="stylesheet"
href="https://unpkg.com/tachyons@4.2.1/css/tachyons.min.css"
/>
...
index.css
を下記に修正します。<react-app/src/styles/index.css>
body {
margin: 0;
padding: 0;
font-family: Verdana, Geneva, sans-serif;
}
input {
max-width: 500px;
}
.gray {
color: #828282;
}
.orange {
background-color: #ff6600;
}
.background-gray {
background-color: rgb(246,246,239);
}
.f11 {
font-size: 11px;
}
.w85 {
width: 85%;
}
.button {
font-family: monospace;
font-size: 10pt;
color: black;
background-color: buttonface;
text-align: center;
padding: 2px 6px 3px;
border-width: 2px;
border-style: outset;
border-color: buttonface;
cursor: pointer;
max-width: 250px;
}
*Apolloのインストール
下記コマンドを実行して Apollo をインストールします。apollo-boost
----- セットアップに必要なメモリキャッシュやエラー処理、状態管理などをまとめたパッケージです。react-apollo
----- React コンポーネントから GraphQL サーバにアクセスするためのパッケージです。graphql
----- GraphQL のパーサーです。
$ npm install apollo-boost react-apollo graphql --save
index.js
に下記を追加します。ApolloProvider
コンポーネントでその他のコンポーネントを囲うことで、子コンポーネントから GraphQL にアクセスできるようになります。<react-app/src/index.js>
// apolloパッケージからインポート
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
// GraphQL APIに接続
const httpLink = createHttpLink({
uri: "http://localhost:4000"
});
// ApolloClientのインスタンスを作成
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
});
// Reactアプリのルートコンポーネントをレンダリング
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
*バックエンドの実装(ダウンロード)
バックエンドは実装済みのプロジェクトをダウンロードして使います。React プロジェクト配下で下記コマンドを実行するとserver
ディレクトリが作成されます。$ curl https://codeload.github.com/howtographql/react-apollo/tar.gz/starter | tar -xz --strip=1 react-apollo-starter/server
server
ディレクトリの中は下記になっています。schema.graphql
でスキーマを定義しています。server
├── node_modules
├── package-lock.json
├── package.json
├── prisma
├── yarn.lock
└── src
├── generated
│ └── prisma-client
│ ├── index.d.ts
│ ├── index.js
│ └── prisma-schema.js
├── index.js
├── resolvers
│ ├── Link.js
│ ├── Mutation.js
│ ├── Query.js
│ ├── Subscription.js
│ ├── User.js
│ └── Vote.js
├── schema.graphql
└── utils.js
schema.graphql
のスキーマ言語に従ってレスポンスが生成されます。オブジェクト毎ににフィールドを定義します。フィールドには型を定義することができます。Type
----------- 言語でいうクラスのような使い方をする複数のフィールドからなる型!
--------------- 必須項目Field
---------- 名前 : 型Interface
----- 複数のTypeに共通のフィールドを持たせられるUnion
----------- 定義された型のうちどちらかを示す抽象型Scalar
---------- ただ1つの値からなる型Enum
------------- 特定の値をもつ型(Scalar
の一種)
scalar DateTime
"---- queryのルートとなる定義 ----"
type Query {
info: String!
feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): Feed!
}
enum LinkOrderByInput {
description_ASC
description_DESC
url_ASC
url_DESC
createdAt_ASC
createdAt_DESC
}
type Feed {
links: [Link!]!
count: Int!
}
type Mutation {
post(url: String!, description: String!): Link!
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
vote(linkId: ID!): Vote!
}
type Subscription {
newLink: Link
newVote: Vote
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String!
email: String!
links: [Link!]!
}
type Link {
id: ID!
createdAt: DateTime!
description: String!
url: String!
postedBy: User
votes: [Vote!]!
}
type Vote {
id: ID!
link: Link!
user: User!
}
*デモサーバーの起動
prisma
をインストールしデプロイします。ターミナルで下記コマンドを実行します。使うサーバーを聞かれるので
Demo server + MySQL database
を選択します。$ cd server/
$ npm install
$ sudo npm install -g prisma
$ prisma deploy
? Set up a new Prisma server or deploy to an existing server? Demo server + MySQL database
GitHub の認証を行います。
認証後にリージョンなどを聞かれるので適宜答えます。
? Choose the region of your demo server XXXX/demo-eu1
? Choose a name for your service server
? Choose a name for your stage dev
server
ディレクトリで下記コマンドを実行し、サーバーを起動します。$ npm start
下記にアクセスすると GraphQL Playground が表示されます。
http://localhost:4000
GraphQL はクエリ言語を使って API のリクエストを行います。書き方は主に3パターンあります。
query
-------------- データ取得mutation
---------- データ更新/登録subscription
----- サーバーサイドからのイベント通知
mutation
として POST する処理を定義しています。mutation CreatePrismaLink {
post(
description: "Prisma turns your database into a GraphQL API",
url: "https://www.prismagraphql.com"
) {
id
}
}
mutation CreateApolloLink {
post(
description: "The best GraphQL client for React",
url: "https://www.apollographql.com/docs/react/"
) {
id
}
}
下記を実行すると、登録したデータを参照することができます。
query GetLink{
feed {
links {
id
description
url
}
}
}
JSON形式で実行結果が得られます。
{
"data": {
"feed": {
"links": [
{
"id": "cjz5jo3sc8vgb0b53tox8j41q",
"description": "The best GraphQL client for React",
"url": "https://www.apollographql.com/docs/react/"
},
{
"id": "cjz6v5rziank70b53olfiymr3",
"description": "Prisma turns your database into a GraphQL API",
"url": "https://www.prismagraphql.com"
},
{
"id": "cjz6v5xrdanpz0b53dc5k2omf",
"description": "Prisma turns your database into a GraphQL API",
"url": "https://www.prismagraphql.com"
}
]
}
}
}
*モックデータの表示
React アプリでデータを表示できるようにします。まずはモックデータで表示し、そのあと GraphQL を使って表示させます。
Link.js
のコンポーネントを新規作成し、props
で受け取ったデータを表示するようにします。<react-app/src/components/Link.js>
import React, { Component } from "react";
class Link extends Component {
render() {
return (
<div>
<div>
{this.props.link.description} ({this.props.link.url})
</div>
</div>
);
}
}
export default Link;
LinkList.js
のコンポーネントを新規作成し、モックデータの定義とLink
コンポーネントの呼び出しをします。<react-app/src/components/LinkList.js>
import React, { Component } from "react";
import Link from "./Link";
class LinkList extends Component {
render() {
const linksToRender = [
{
id: "1",
description: "Prisma turns your database into a GraphQL API!",
url: "https://www.prismagraphql.com"
},
{
id: "2",
description: "The best GraphQL client",
url: "https://www.apollographql/docs/react/"
}
];
return (
<div>
{linksToRender.map(link => (
<Link key={link.id} link={link} />
))}
</div>
);
}
}
export default LinkList;
App.js
からLinkList
コンポーネントを呼び出すよう修正します。<react-app/src/components/App.js>
import React, { Component } from "react";
// ---- ↓追加 ----
import LinkList from "./LinkList";
class App extends Component {
render() {
// ---- ↓修正 ----
return <LinkList />;
}
}
export default App;
アプリケーションを起動するとモックデータが表示されます。
$ npm start
*GraphQLクエリを使ったデータ表示
LinkList.js
を下記に修正します。react-apollo
のQuery
コンポーネントを使ってクエリの結果を取得できるようにします。取得した結果はdata
に入ります。<react-app/src/components/LinkList.js>
import React, { Component } from "react";
import Link from "./Link";
// ---- ↓インポートを追加 ----
import { Query } from "react-apollo";
import gql from "graphql-tag";
// ---- gqlでGraphQLの文字列を解析 ----
const FEED_QUERY = gql`
{
feed {
links {
id
createdAt
url
description
}
}
}
`;
class LinkList extends Component {
render() {
return (
// ---- Queryコンポーネントで結果を取得 ----
<Query query={FEED_QUERY}>
{({ loading, error, data }) => {
// ---- 処理が進行中 -----
if (loading) return <div>Fetching</div>;
// ---- リクエストが失敗 -----
if (error) return <div>Error</div>;
const linksToRender = data.feed.links;
return (
<div>
{linksToRender.map(link => (
<Link key={link.id} link={link} />
))}
</div>
);
}}
</Query>
);
}
}
export default LinkList;
サーバー用と React アプリ用のサーバ2種類を起動する必要があります。
server
ディレクトリ配下で下記コマンドを実行して、サーバーを起動します。/react-app/server
$ npm start
クライアント側のサーバーを起動します。
/react-app/
$ npm start
下記にアクセスすると、Playground で登録したデータが表示されます。
http://localhost:3000/
*所感
実装方法や書き方を覚える必要はありますが、型の定義など直感的で覚えやすく、他の言語にも多く対応しているので使えるケースは多そうだと感じました。(Go や Java、PHP、Python、Scala、Ruby など)サーバーサイドの実装を気にせずにクライアント側で欲しいデータだけ要求できるといった点が特に魅力的でした。昔はエンドポイントが1つだったことで、アクセス解析ができないといったデメリットもあったそうですが、現在は
Apollo Engine
というサービスで解決できるようになったようです。現在も活発にOSS開発がされているようなので、今後ますます使いやすくなるのではと期待しています。Sign up here with your email
ConversionConversion EmoticonEmoticon