🐡

【GraphQL】apollo/clien, codegenでより効率良く、型安全にデータ取得しよう!

2022/05/31に公開約5,600字

こんにちは、@nerusanです。
皆さんは、APIを利用したことは有馬でしょうか。フロントエンドエンジニアであれば必ず使ったことがあると思います。

sample.ts
async () => {
  const result = await axios.get('https://api.com/user/1'); // result.dataには、たとえば{"Id":1,"Name":"山田太郎","Birth":"April","Age":20}が入る
  console.log(result.data.name); // nameのみ表示
}

こちらは、RESTAPIと言われる取得の仕方です。馴染みがあるものかと思います。
しかし、RESTAPIには、問題点があります。

  1. 利用しないデータも多く含まれれ、パフォーマンスがあまり良くない
  2. 複数エンドポイントがある

1.については、皆さんも経験あると思います。
API経由で、以下のような情報が手に入ることを考えます。
API経由で取得した値のNameのみ画面を表示したいと考えます。
そうなった場合、Id, Birth, Ageは不要なプロパティとなります。不要なプロパティを取得することは、取得にも時間を要しますし、フロント側で解析する手間もかかります。

{"Id":1,"Name":"山田太郎","Birth":"April","Age":20}

2.は、ユーザ、商品など、リソースごとにエンドポイントを変更する必要があります。

# ユーザデータ処理
https://api.com/users
# 商品データ処理
https://api.com/products

また、バージョンに応じても、エンドポイントが増えたりもします。

# ユーザデータ処理version1
https://api.com/v1/users
# ユーザデータ処理version2
https://api.com/v2/users

リソースごとにエンドポイントを指定する必要があるので、フロントエンド側では、結構記述が大変になったりします。

その問題を解決するために、GraphQLが生まれました。
こちらは、Facebook社が考えたデータを取得するための新たな規格です。

GraphQLとは

GraphQLの説明は公式ページでは以下のように説明されています。

GraphQLは、APIのクエリ言語であり、既存のデータでこれらのクエリを実行するためのランタイムです。GraphQLは、API内のデータの完全で理解しやすい説明を提供し、クライアントが必要なものだけを正確に要求できるようにし、時間の経過とともにAPIを進化させやすくし、強力な開発者ツールを可能にします。

https://graphql.org/

つまり、データを取得するためのクエリ言語です。
上記挙げた問題を解決してくれます。

何はともあれ、具体的な取得方法を見てみましょう。

使い方

今回GitHubが公開しているデータをGraphQLによりアクセスすることを考えます。

1. Reactの環境構築

まずはReactの環境を構築します。typescirptを利用するので、オプションをつけます。

$ npx create-react-app graphql-github --template typescript
$ cd graphql-github
$ npm start 

これでhttp://localhost:3000/にアクセスで最初の画面が見れます。

2. パッケージ導入

次に、必要なパッケージを導入します。
今回は、graphQLの取得クライアントとして、apollop/clientを利用します。
また、TypeScpritのレスポンスの型生成するためにgraphql-code-generate(codegen)を利用します。codegenは必ずしも必要で張りませんが、手動で型定義を書くのは、時間もかかり、Typoする可能性もあり、メンテナンスもめんどくさいです。なので、codegenを利用することをおすすめします。

# applo/clientの導入
$ npm i @apollo/client

# codegenとgraphqlの導入
$ npm i @graphql-codegen/cli graphql

# codegenのプラグイン追加
$ npm i @graphql-codegen/typed-document-node @graphql-codegen/typescript @graphql-codegen/typescript-operations 

3. リクエストのためのリクエストクエリ及び型の生成

クエリファイルを生成します。実際にGitHubより取得したいクエリを、*.grapql拡張子のファイルに記載します。クエリ名は任意に決めることができます。今回は、UserInfoにしています。

src/query.graphql
query UserInfo {
  viewer {
    login
  }
}

次に、codegenerateの設定ファイルを定義します。クエリファイルの指定、エンドポイントの指定、型定義ファイルの生成先を指定します。今回は、GitHubのGraphQLを利用するので、アクセストークンが必要であり、リクエストヘッダーに指定する必要があります。取得方法はここを参照。

codegen.yml
schema:
  - https://api.github.com/graphql: # エンドポイントのURL(通常は.envなどで管理)
      headers:
        "Authorization": "Bearer <GitHubのToken>"
documents: "./src/**/*.graphql" # クエリファイルの指定
generates:
  ./src/generated.ts: # 型定義のファイル作成先
    plugins: # プラグインを指定
      - typescript
      - typescript-operations
      - typed-document-node

型生成ファイルの生成コマンドを指定します。

package.json
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
+   "generate": "graphql-codegen"
  },

型生成コマンドを実行します。

shell
$ npm run generate
> graphql-github-api@0.1.0 generate
> graphql-codegen

  ✔ Parse configuration
  ✔ Generate outputs

codege.ymlとquery.graphqlにより実際にリクエストをして、型定義ファイルとクエリパラメータドキュメントを作成します。
codegen.ymlgeneratesに指定したフォルダにファイルができたら成功です。今回は、src/generated.tsに生成されます。

4. アプリ内で値を取得する

まず、アプリ内でapollo/clientを利用するための設定をします。

index.tsx
+ import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
  import React from "react";
  import ReactDOM from "react-dom/client";
  import App from "./App";
  import "./index.css";


const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

+const client = new ApolloClient({
+  uri: "https://api.github.com/graphql",  // エンドポイント
+  headers: { Authorization: "Bearer <GitHubアクセストークン>" },  // ヘッダー
+  cache: new InMemoryCache(),  // キャッシュを利用する設定
+});

root.render(
  <React.StrictMode>
+   <ApolloProvider client={client}>
      <App />
+   </ApolloProvider>
  </React.StrictMode>
);

これでAppコンポーネント内でapollo/clientが使えるようになります。

次に、実際に値を取得します。generated.tsよりクエリのためのドキュメント(*Document)と型定義ファイル(*Query)をimportします。今回はクエリ名をUserInfoにしたので、UserInfoQueryとUserInfoDocumentです。

App.tsx
import { useQuery } from "@apollo/client";
import React from "react";
import "./App.css";
import { UserInfoDocument, UserInfoQuery } from "./generated";

function App() {
  const { data, loading, error } = useQuery<UserInfoQuery>(UserInfoDocument);

  return <p>{data?.viewer.login}</p>;
}

export default App;

data変数にアクセスすると、型補完が効いていることがわかると思います。便利ですね。

以上で、ブラウザに自身のユーザ名が表示されたら成功です。
レスポンスの型定義ファイルもcodegenを利用すれば自動で作成してくれるので、コードがよりすっきりしますし、記述時間の削減、Typoによるミスなどが減り、とても効率的になりますので、是非、積極的に利用したいですね。
詳しいコードは、GitHub参照ください。

https://github.com/Yuki-TU/graphql-github-api/tree/main/src

まとめ

どうだったでしょうか。実際に会社で導入などになると、サーバーサイドも対応する必要があるので、要検討する必要があります。フロントエンド側では、処理が簡単になり、恩恵が大きいですが、サーバサイド側の処理が、RESTAPIより複雑になるので、少し大変になります。

また、graphQLは自由度が高くなるため、それに伴い問題も生じます。詳しくは、こちらの記事を参考にするといいです。

https://engineering.mercari.com/blog/entry/20220303-concerns-with-using-graphql/

参考

https://www.apollographql.com/docs/

https://www.graphql-code-generator.com/docs/guides/react#optimal-configuration-for-apollo-and-urql

Discussion

ログインするとコメントできます