T-CREATOR

Vite と GraphQL の連携方法

Vite と GraphQL の連携方法

モダンなフロントエンド開発において、高速なビルドツール「Vite」と効率的なデータ取得を実現する「GraphQL」の組み合わせが注目を集めています。

この記事では、ViteプロジェクトでGraphQLを効率的に導入する方法から、実際のコード実装まで、段階的に解説していきます。初心者の方にもわかりやすく、実用的な内容をお届けいたしますので、ぜひ最後までお読みください。

背景

Viteが選ばれる理由

Viteは、従来のWebpackと比較して圧倒的な高速性を誇るビルドツールです。開発サーバーの起動が秒単位で完了し、ホットリロードも瞬時に反映されます。

ES modules を活用した仕組みにより、変更した部分のみを再ビルドするため、大規模なプロジェクトでも快適な開発体験を実現できますね。

GraphQLの優位性

GraphQLは、RESTful APIの課題を解決する革新的なクエリ言語です。必要なデータだけを指定して取得できるため、オーバーフェッチングやアンダーフェッチングの問題を解決します。

型安全性が確保されており、フロントエンドとバックエンドの連携がスムーズになるでしょう。

両技術の組み合わせがもたらす価値

ViteとGraphQLを組み合わせることで、以下のメリットが得られます:

#メリット詳細
1開発速度の向上Viteの高速ビルド + GraphQLの効率的なデータ取得
2型安全性の確保TypeScriptとの親和性が高い両技術の組み合わせ
3開発体験の向上ホットリロードとリアルタイムなデータ取得
4パフォーマンス最適化必要最小限のデータ転送とバンドルサイズ削減

課題

従来のWebpack環境での制約

従来のWebpack環境では、GraphQLクライアントの設定に多くの設定ファイルが必要でした。バンドル時間も長く、開発中のフィードバックループが遅くなりがちです。

特に、以下の課題が頻繁に発生していました。

設定の複雑さ

javascript// webpack.config.js での複雑な設定例
module.exports = {
  // ... 多数の設定項目
  module: {
    rules: [
      {
        test: /\.(graphql|gql)$/,
        exclude: /node_modules/,
        loader: 'graphql-tag/loader'
      }
    ]
  }
};

このような設定ファイルの管理が煩雑で、プロジェクトの立ち上げに時間がかかっていました。

ビルド時間の長さ

大規模なプロジェクトでは、ビルド時間が数分に及ぶことも珍しくありません。開発中の変更確認に時間がかかり、開発効率が著しく低下していました。

型安全性の不足

従来の環境では、GraphQLスキーマとTypeScriptの型定義の同期が困難で、ランタイムエラーが発生しやすい状況でした。

解決策

GraphQLクライアントライブラリの選択

Vite環境でのGraphQL統合には、適切なクライアントライブラリの選択が重要です。主要な選択肢を比較してみましょう。

#ライブラリ名特徴適用場面
1Apollo Client高機能、豊富なエコシステム大規模アプリケーション
2urql軽量、簡単な設定中小規模プロジェクト
3React Query + graphql-request軽量、柔軟性が高いカスタマイズ重視
4SWR + graphql-requestシンプル、キャッシュ機能軽量アプリケーション

今回は、最も機能が豊富で実績のあるApollo Clientを中心に解説いたします。

Vite環境でのGraphQLクライアント設定

ViteではES modulesを活用するため、従来のWebpackとは異なる設定方法が必要です。

基本的なVite設定

javascript// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  define: {
    __DEV__: JSON.stringify(true),
  },
});

この設定により、Vite環境でのReact + GraphQLの基盤が整います。

環境変数の設定

typescript// .env.local
VITE_GRAPHQL_ENDPOINT=http://localhost:4000/graphql
VITE_API_KEY=your_api_key_here

ViteではVITE_プレフィックスを付けることで、環境変数をフロントエンドで利用できます。

TypeScript連携の設定方法

型安全性を確保するため、TypeScriptとの連携設定を行います。

TypeScript設定ファイル

json// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src", "**/*.ts", "**/*.tsx"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

この設定により、ViteとTypeScriptの連携が最適化されます。

GraphQL型定義の自動生成設定

yaml# codegen.yml
overwrite: true
schema: "http://localhost:4000/graphql"
documents: "src/**/*.graphql"
generates:
  src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withHooks: true

この設定ファイルにより、GraphQLスキーマから自動的にTypeScript型定義が生成されます。

具体例

Apollo Client との連携実装

実際にViteプロジェクトでApollo Clientを設定する手順を見ていきましょう。

必要なパッケージのインストール

bashyarn add @apollo/client graphql
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

Apollo Clientと関連するGraphQL Code Generatorのパッケージをインストールします。

Apollo Client の基本設定

typescript// src/lib/apolloClient.ts
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: import.meta.env.VITE_GRAPHQL_ENDPOINT,
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      errorPolicy: 'ignore',
    },
    query: {
      errorPolicy: 'all',
    },
  },
});

export default client;

この設定では、認証トークンの自動付与とエラーハンドリングの設定も含めています。

React アプリケーションとの統合

typescript// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import App from './App.tsx';
import client from './lib/apolloClient.ts';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
);

ApolloProviderでアプリケーション全体をラップすることで、全てのコンポーネントでGraphQLが利用できるようになります。

コード生成ツール(GraphQL Code Generator)の活用

GraphQL Code Generatorを使用して、型安全なコードを自動生成します。

GraphQLクエリファイルの作成

graphql# src/queries/user.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    avatar
    createdAt
  }
}

mutation UpdateUser($id: ID!, $input: UserUpdateInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    email
    avatar
  }
}

このGraphQLクエリから、TypeScript型定義とReact Hooksが自動生成されます。

型生成コマンドの実行

json// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "codegen": "graphql-codegen --config codegen.yml",
    "codegen:watch": "graphql-codegen --config codegen.yml --watch"
  }
}

開発中はyarn codegen:watchで自動的に型定義を更新できます。

生成されたコードの活用

typescript// 生成された型定義とフックの使用例
import { useGetUserQuery, useUpdateUserMutation } from '../generated/graphql';

export const UserProfile: React.FC<{ userId: string }> = ({ userId }) => {
  const { data, loading, error } = useGetUserQuery({
    variables: { id: userId }
  });

  const [updateUser, { loading: updating }] = useUpdateUserMutation();

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error.message}</div>;
  if (!data?.user) return <div>ユーザーが見つかりません</div>;

  return (
    <div>
      <h2>{data.user.name}</h2>
      <p>{data.user.email}</p>
      {/* 更新ボタンなどのUI */}
    </div>
  );
};

型安全性が確保された状態で、GraphQLクエリの結果を活用できます。

GraphQLクエリの実装例

実際のアプリケーションで使用する、より実践的なクエリ実装を見ていきましょう。

複雑なクエリの実装

graphql# src/queries/posts.graphql
query GetPosts($first: Int!, $after: String, $filter: PostFilter) {
  posts(first: $first, after: $after, filter: $filter) {
    edges {
      node {
        id
        title
        content
        publishedAt
        author {
          id
          name
          avatar
        }
        tags {
          id
          name
        }
        _count {
          comments
          likes
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

このクエリでは、ページネーションとフィルタリング機能を含む複雑なデータ取得を実現しています。

カスタムフックの作成

typescript// src/hooks/usePosts.ts
import { useState } from 'react';
import { useGetPostsQuery } from '../generated/graphql';

export const usePosts = (filter?: PostFilter) => {
  const [hasMore, setHasMore] = useState(true);
  
  const { data, loading, error, fetchMore } = useGetPostsQuery({
    variables: {
      first: 10,
      filter
    },
    notifyOnNetworkStatusChange: true
  });

  const loadMore = async () => {
    if (!data?.posts.pageInfo.hasNextPage) {
      setHasMore(false);
      return;
    }

    try {
      await fetchMore({
        variables: {
          after: data.posts.pageInfo.endCursor
        },
        updateQuery: (prev, { fetchMoreResult }) => {
          if (!fetchMoreResult) return prev;

          return {
            posts: {
              ...fetchMoreResult.posts,
              edges: [
                ...prev.posts.edges,
                ...fetchMoreResult.posts.edges
              ]
            }
          };
        }
      });
    } catch (err) {
      console.error('読み込みエラー:', err);
    }
  };

  return {
    posts: data?.posts.edges.map(edge => edge.node) || [],
    loading,
    error,
    hasMore,
    loadMore
  };
};

このカスタムフックにより、ページネーション機能を含む投稿一覧の取得が簡単に実装できますね。

エラーハンドリングの実装

typescript// src/components/ErrorBoundary.tsx
import React from 'react';
import { ApolloError } from '@apollo/client';

interface ErrorDisplayProps {
  error: ApolloError;
}

export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error }) => {
  // ネットワークエラーの処理
  if (error.networkError) {
    return (
      <div className="error-container">
        <h3>接続エラーが発生しました</h3>
        <p>サーバーに接続できません。しばらく待ってから再試行してください。</p>
        <code>{error.networkError.message}</code>
      </div>
    );
  }

  // GraphQLエラーの処理
  if (error.graphQLErrors.length > 0) {
    return (
      <div className="error-container">
        <h3>データ取得エラー</h3>
        {error.graphQLErrors.map((err, index) => (
          <div key={index}>
            <p>{err.message}</p>
            {err.locations && (
              <code>
                Line: {err.locations[0].line}, Column: {err.locations[0].column}
              </code>
            )}
          </div>
        ))}
      </div>
    );
  }

  return (
    <div className="error-container">
      <h3>予期しないエラーが発生しました</h3>
      <p>しばらく待ってから再試行してください。</p>
    </div>
  );
};

適切なエラーハンドリングにより、ユーザーフレンドリーなアプリケーションを構築できます。

キャッシュ戦略の実装

typescript// src/lib/cache.ts
import { InMemoryCache, makeVar } from '@apollo/client';

// リアクティブ変数の定義
export const isLoggedInVar = makeVar<boolean>(
  !!localStorage.getItem('token')
);

export const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        isLoggedIn: {
          read() {
            return isLoggedInVar();
          }
        }
      }
    },
    Post: {
      fields: {
        comments: {
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          }
        }
      }
    }
  }
});

効果的なキャッシュ戦略により、アプリケーションのパフォーマンスが大幅に向上します。

まとめ

ViteとGraphQLの連携は、モダンなフロントエンド開発において強力な組み合わせです。

本記事でご紹介した実装方法により、以下の効果が期待できます:

#効果詳細
1開発速度向上Viteの高速ビルド + 型安全なコード生成
2保守性向上GraphQL Code Generatorによる自動型生成
3パフォーマンス最適化Apollo Clientのキャッシュ機能活用
4開発体験向上ホットリロードと型安全性の組み合わせ

特に重要なポイントは、GraphQL Code Generatorを活用した型安全なコード生成です。これにより、ランタイムエラーを大幅に減らし、開発効率を向上させることができますね。

また、Apollo Clientの豊富な機能を活用することで、キャッシュ戦略やエラーハンドリングなど、プロダクションレベルのアプリケーション開発に必要な要素をしっかりと実装できます。

今回ご紹介した実装パターンをベースに、プロジェクトの要件に合わせてカスタマイズしていけば、高品質なWebアプリケーションを効率的に開発できるでしょう。

関連リンク