この記事は YAMAP エンジニア Advent Calender 2021 5日目の記事です。
この記事について
フロントエンドの GraphQL 導入においての学び、備忘録を共有する目的で書きました。
この記事では、Apollo Client を用いて React アプリケーションに GraphQL を導入する具体的な方法をまとめています。
Apollo Client とは
簡単に言うと、Web フロントエンド環境(React が主で、integration により他フレームワークに対応)で GraphQL を扱うためのイケてるライブラリです。キャッシュ機構や状態管理機構も備えており、モダンで非常に理解しやすいインターフェースを有しています。
Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI.
セットアップ
Nextjs アプリケーションの構築が出来ていることを前提で進めます。
開発環境
以下の開発環境を想定しています。
- エディタ
- VSCode
- 言語
- JavaScript または TypeScript
- フレームワーク
- Nextjs
- React
ライブラリをインストール
まずは Apollo Client 関連のライブラリをインストールします。
$ yarn add @apollo/client graphql
$ yarn add -D apollo
エディタ拡張機能をインストール
VSCode に Apollo GraphQL Extension をインストールします。
これによって、後述する GraphQL スキーマに基づき、クエリのコード補完がエディタ上で機能するようになります(開発効率爆上がり間違いなしですね・・)。
Apollo の設定ファイルを作成
次に、プロジェクトルートに apollo.cofig.js
ファイルを作成します。
module.exports = {
client: {
includes: ['./src/**/*.js'],
service: {
name: 'yamap-app',
url: 'https://api.yamap.com/graphql',
},
},
};
client.service.url
には GrapQL API のエンドポイントを記述します。
ちなみに Apollo Studio というサービスもあって、これを 使えば幸せになれるという言い伝えがあるようです。 使っている場合、service で登録している Graph の name
を指定すれば OK のようです。
module.exports = {
client: {
includes: ['./src/**/*.js'],
service: 'my-graph-in-apollo-studio'
},
};
スキーマをダウンロード
あらかじめインストールしておいた apollo
パッケージを使ってスキーマをダウンロードします。
$ yarn apollo service:download -c ./apollo.config.js graphql-schema.json
graphql-schema.json
がプロジェクトルートに生成されたと思います。これによって、エディタ(VSCode)上でクエリのコード補完が効くようになります。
実装
実際に Apollo Client を使ってユーザー一覧データを取得してみます。
apolloClient
の実装
@apollo/client
パッケージに含まれる ApolloClient
クラスを使います。
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: "https://api.yamap.com/graphql",
cache: new InMemoryCache({}),
});
export default client;
ApolloProvider
を組み込む
Nextjs アプリケーションの場合、pages 配下に _app.js
ファイルを作成し、Custom App を実装する必要があります。
import { ApolloProvider } from "@apollo/client";
import client from "../apollo-client"; // さっき作った apolloClient を import
function CustomApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default CustomApp;
データの取得(CSR)
まずは useFetch()
を使ったクライアントサイドでのデータ取得です。
クエリ
import { gql } from "@apollo/client"
export const GET_USERS_QUERY = gql`
query ListUsers($first: Int!, $after: String) {
users(first: $first, after: $after) {
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
edges {
cursor
node {
id
databaseId
nickname
}
}
}
}
`
クライアントサイドフェッチ
import { useQuery } from "@apollo/client"
import { GET_USERS_QUERY } from "../users-query"
function Users () {
const { data, loading, error } = useQuery(QUERY, {
variables: {
first: 10,
}
});
const users = data?.users.edges.map(edge => edge.node);
if (loading) {
return <div>Loading...</div>
}
if (error) {
console.error(error);
return null;
}
return (
<div>
<h1>ユーザー一覧</h1>
<div>
{users?.map(user => (
<div key={user.id}>
{user.nickname}
</div>
))}
</div>
</div>
)
}
export default Users;
データの取得(SSR)
クエリは先述の GET_USERS_QUERY
を流用します。
サーバーサイドフェッチ
function Users ({ initialData, initialError }) {
const { data, loading, error } = useQuery(QUERY, {
variables: {
ssr: false, // getServerSideProps によって、既にサーバーサイドでデータ取得済のため
first: 10,
}
});
if (!initialData && loading) {
return <div>Loading...</div>
}
if (initialError || error) {
console.error(error);
return null;
}
const users = data?.users.edges.map(edge => edge.node) || initialData?.users.edges.map(edge => edge.node);
return (
<div>
<h1>ユーザー一覧</h1>
<div>
{users?.map(user => (
<div key={user.id}>
{user.nickname}
</div>
))}
</div>
</div>
)
}
export async function getServerSideProps() {
const { data, error } = await client.query({
query: QUERY,
variables: {
first: 10,
}
});
return {
props: {
initialData: data,
initialError: error,
},
};
以上です
ページネーションを想定しなければ SSR のコードはもう少しシンプルになるかもしれません。
参考になれば幸いです。