Apollo Clientは、ReactアプリからGraphQL APIを呼び出し、その結果を正規化キャッシュに保存する定番のクライアントライブラリです。単なる通信ライブラリではなく、取得済みデータをコンポーネント間で共有し、同じデータの再取得を省く「状態管理を兼ねたデータ層」として使われます。2025年9月にメジャー更新のv4がGAとなり(記事作成時点の最新は4.2系)、Reactフックのimport先や接続設定の書き方が変わりました。本記事は定義から、useQuery・useMutation・キャッシュの使い方、v3からv4への移行までを、現行v4のコードで解説します。GraphQL自体の基礎はGraphQLとは?REST APIとの違い・メリット・デメリットを先に押さえると理解が早まります。
まとめ:この記事の要点
- Apollo ClientはReact(Vue・Angularもコミュニティ対応)でGraphQLを扱うクライアント。クエリ結果をID単位で正規化キャッシュに保存し、再取得を減らす。
- v4(2025年9月GA)でReactフックのimportが
@apollo/client/reactへ分離。接続はコンストラクタのuriを廃しlink(HttpLink)が必須。内部ObservableはRxJSに変更された。 - データ取得は
useQuery、作成・更新・削除はuseMutation、リアルタイム受信はuseSubscriptionで行う。 - キャッシュとネットワークのどちらを優先するかは
fetchPolicyで制御する。既定はcache-first。 - v3からはコードモッド
npx @apollo/client-codemod-migrate-3-to-4で機械的に移行できる。
Apollo Clientとは:役割とGraphQLクライアントとしての位置づけ
Apollo Clientは、GraphQLクエリを送信して結果を受け取り、それをクライアント側のキャッシュに保存するJavaScriptライブラリです。2016年にMeteor Development Group(現Apollo GraphQL)が公開しました。特徴は、レスポンスを画面にそのまま流し込むだけでなく、__typenameとIDを使ってオブジェクト単位で正規化して保存する点にあります。これにより、別の画面で同じ商品を再表示するときにネットワークを介さずキャッシュから返せます。
GraphQLは必要なフィールドだけを1リクエストで取得できる問い合わせ言語で、過不足なくデータを取れる点がREST APIと異なります(詳細はGraphQLとREST APIの違い)。Apollo Clientはその特性を活かし、取得・更新・購読・キャッシュを一貫したAPIで扱えるようにします。React向けが中心ですが、Vue(Vue Apollo)やAngular(Apollo Angular)向けの統合も提供されています。
Relay・urqlとの違いと選び方
同種のGraphQLクライアントにはRelayとurqlがあります。Relayはコンパイラ前提でフラグメント設計を強制し、大規模かつ性能要件が厳しいアプリで威力を発揮しますが、規約が多く学習コストが高めです。urqlは軽量で最小構成から始めやすい一方、正規化キャッシュは追加パッケージで補います。Apollo Clientはその中間で、正規化キャッシュ・開発者ツール・豊富な公式ドキュメントが標準で揃い、初学者から実務まで採用しやすい構成です。「正規化キャッシュを標準で使いたい」「Reactで実績のある選択肢が欲しい」ならApollo Clientが第一候補になります。逆に、バンドルサイズを最小化したい小規模SPAではurqlの方が向く場面もあります。
Apollo Clientのセットアップ(v4・ApolloProvider)
インストールとv4の依存関係
v4では内部のObservable実装がzen-observableからRxJSへ移行し、RxJSがpeer dependencyになりました。そのためrxjsも併せてインストールします。
npm install @apollo/client rxjs graphql
ApolloProviderで接続を張る(v4はlink必須)
v4の最大の破壊的変更が、Reactフックとプロバイダのimport先の分離です。ApolloClient・InMemoryCache・gql・HttpLinkは従来どおり@apollo/clientから、ApolloProviderや各フックは@apollo/client/reactから読み込みます。また、コンストラクタのuri・headers・credentialsは廃止され、linkにHttpLinkを渡す形が必須になりました。Reactのマウントも、React 18で削除されたReactDOM.renderではなくcreateRootを使います。
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { ApolloProvider } from "@apollo/client/react";
import { createRoot } from "react-dom/client";
import App from "./App";
const client = new ApolloClient({
link: new HttpLink({ uri: "https://example.com/graphql" }),
cache: new InMemoryCache(),
});
createRoot(document.getElementById("root")).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
アプリ全体をApolloProviderで包むと、配下のどのコンポーネントからでもフックでGraphQL操作ができるようになります。
データの取得・更新・購読(useQuery/useMutation/useSubscription)
useQueryでクエリを実行する
データ取得はuseQueryフックが基本です。戻り値のloading・error・dataで状態を分岐します(v4ではより細かい状態を表すdataStateも追加されています)。
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
const GET_BOOKS = gql`
query GetBooks {
books { id title author }
}
`;
function Books() {
const { loading, error, data } = useQuery(GET_BOOKS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return data.books.map((b) => <p key={b.id}>{b.title}</p>);
}
クエリ内で共通のフィールド集合を再利用したいときは、GraphQLのフラグメントを使うと重複を減らせます(GraphQLのFragmentとは)。
useMutationで更新し、キャッシュへ反映する
作成・更新・削除はuseMutationで行います。返るのは「実行関数」と「結果オブジェクト」のタプルで、レンダー時に自動実行はされず、任意のタイミングで実行関数を呼び出します。更新後は、サーバーが返した新データをキャッシュへ反映しないと画面が古いままになるため、update関数でcache.modifyを使ってキャッシュを書き換えます。
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";
const ADD_BOOK = gql`
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) { id title author }
}
`;
function AddBook() {
const [addBook, { loading, error }] = useMutation(ADD_BOOK, {
update(cache, { data }) {
cache.modify({
fields: {
books(existing = []) {
return [...existing, data.addBook];
},
},
});
},
});
// 実行: addBook({ variables: { title, author } })
}
上の例は単純な配列への追記ですが、既存キャッシュ内の別オブジェクトを参照する更新では、writeFragmentやtoReferenceを併用するとキャッシュの正規化を壊さずに書き換えられます。なお、v4ではuseMutationの戻り値がFetchResultからMutateResultに変わり、errors(配列)はerror(単一)へ統合されました。v3の書き方でerrorsを参照しているコードは修正が必要です。
useSubscriptionでリアルタイムに受け取る
チャットの新着や在庫の変動など、サーバー起点の更新を受け取るにはuseSubscriptionを使います。WebSocket用のリンク設定が別途必要ですが、フックの使い方自体はシンプルです。
import { gql } from "@apollo/client";
import { useSubscription } from "@apollo/client/react";
const BOOK_ADDED = gql`
subscription BookAdded {
bookAdded { id title author }
}
`;
function LiveBooks() {
const { data } = useSubscription(BOOK_ADDED);
return <p>{data?.bookAdded?.title}</p>;
}
fetchPolicyでキャッシュとネットワークを使い分ける
各クエリがキャッシュとネットワークのどちらを優先するかはfetchPolicyで決めます。読み取り主体の一覧はcache-first、常に最新が必要な残高表示などはnetwork-only、というように用途で選び分ける。既定はcache-firstで、多くの画面はそのままで十分です。
| fetchPolicy | 挙動 | 向く用途 |
|---|---|---|
| cache-first(既定) | キャッシュ優先、無ければ取得 | 更新頻度が低い一覧 |
| cache-and-network | キャッシュ即表示+裏で再取得 | 体感速度と鮮度の両立 |
| network-only | 常にネットワーク取得 | 残高・在庫など最新必須 |
| cache-only | ネットワークを使わない | オフライン表示 |
| no-cache | 取得するが保存しない | 使い捨ての一時データ |
const { data } = useQuery(GET_BOOKS, {
fetchPolicy: "cache-and-network",
});
InMemoryCacheの正規化とキャッシュ管理
正規化と__typenameの役割
Apollo Clientのキャッシュは、レスポンスを丸ごと保存するのではなく、各オブジェクトを__typenameとIDの組(例:Book:1)をキーに正規化して保存します。__typenameはApollo Clientが自動でクエリに付与するため、通常は意識せず正規化の恩恵を受けられます。IDが一意なら、あるクエリで更新したオブジェクトが、別のクエリの結果にも自動で反映されます。IDフィールドがidや_idでない場合は、keyFieldsで指定します。
typePoliciesで結合ルールを定義する
ページネーションのように「既存の配列に追記したい」ケースでは、フィールドごとのmerge関数をtypePoliciesで定義します。定義しないと、後のクエリ結果が前の結果を上書きしてしまいます。
const cache = new InMemoryCache({
typePolicies: {
Book: { keyFields: ["id"] },
Query: {
fields: {
books: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
Apollo Client 4の主な変更点と移行
v4はv3からの互換を一部断つメジャー更新です。React機能をコアから切り離して@apollo/client/reactへ移し、フレームワーク非依存の設計に整理しました。これにビルド対象を2023年以降のブラウザとNode.js 20以上へ絞り込んだこと、ESM対応とtree-shakingの改善が相まって、バンドルサイズへの寄与を20〜30%削減しています。主な差分は次のとおりです。
| 変更点 | v3 | v4 |
|---|---|---|
| Reactフックのimport | @apollo/client | @apollo/client/react |
| 接続設定 | uri直接指定が可 | link(HttpLink)必須 |
| 内部Observable | zen-observable | RxJS(peer依存) |
| useMutationの戻り | FetchResult(errors) | MutateResult(error) |
| SSR | getDataFromTree等 | prerenderStatic |
手作業での置き換えは漏れが出やすいため、公式のコードモッドで機械的に移行するのが確実です。importの付け替えやコンストラクタ引数の書き換えを自動で行います。
npx @apollo/client-codemod-migrate-3-to-4 src
移行が必要かの判断は明快です。新規開発なら最初からv4で始めるべきです。既存のv3プロジェクトは、動いている限り急いで上げる必要はありませんが、バンドル削減とTypeScriptの型強化(useQuery.Optionsなどの名前空間型、必須変数の型チェック強化)の恩恵は大きいため、まとまった改修のタイミングでコードモッドをかけるのが現実的です。
よくある質問
Apollo ClientとApollo Serverの違いは?
Apollo Clientはフロントエンド側でGraphQLを呼び出すクライアントライブラリ、Apollo Serverはバックエンドでスキーマとリゾルバを実装するGraphQLサーバーです。役割は正反対で、両者を組み合わせても、別々の技術と組み合わせても構いません。本記事はクライアント側を扱っています。
Apollo ClientはReact以外でも使えますか?
使えます。コアの@apollo/clientはフレームワーク非依存で、Reactは@apollo/client/react、VueはVue Apollo、AngularはApollo Angularという統合が用意されています。v4でReact部分がコアから分離されたことで、この非依存性がより明確になりました。
Apollo Client 4でuseQueryのimportはどこからですか?
@apollo/client/reactからです。v3では@apollo/clientから読み込めましたが、v4でReactフック(useQuery・useMutation・useSubscriptionなど)とApolloProviderはサブパスへ移りました。gqlやInMemoryCacheなど非React部分は引き続き@apollo/clientから読み込みます。
fetchPolicyは何を選べばよいですか?
迷ったら既定のcache-firstのままで問題ありません。表示速度と鮮度を両立したい画面はcache-and-network、残高や在庫のように常に最新が必要ならnetwork-onlyを選びます。
Apollo ClientはTypeScriptに対応していますか?
対応しています。v4ではuseQuery.OptionsやuseQuery.Resultといった名前空間型が導入され、必須変数の型チェックも強化されました。GraphQL Code Generatorと組み合わせると、クエリから型を自動生成できます。