モバイルアプリ開発では、画面ごとに必要なデータが異なり、REST APIでは過剰取得(over-fetching)や取得不足(under-fetching)が頻発します。GraphQLは「必要なフィールドだけを1回のリクエストで取得する」設計思想を持ち、この問題を根本から解消します。
React NativeとGraphQLの組み合わせは、iOSとAndroidの両プラットフォームで統一的なデータ取得レイヤーを実現し、通信量の削減・開発速度の向上・型安全性の強化に直結します。
GraphQLがReact Nativeに適している理由
REST APIとの根本的な違い
REST APIでは、エンドポイントごとに返却されるデータ構造が固定されています。たとえばユーザープロフィール画面で「名前」と「アバターURL」だけが必要でも、/api/users/123は住所や電話番号など不要なフィールドも含めて返します。
GraphQLでは、クライアント側がクエリで必要なフィールドを明示的に指定します。
query UserProfile($id: ID!) {
user(id: $id) {
name
avatarUrl
}
}
このアプローチにより、モバイル回線でのデータ転送量を最小化できます。
モバイル開発特有の利点
| 課題 | REST APIでの対応 | GraphQLでの対応 |
|---|---|---|
| 画面ごとに必要なデータが異なる | エンドポイントの乱立またはクエリパラメータで制御 | クエリで必要フィールドを指定 |
| 複数リソースの同時取得 | 複数のAPI呼び出しが必要 | 1つのクエリで複数リソースを結合 |
| リアルタイム更新 | WebSocket・ポーリングを自前実装 | Subscriptionとして標準サポート |
| APIバージョニング | URL・ヘッダーで管理 | スキーマの拡張で後方互換を維持 |
| 型安全性 | OpenAPI+コード生成が必要 | スキーマから直接TypeScript型を生成 |
React Native向けGraphQLクライアント比較
React NativeでGraphQLを利用する際、主要な選択肢は3つあります。プロジェクトの規模や要件に応じて最適なクライアントを選ぶ必要があります。
主要クライアント3種の特性
| 特性 | Apollo Client 4.0 | urql 5.x | graphql-request 7.x + TanStack Query |
|---|---|---|---|
| 最新バージョン | 4.0(2025年8月リリース) | 5.0.1 | 7.4.0 + TanStack Query 5.x |
| バンドルサイズ | 約30KB(minified) | 約14KB(minified) | 約5KB + TanStack Query分 |
| 正規化キャッシュ | 標準搭載(InMemoryCache) | Exchange追加で対応(@urql/exchange-graphcache) | なし(TanStack Queryのクエリキャッシュ) |
| Subscription対応 | WebSocket・HTTP multipart | Exchange追加で対応 | 別途実装が必要 |
| React Native公式サポート | 公式ドキュメントあり | 公式対応 | 制限なし |
| TypeScript | フル対応 | フル対応 | フル対応 |
| Devtools | React Native Debugger連携 | urql Devtools | TanStack Query Devtools |
| 学習コスト | 高い | 中程度 | 低い |
| npmダウンロード数/週 | 約600万 | 約20〜37万 | 約520万(graphql-request単体) |
選定の目安
- Apollo Client: 大規模アプリ・正規化キャッシュが必須・Subscriptionを多用する場合
- urql: 中規模アプリ・バンドルサイズを抑えたい・拡張性を重視する場合
- graphql-request + TanStack Query: 小〜中規模アプリ・既にTanStack Queryを使用中・シンプルな構成を好む場合
Apollo Client 4.0によるセットアップ
Apollo Client 4.0は2025年8月にリリースされた最新メジャーバージョンです。RxJSがpeer dependencyとして追加され、バンドルサイズが20〜30%削減されました。
パッケージのインストール
npx expo install @apollo/client graphql rxjs
Apollo Client 4.0からはrxjsがpeer dependencyとなっているため、明示的なインストールが必要です。
基本セットアップ
src/lib/apollo.tsを作成し、クライアントインスタンスを初期化します。
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const httpLink = new HttpLink({
uri: "https://your-api.example.com/graphql",
});
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
Apollo Client 4.0では、フレームワーク非依存のコア機能は@apollo/clientから、Reactフック(useQueryなど)は@apollo/client/reactからインポートする構成に整理されています。
ApolloProviderでアプリをラップ
App.tsxでプロバイダーを設定します。
import { ApolloProvider } from "@apollo/client/react";
import { client } from "./src/lib/apollo";
export default function App() {
return (
<ApolloProvider client={client}>
<Navigation />
</ApolloProvider>
);
}
Queryの実装
import { useQuery, gql } from "@apollo/client/react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
interface User {
id: string;
name: string;
email: string;
}
interface GetUsersData {
users: User[];
}
function UserList() {
const { loading, error, data } = useQuery<GetUsersData>(GET_USERS);
if (loading) return <ActivityIndicator size="large" />;
if (error) return <Text>エラー: {error.message}</Text>;
return (
<FlatList
data={data?.users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 16 }}>
<Text style={{ fontWeight: "bold" }}>{item.name}</Text>
<Text>{item.email}</Text>
</View>
)}
/>
);
}
Mutationの実装
import { useMutation, gql } from "@apollo/client/react";
const CREATE_USER = gql`
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
function CreateUserForm() {
const [createUser, { loading }] = useMutation(CREATE_USER, {
refetchQueries: ["GetUsers"],
});
const handleSubmit = async (name: string, email: string) => {
await createUser({
variables: { name, email },
});
};
// フォームUIの実装...
}
テキストストリーミング対応(@defer・Subscription over HTTP)
React Nativeの標準fetchはXHRベースでテキストストリーミングに対応していません。@deferディレクティブやHTTP multipart Subscriptionを使う場合は、ポリフィルの追加が必要です。
npx expo install react-native-fetch-api react-native-polyfill-globals
アプリのエントリーポイント(index.jsなど)の先頭にポリフィルを記述します。
import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
import { polyfill as polyfillFetch } from "react-native-polyfill-globals/src/fetch";
polyfillReadableStream();
polyfillEncoding();
polyfillFetch();
HttpLinkにストリーミングオプションを追加します。
const httpLink = new HttpLink({
uri: "https://your-api.example.com/graphql",
fetchOptions: {
reactNative: { textStreaming: true },
},
});
urqlによる軽量セットアップ
urqlはバンドルサイズがApollo Clientの半分以下で、Exchange(プラグイン)によるモジュラーな設計が特長です。
パッケージのインストール
npx expo install urql graphql
基本セットアップ
import { Client, Provider, cacheExchange, fetchExchange } from "urql";
const urqlClient = new Client({
url: "https://your-api.example.com/graphql",
exchanges: [cacheExchange, fetchExchange],
});
export default function App() {
return (
<Provider value={urqlClient}>
<Navigation />
</Provider>
);
}
Queryの実装
import { useQuery, gql } from "urql";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
function UserList() {
const [result] = useQuery({ query: GET_USERS });
const { data, fetching, error } = result;
if (fetching) return <ActivityIndicator size="large" />;
if (error) return <Text>エラー: {error.message}</Text>;
return (
<FlatList
data={data?.users}
keyExtractor={(item: { id: string }) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 16 }}>
<Text style={{ fontWeight: "bold" }}>{item.name}</Text>
<Text>{item.email}</Text>
</View>
)}
/>
);
}
正規化キャッシュの追加
urqlの標準キャッシュはドキュメントキャッシュ(クエリ単位)です。エンティティ単位の正規化キャッシュが必要な場合は@urql/exchange-graphcacheを追加します。
npx expo install @urql/exchange-graphcache
import { Client, fetchExchange } from "urql";
import { cacheExchange } from "@urql/exchange-graphcache";
const urqlClient = new Client({
url: "https://your-api.example.com/graphql",
exchanges: [
cacheExchange({
keys: {
// エンティティのキー設定
},
}),
fetchExchange,
],
});
GraphQL Code Generatorで型安全性を確保
GraphQL Code Generator(@graphql-codegen/cli v6.x)を使うと、GraphQLスキーマからTypeScriptの型定義を自動生成できます。手動でinterfaceを定義する必要がなくなり、スキーマ変更時の型不整合を防止できます。
インストール
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/client-preset
codegen.tsの設定
プロジェクトルートにcodegen.tsを作成します。
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "https://your-api.example.com/graphql",
documents: ["src/**/*.{ts,tsx}"],
generates: {
"./src/__generated__/": {
preset: "client",
config: {
documentMode: "string",
},
},
},
};
export default config;
package.jsonにスクリプトを追加
{
"scripts": {
"codegen": "graphql-codegen --config codegen.ts",
"codegen:watch": "graphql-codegen --config codegen.ts --watch"
}
}
生成された型の利用
型生成を実行すると、src/__generated__/ディレクトリにTypeScriptファイルが出力されます。
npm run codegen
生成された型付きドキュメントをコンポーネントで利用します。
import { useQuery } from "@apollo/client/react";
import { graphql } from "../__generated__/gql";
const GET_USERS = graphql(`
query GetUsers {
users {
id
name
email
}
}
`);
function UserList() {
// data の型が自動的に推論される
const { loading, data } = useQuery(GET_USERS);
return (
<FlatList
data={data?.users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
}
Fragment Colocationパターン
コンポーネントが必要とするデータをFragmentとして宣言し、親コンポーネントのクエリに組み込むパターンです。データ依存関係が明確になり、大規模アプリの保守性が向上します。
// UserCard.tsx
import { graphql, FragmentType, useFragment } from "../__generated__/gql";
const UserCardFragment = graphql(`
fragment UserCardFields on User {
id
name
avatarUrl
}
`);
interface UserCardProps {
user: FragmentType<typeof UserCardFragment>;
}
function UserCard({ user }: UserCardProps) {
const data = useFragment(UserCardFragment, user);
return (
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Image source={{ uri: data.avatarUrl }} style={{ width: 40, height: 40 }} />
<Text>{data.name}</Text>
</View>
);
}
// UserListScreen.tsx
const GET_USERS = graphql(`
query GetUsers {
users {
...UserCardFields
}
}
`);
function UserListScreen() {
const { data } = useQuery(GET_USERS);
return (
<FlatList
data={data?.users}
renderItem={({ item }) => <UserCard user={item} />}
/>
);
}
Subscriptionによるリアルタイム通信
GraphQL Subscriptionは、サーバーからクライアントへリアルタイムにデータをプッシュする仕組みです。チャット・通知・ライブフィードなどの機能に適しています。
Apollo ClientでのWebSocket Subscription
npx expo install graphql-ws
import { ApolloClient, InMemoryCache, HttpLink, split } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { getMainDefinition } from "@apollo/client/utilities";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: "https://your-api.example.com/graphql",
});
const wsLink = new GraphQLWsLink(
createClient({
url: "wss://your-api.example.com/graphql",
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
コンポーネントでの利用:
import { useSubscription, gql } from "@apollo/client/react";
const MESSAGE_ADDED = gql`
subscription OnMessageAdded($channelId: ID!) {
messageAdded(channelId: $channelId) {
id
text
createdAt
sender {
name
}
}
}
`;
function ChatMessages({ channelId }: { channelId: string }) {
const { data, loading } = useSubscription(MESSAGE_ADDED, {
variables: { channelId },
});
// data?.messageAdded に新着メッセージが入る
}
キャッシュ戦略とパフォーマンス最適化
Apollo Clientのキャッシュ設計
Apollo ClientのInMemoryCacheは、レスポンスデータを__typenameとidフィールドの組み合わせで正規化して保存します。同一エンティティが複数のクエリで返されても、キャッシュ内では1つの参照として統合されます。
const cache = new InMemoryCache({
typePolicies: {
User: {
// カスタムキーフィールド
keyFields: ["userId"],
},
Query: {
fields: {
users: {
// ページネーションのマージ戦略
keyArgs: false,
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
FlatListとの統合でパフォーマンスを確保
React NativeのFlatListは大量データの表示に最適化されていますが、GraphQLクエリとの組み合わせでは注意点があります。
function UserListOptimized() {
const { data, loading, fetchMore } = useQuery(GET_USERS, {
variables: { offset: 0, limit: 20 },
});
const loadMore = () => {
fetchMore({
variables: {
offset: data?.users.length ?? 0,
},
});
};
return (
<FlatList
data={data?.users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <UserCard user={item} />}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
// 不要な再レンダリングを防止
getItemLayout={(_, index) => ({
length: 80,
offset: 80 * index,
index,
})}
/>
);
}
ネットワーク状態に応じたフェッチポリシー
モバイルアプリではネットワーク状態が不安定なことが多いため、フェッチポリシーの使い分けが重要です。
| フェッチポリシー | 動作 | 適した場面 |
|---|---|---|
cache-first | キャッシュ優先、なければネットワーク | 頻繁に変わらないデータ(プロフィールなど) |
network-only | 常にネットワークから取得 | リアルタイム性が求められるデータ |
cache-and-network | キャッシュを即返し、裏でネットワーク更新 | 一覧画面の初期表示 |
no-cache | キャッシュを使わない | ワンタイムのデータ取得 |
const { data } = useQuery(GET_TIMELINE, {
fetchPolicy: "cache-and-network",
});
New Architecture(Fabric / TurboModules)との互換性
React Native 0.76(2024年)でNew Architectureがデフォルトとなり、0.82(2025年10月リリース)では旧アーキテクチャが完全に廃止され、New Architecture(Fabric + TurboModules + JSI)が唯一のアーキテクチャとして必須化されました。Apollo Client、urqlともにJavaScriptレイヤーで動作するため、New Architectureとの互換性に問題はありません。
JSI(JavaScript Interface)によりJavaScriptとネイティブ層が同期的に通信できるようになりましたが、GraphQLクライアントはHTTPレイヤーで動作するため、この変更が直接的に影響を与えることはありません。ただし、ネイティブモジュール経由でのネットワーク通信が高速化される恩恵は間接的に受けられます。
よくあるトラブルと対処法
キャッシュの不整合
Apollo Clientのキャッシュは自動更新が基本ですが、Mutationの戻り値に含まれないフィールドが更新されないケースがあります。
対処法: Mutationのupdate関数でキャッシュを手動更新するか、refetchQueriesで関連クエリを再取得します。
const [deleteUser] = useMutation(DELETE_USER, {
update(cache, { data }) {
cache.modify({
fields: {
users(existingUsers = [], { readField }) {
return existingUsers.filter(
(ref: any) => readField("id", ref) !== data?.deleteUser.id
);
},
},
});
},
});
React NativeでのTextEncoder未定義エラー
一部のGraphQLクライアントがTextEncoderを使用しますが、React Native環境にはデフォルトで存在しません。
対処法: react-native-polyfill-globalsを導入してポリフィルを適用します(前述のテキストストリーミング対応セクションを参照)。
Expoでのmetro.config.js設定
Expoプロジェクトで.graphqlファイルを直接インポートする場合、metroの設定変更が必要です。
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push("graphql", "gql");
module.exports = config;
ただし、GraphQL Code Generatorを使う場合は.graphqlファイルのインポートは不要で、TypeScriptファイル内にgqlタグで直接記述する方式が推奨されます。
プロジェクト構成の実践例
中〜大規模のReact Native + GraphQLプロジェクトでは、以下のディレクトリ構成が保守性と拡張性のバランスに優れています。
src/
├── __generated__/ # GraphQL Code Generator出力
│ ├── gql.ts
│ ├── graphql.ts
│ └── fragment-masking.ts
├── graphql/
│ ├── queries/ # Query定義
│ │ ├── user.ts
│ │ └── post.ts
│ ├── mutations/ # Mutation定義
│ │ └── user.ts
│ ├── subscriptions/ # Subscription定義
│ │ └── message.ts
│ └── fragments/ # 共有Fragment
│ └── userFields.ts
├── lib/
│ └── apollo.ts # クライアント初期化
├── screens/
│ ├── UserListScreen.tsx
│ └── ChatScreen.tsx
└── components/
├── UserCard.tsx
└── MessageBubble.tsx
まとめ
React NativeとGraphQLの組み合わせは、モバイルアプリのデータ取得を効率化する強力な手段です。Apollo Client 4.0は正規化キャッシュやSubscriptionを含む包括的な機能を提供し、urqlはバンドルサイズを抑えつつ十分な拡張性を備えています。graphql-request + TanStack Queryの組み合わせは、シンプルさを重視するプロジェクトに適しています。
GraphQL Code Generatorによる型自動生成を加えれば、スキーマとフロントエンドの型が常に同期し、実行時エラーを大幅に削減できます。プロジェクトの規模・チームの経験・パフォーマンス要件を踏まえて、最適なクライアントを選定してください。
