近年のWebアプリケーションでは、画面構成やUXの多様化にともない「より柔軟で効率的なデータ取得」の重要性が高まっています。

特にフロントエンドでは、不要なデータ取得複数回のAPIリクエストが開発体験やパフォーマンスに影響を与えるケースも増えてきました。

よく使われているREST APIでは、エンドポイントごとに決まったレスポンスが返ってくるため、 フロント側の要件に合わせて「不要なデータを受け取る」「複数のエンドポイントを呼び分ける」 といった非効率が発生しがちです。

これらの課題を解決する手段として注目されているのがGraphQLです。

GraphQLを使えば、クライアントが必要なデータだけを明確に指定して取得できるため、 通信の無駄が減り開発効率やパフォーマンスの向上につながります。

本記事では、Symfony と API Platformを使って、GraphQL APIを簡単に作成する方法を紹介します。

✅️ 今回作成するもの

今回は「書籍(Book)」と「レビュー(Review)」のデータを扱うGraphQL APIを作成します。

  • 書籍一覧の取得
  • 各書籍に紐づくレビューの平均スコアの取得
  • React + Apollo Clientを使ったフロントエンドでのデータ取得

フロント以外のコードはGitHubに公開していますので、ぜひ参考にしてください。

🔗GitHub:symfony-api-platform-graphql

📝 目次

1. Symfonyプロジェクトのセットアップ

使用バージョン:

  • PHP:v8.2.28
  • Symfony:v7.3.1

まずは新規プロジェクトを作成します。

$ symfony new symfony-api-platform-graphql

2. API Platformの導入

API Platformは、PHP製のWeb APIフレームワークです。 導入するだけで自動的にRESTとGraphQLのAPIエンドポイントが作成されます。

$ composer require api

これだけで/apiにアクセスすると、Swagger UIでREST APIのドキュメントが確認できます。

💡 API Platform では、REST API と GraphQL API の両方を併用できます。 エンティティを定義するだけで、自動的に REST のエンドポイントが生成され、Swagger UI から確認や操作も可能です。 本記事では GraphQL をメインに扱いますが、動作確認も兼ねてデータ登録には REST API を使って進めていきます。

APIドキュメント-データ0

3. データベース接続(PostgreSQL)

.envファイルを編集し、PostgreSQLに接続できるように設定します。

- DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
+ DATABASE_URL="postgresql://app:!@127.0.0.1:5432/symfony-api-platform-graphql?serverVersion=16&charset=utf8"
  • ユーザー名やパスワードは自分の環境に合わせて変更してください。
  • 事前にPostgreSQLをインストールしておく必要があります。
  • PostgreSQL の代わりに SQLite や MySQL を使っても大丈夫です。

その後、以下のコマンドでデータベースを作成します。

$ php bin/console doctrine:database:create

4. Book / Reviewエンティティの作成とマイグレーション

まずは開発用にMakerBundleを追加します。

$ composer require symfony/maker-bundle --dev

次に、書籍(Book)レビュー(Review)のエンティティを作成し、マイグレーションを実行します。

$ php bin/console make:entity
$ php bin/console make:migration 
$ php bin/console doctrine:migrations:migrate

👇️️ エンティティ定義のコード

反映後、/apiにアクセスすると、BookReviewのエンドポイントが自動生成されていることが確認できます。

REST APIドキュメント-エンティティ反映

REST APIドキュメント-スキーマ

次に、実際にBookとReviewのデータをAPI経由で登録してみます。

5. データ登録 (REST API)

生成された/apiエンドポイントに対して、Postmanなどを使ってデータを登録します。

Bookデータを登録するために、以下のように/api/booksPOSTリクエストを送信します。

{
    "isbn": "978-4-7741-8411-1",
    "title": "ドメイン駆動設計入門",
    "description": "DDDの基礎を学べる書籍です。",
    "author": "山田太郎",
    "publicationDate": "2024-05-01T00:00:00+09:00"
}

Reviewデータの登録も同様に/api/reviewsPOSTリクエストを行います。

{
    "rating": 5,
    "body": "とても分かりやすい内容でDDDが理解できました!",
    "author": "読者A",
    "publicationDate": "2024-05-10T00:00:00+09:00",
    "book": "/api/books/1"
}

Postmanでのデータ登録

PostgreSQLでBookデータ確認

データベースにデータが入っていることが確認できます。

サンプルデータとしてBookReviewデータをいくつか登録しておきます。

サンプルデータはREADME.mdに記載しておきます。

PostgreSQLでBookとReviewのデータ確認

REST APIが問題なく動作することを確認できたので、 ここからは本題であるGraphQLの利用方法を見ていきます。

6. GraphQLの有効化

GraphQLを有効化します。

$ composer require api-platform/graphql

これで/graphqlにアクセスすれば、GraphiQL(GraphQL IDE)が利用可能になります。

GraphQL IDE

7. GraphQLで書籍一覧を取得

API Platformでは、GraphQLエンドポイント(/graphql)が自動で用意されており、ブラウザからGraphiQLを使ってクエリを試すことができます。

まずは、シンプルに書籍の一覧を取得してみます。

query {
  books {
    edges {
      node {
        id
        title
        author
        isbn
        description
        publicationDate
      }
    }
  }
}

このように、GraphQLでは必要なフィールドだけを指定して取得できるのが大きなメリットです。

edges > nodeという構造は、Relay仕様に沿ったページネーション形式です。

👇️️ 結果画面はこんな感じ

GraphQLのクエリ実行

8. カスタムフィールド(レビューの平均スコア)を追加

クライアントでレビューを集計するのは手間なので、バックエンドで平均スコアを計算して返すようにします。

📌 Bookエンティティに集計ロジックを追加

#[Groups(['book:read'])]
public function getAverageRating(): ?float
{
    if (!is_iterable($this->reviews) || count($this->reviews) === 0) {
        return null;
    }

    $sum = 0;
    $count = 0;
    foreach ($this->reviews as $review) {
        $sum += $review->rating;
        $count++;
    }

    return $count > 0 ? round($sum / $count, 2) : null;
}

レビューが存在する場合だけ平均を計算し、小数第2位で丸めて返すというシンプルな実装です。

📌 GraphQLでフィールドを有効化する

このaverageRatingをGraphQLのレスポンスに含めるためには、ApiResourceにグループ指定を追加します。

#[ApiResource(normalizationContext: ['groups' => ['book:read']])]

これでGraphQL側でも以下のようなクエリで取得可能になります!

query {
  books {
    edges {
      node {
        title
        averageRating
      }
    }
  }
}

👇️️ 結果画面はこんな感じ

平均スコア追加後のGraphQLのクエリ実行

バックエンドで集計処理を済ませておけば、フロント側はaverageRatingをクエリに追加するだけで、すぐに表示できます。

REST APIでも集約済みのデータを1リクエストで取得することは可能ですが、 事前に専用のエンドポイントを用意する必要があり画面ごとのデータ要件に応じて設計が増えていきがちです。

その点GraphQLでは、クライアントが欲しい形で柔軟にクエリを定義できるため、バックエンド側は汎用的なスキーマを用意するだけで済みます。

その結果、クライアントは欲しいデータを、欲しい形で取得できるようになり、 バックエンド側も汎用的なスキーマ設計だけで済むため、API設計の手間を大きく減らすことができます。

GraphQL のこうした柔軟さは、複雑な画面構成や多様なデータ要件を持つアプリ開発において、特に効果を発揮します。

9. フロント側からデータを取得する

GraphQLの大きな利点のひとつが、「フロントエンドから必要なデータだけを効率的に取得できる」点です。

ここではReact + Apollo Clientを使って、書籍のタイトルと平均スコアを表示するシンプルなUIを作ってみます。

GraphQLクエリの定義

const GET_BOOKS = gql`
  query {
    books {
      edges {
        node {
          id
          title
          averageRating
        }
      }
    }
  }
`;

コンポーネント側の実装

import { gql, useQuery } from '@apollo/client';

const BookList = () => {
  const { loading, error, data } = useQuery(GET_BOOKS);

  if (loading) return <p>読み込み中...</p>;
  if (error) return <p>エラー: {error.message}</p>;

  return (
    <ul>
      {data.books.edges.map(({ node }: any) => (
        <li key={node.id}>
          {node.title} - 平均スコア: {node.averageRating ?? '評価なし'}
        </li>
      ))}
    </ul>
  );
};

export default BookList;

このように、GraphQLクエリを使ってフロント側で必要なフィールドだけを柔軟に取得できます。

バックエンド側でGroups属性によってフィールド公開範囲を調整できるのも、API Platform の便利な点です。

10. まとめ

今回の構成(Symfony + API Platform + GraphQL)を振り返ってみると、こんな強みがありました。

  • 自動生成:エンティティ定義だけでREST/GraphQL APIを即構築可能
  • 柔軟性:Groups属性で公開フィールドを制御したり、フロントエンドから必要なデータを自由に取得できる
  • 効率性:GraphQL により、必要な情報だけを最小限で取得できるため、通信や処理の無駄がない

GraphQLが初めての方でも、API Platformを使えば簡単に構築できます。

特に、データの粒度を調整したい場面複雑な画面構成を持つSPA開発においては、GraphQLのメリットを最大限に活かせます。

今回は実装中心の紹介でしたが、GraphQLはUIの多様化に柔軟に対応できる強力な選択肢です。

ぜひ一度、試してみてください!

📚️ 参考リンク