🥝

フロントエンドにおけるGraphQLクエリの流れと基本構成

2024/09/02に公開

GraphQLの操作を理解するためには、「クエリ」「リゾルバ」「データソース」「スキーマ」の役割を把握することが重要である。それぞれの要素がどのように連携してデータ取得までの流れを構成しているかをまとめた。フォルダ構成はこのページの下をご参照ください。

GraphQLクエリの基本形

GraphQLクエリの基本形として、以下のようなクエリを使用して特定のユーザーの情報を取得する

query GetUser($userId: ID!) {
  user(id: $userId) {
    name
    email
  }
}

1. GraphQLクエリが送信される

まず、クライアントからGraphQLサーバーにクエリが送信される。

src/components/UserDisplay.tsx
import { gql, useQuery } from '@apollo/client';
//クエリの定義
const GET_USER = gql`
  query GetUser($userId: ID!) {
    user(id: $userId) {
      name
      email
    }
  }
`;

//クエリの実行
const { loading, error, data } = useQuery(GET_USER, {
  variables: { userId: "123" },
});

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

return (
  <div>
    <p>Name: {data.user.name}</p>
    <p>Email: {data.user.email}</p>
  </div>
);

2. サーバーがクエリを解析する

GraphQLサーバーは、クエリを受け取ると、スキーマを基に内容を解析し、適切なリゾルバを判断する。スキーマは、すべてのクエリ、ミューテーション、サブスクリプション、および関連するデータ型を定義し、通常1つのファイルにまとめられるが、複数ファイルに分割する場合もある。

src/graphql/schema.ts
import { ApolloServer } from 'apollo-server';
import { makeExecutableSchema } from 'graphql-tools';
import { resolvers } from './resolvers/userResolver';

// スキーマの定義
const typeDefs = `
  type User {
    name: String
    email: String
  }

  type Query {
    user(id: ID!): User
  }
`;

// スキーマとリゾルバを結びつけて、サーバーで使用できる形にする
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

// サーバーの初期化
const server = new ApolloServer({ schema });

// サーバーの起動
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

「1. GraphQLクエリが送信される」のGetUserクエリでは、userフィールドに対応するリゾルバが、このスキーマに基づいて次の段階(3. リゾルバが呼び出される)で呼び出される。

3. リゾルバが呼び出される

次に、解析されたクエリに対応するリゾルバが呼び出される。リゾルバは、指定されたクエリに基づいてデータを取得し、そのデータを返す。

src/graphql/resolvers/userResolver.ts
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUserById(id);
    },
  },
};

この例では、userIdを引数として受け取り、データソースのgetUserByIdメソッドを呼び出すことで、指定されたユーザーのデータを取得する。

4. データソースが呼び出される

リゾルバからデータソースが呼び出される。データソースは、実際にデータを取得するための手段を提供する。データソースは、API、データベース、その他の外部リソースと通信し、必要なデータを取得する。

src/graphql/datasources/UserAPI.ts
class UserAPI {
  async getUserById(id) {
    const response = await fetch(`https://api.example.com/users/${id}`);
    const data = await response.json();
    return {
      name: data.name,
      email: data.email,
    };
  }
}

この例では、idに基づいて外部APIからユーザー情報を取得し、それをリゾルバに渡す。

5. データがリゾルバを通じて返され、最終的にクライアントに返される

データソースから取得されたデータは、リゾルバを通じてクライアントに返される。この一連の流れが、GraphQLを使ったデータ取得の基本プロセスである。

src/components/UserDisplay.tsx
//クライアント側では、取得したデータを次のように使用する
return (
  <div>
    <p>Name: {data.user.name}</p>
    <p>Email: {data.user.email}</p>
  </div>
);

GraphQLのデータソース

データソースとは、一般的に、データが保管されている場所やそのデータにアクセスするための手段のことであり、データベース、API、ファイルシステムなどが該当する。GraphQLにおいては、外部のデータソースからデータを取得するためのロジックを含んだメソッドやクラスがデータソースと呼ばれることが多い。

GraphQLのフロントエンド操作の構成要素

GraphQLのフロントエンド操作は、「クエリ」「リゾルバ」「データソース」から成り立っている。リゾルバ内にデータソースが含まれることもある。

プロジェクトのフォルダ構成

このページの説明に基づくフォルダ構成は以下の通りである。これは、GraphQLを多く使わない場合の例であり、小規模なプロジェクトや特定の機能にGraphQLを利用する場合に適している。

src/
│
├── graphql/
│   ├── resolvers/
│   │   └── userResolver.ts
│   ├── datasources/
│   │   └── UserAPI.ts
│   └── schema.ts
│
├── components/
│   └── UserDisplay.tsx
│
└── index.tsx

また、「1. GraphQLクエリ」で説明したクエリ部分を切り離す構成も推奨される。

src/
│
├── graphql/
│   ├── queries/
│   │   └── GetUserQuery.ts
│   ├── resolvers/
│   │   └── userResolver.ts
│   ├── datasources/
│   │   └── UserAPI.ts
│   └── schema.ts
│
├── components/
│   └── UserDisplay.tsx
│
└── index.tsx

Discussion