■ はじめに
こんにちは。
株式会社iimonでエンジニアをしている遠藤です。
「GraphQL」という用語をよく耳にするのですが、実際に触ったことがなく、どのようなものなのかの理解が曖昧でした。
そのためこの機会に、そもそもGraphQLとはどういうものなのかをRESTと比較しながら、どういった場面で使うのが良いのかや基本的な仕様について、実際にアプリケーションを構築しながら整理していきたいと思います。
■ GraphQLとは
GraphQLとは、API向けのクエリ言語であり、既存のデータを使ってクエリを実行するためのランタイムです。
GraphQLは、必要な特定のデータをサーバーにリクエストすることができ、データの過剰フェッチや過小フェッチをなくすことができます。クライアントが必要なデータの構造を指定すると、サーバーはそのデータで正確に応答するため、ネットワークトラフィックが削減され、パフォーマンスが向上します。
また、1 回のリクエストで多数のソースにアクセスできるため、ネットワークの呼び出し回数や帯域幅の要件が緩和され、アプリケーションによって消費されるバッテリー寿命や CPU サイクルを節約できます。
GraphQLはまた、クライアントとサーバー間で機能する、強く型付けされたスキーマを提供しています。スキーマには型とフィールドを定義し、クライアントがAPIの機能を発見し理解することを可能にします。
■ RESTとの比較
具体的にRESTと比較しながら、GraphQLについて整理します。
ここでは、RESTについての詳しい説明は省くので、以下参考になりそうなリンクを貼らせていただきます。
https://aws.amazon.com/jp/what-is/restful-api/
類似点
- アーキテクチャ
- リソースベースのデザイン
- リソース(クライアントが API を介してアクセスして操作できるあらゆるデータまたはオブジェクト)を中心にデータ交換を設計している。各リソースには、一意のIDと、クライアントが実行できる一連の操作 (HTTP メソッド) がある。
- データ交換
- 言語とデータベースの中立性
- クライアント側とサーバー側の両方で、あらゆるデータベース構造とプログラミング言語で動作するため、どのアプリケーションとも高度に相互運用できる。
相違点
REST API はアプリケーション通信のアーキテクチャコンセプトであるのに対し、GraphQL は仕様であり、API クエリ言語であり、ツールのセットでもあります。
また、リクエストやレスポンスの形式を比較すると以下の図のようになります。図はECサイトでユーザー名と購入履歴を取得するような例です。
REST APIは、アクションを決定するHTTP動詞と処理対象となるリソースを識別するエンドポイントを指定してリクエストを送るため、複数のリソースにアクセスするには複数回のREST APIリクエストが必要となります。また、REST API は常にデータセット全体を返すため、ユーザー名のみ取得したい場合にも、すべてのデータを返却します。
GraphQLの場合、クエリは 1 回の API リクエストとレスポンス交換で、必要なデータを取得することができます。また、GraphQL は内部的にはすべてのクライアントリクエストを POST HTTP リクエストとして送信します。
その他、RESTとGraphQLには以下のような違いがあります。
- サーバー側スキーマ
- バージョニング
- エラー処理
■ ハンズオン
フロントはReact バックエンドはNodejsを用いて実際にプロジェクトを作成してみます。
プロジェクト用ディレクトリを作成し移動する
$ mkdir sample-graphql-api && cd $_
バックエンド用のフォルダを作成し、backendフォルダ配下にpackage.jsonとindex.jsを作成
$ mkdir backend && cd $_ $ npm init -y $ touch index.js
バックエンドで使用するライブラリ(graphql とapollo-server)を開発環境にインストール
$ npm i graphql apollo-server -D
開発サーバーを開くために、package.jsonの”scripts”の中身にdevコマンド追加
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "node index.js" },
index.jsの中身を以下のように修正
const {ApolloServer, gql} = require("apollo-server"); // 擬似的なデータを定義する const drinks = [ {name: "コーラ", price: 200}, {name: "ラムネ", price: 210}, {name: "コーヒー", price: 100}, {name: "紅茶", price: 110}, ] // GraphQLのスキーマを定義する(どのようにAPIを呼び出すか) const typeDefs = gql` # ここで定義した型は、Queryの中で使用することができる type Drink { name: String, price: Int, } # Queryは、データを取得するためのもの # ここで定義した型は、クライアント側で呼び出すことができる type Query { drinks: [Drink] } ` // 呼び出されたkeyに対して、どのようにデータを操作するかを定義する const resolvers = { Query: { drinks: () => drinks, } } const server = new ApolloServer({typeDefs, resolvers}) server.listen().then(({url})=>{ console.log(`Server ready at ${url}`) })
サーバーを立ち上げる
$ npm run dev
以下のような画面が開けばOKです
今回はReact + viteを使用するため、sample-graphql-apiディレクトリで以下を実行
$ npm create vite@latest
色々と聞かれるので、青字のように入力してください
? Project name: frontend ? Select a framework: React ? Select a variant: TypeScript
frontendディレクトリに移動し、開発サーバーを立ち上げる
$ cd frontend $ npm install $ npm run dev
上手くいかない場合はnodeのバージョンを切り替えてみてください
$ nodenv local 19.7.0
フロントで使用するライブラリをインストール
$ cd frontend $ npm i @apollo/client @apollo/react-hooks graphql apollo-boost
クライアント側とAPIを紐づけるため、main.tsxを以下のように修正する
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; // どこにアクセスしに行くのかを指定して通信できるようにする const client = new ApolloClient({ uri: "http://localhost:4000/", cache: new InMemoryCache() }); ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> {/* <App /> 配下すべてのファイルでGraphQLを使えるようになる */} <ApolloProvider client={client}> <App/> </ApolloProvider> </React.StrictMode>, );
クエリを呼び出すクライアント側のコードを記述
$ touch src/Drink.tsx
Drink.tsxの中身
import { useQuery } from "@apollo/client"; import { gql } from '@apollo/client' type DrinkType = { name: string; price: number; }; const DRINKS = gql` query { drinks { name price } } `; const Drink = () => { const {loading, error, data} = useQuery(DRINKS); return ( <div> {loading && <p>取得中...</p>} {error && <p>{error.message}</p>} {data && data.drinks.map((drink: DrinkType, i: number) => ( <div key={i} style={{display:"flex", gap: "6px"}}> <p>商品名:{drink.name}</p> <p>価格:{drink.price}円</p> </div> ))} </div> ) }; export default Drink;
-
App.tsxを以下のように書き換える
import './App.css' import Drink from './Drink' function App() { return ( <div> <h2>ドリンク一覧</h2> <Drink /> </div> ) } export default App
動作確認
以下のように取得されたデータが表示されていればOKです
■ まとめ
実際に軽くGraphQLを触ってみて、簡単にまとめると以下のようなメリットがあることが理解できました。
- 1つのエンドポイントで良いため、API リクエストを処理するために大量のコードを記述しなくて良い
- 複数のデータソースを一回のリクエストで取得できるため、リクエスト数を最小限にできる
- 余計なデータを取得せずに済む
- 型指定でデータが明確になる
そのため、パフォーマンスの向上をより期待できる規模の大きなサービスや、クライアント毎に必要な情報のみを扱えるためマイクロサービスなどを構築する際に使用するとメリットをより感じられそうだと個人的には思いました。
次回は、認証認可周りや、実際のDBとのデータのやり取り、データを更新するミューテーションの実装など、もう少し詳しい部分についても調べられたらなと思います。
参考:
https://youtu.be/u8vD2NESjC0?si=Xfu6ffghQY_2laCZ
https://aws.amazon.com/jp/graphql/guide/
https://aws.amazon.com/jp/graphql/
https://aws.amazon.com/jp/compare/the-difference-between-graphql-and-rest/
https://hasura.io/learn/ja/graphql/intro-graphql/graphql-vs-rest/
https://qiita.com/jabba/items/8d77ab86641937847673
https://zenn.dev/waddy/books/graphql-nestjs-nextjs-bootcamp/viewer/overview_from_backend
https://zenn.dev/saboyutaka/articles/e5515872871534
https://www.apollographql.com/docs/apollo-server/getting-started/