📊

フロントエンドエンジニアGraphQL入門

に公開

はじめに

フロントエンドエンジニアである私が、GraphQLを使ったプロジェクトにアサインされたため、学習内容をまとめました。
この記事は、フロントエンドエンジニア向けにGraphQLの概要をざっと理解したい方向けです。
細かい技術的詳細は省略していますが、内容は正確性を意識していますので安心して読んでください。
フロントエンドエンジニアがGraphQLを学ぶ際の一助になれば幸いです。

この記事の対象者

・GraphQLの概要を知りたい方
・フロントエンドエンジニアで、GraphQLを使った開発に携わる方
・GraphQLとReactの連携の初歩を学びたい方

GraphQLとは

GraphQLは、APIおよびサーバーサイド向けのオープンソースクエリ言語です。
データ間の関係性を定義するための厳密に型指定されたスキーマを提供することで、APIの柔軟性と予測可能性を高めます。

BFFとは
バックエンドから取得したデータを、フロントエンドが扱いやすい形に整形して提供するアーキテクチャです。
GraphQLをBFFとして使うことで、以下の利点があります。
・不要なデータを除去できる
・データ構造をフロントエンドに最適化できる
・REST APIのようにエンドポイントを増やす必要が少ない

GraphQLを使うことで、拡張性・保守性の高いフロントエンド開発が可能になります。

REST APIとは

GraphQLを理解するための比較対象として、まずREST APIについて簡単に紹介します。
REST(Representational State Transfer)は、Webアプリやモバイルアプリがサーバと通信するための設計スタイルです。
簡単に言うと、「Web上のデータ(リソース)をHTTP経由で操作するためのルール」となります。

メリット
・シンプルで理解しやすい
・HTTP標準に沿っているため実装が容易
・幅広く普及しており、情報や参考資料が豊富

デメリット
・必要なデータだけを取得するのが難しい(オーバーフェッチが発生しやすい)
・複雑なアプリになると、エンドポイントが増えて管理が大変になる

使用例
ユーザー情報を取得する場合、以下のようにアクセスします。

GET /users

すると、下記のようなデータが得られます。

{
  "id": 1,
  "name": "Taro",
  "age": 25
}

GraphQLのメリット

REST APIと比較した場合、GraphQLには次のようなメリットがあります。

オーバーフェッチングしない

GraphQLでは、上記のように必要なデータだけを指定して取得できます。
REST APIのように不要なデータまで取得してしまう「オーバーフェッチング」が発生せず、効率的です。

1つのエンドポイントで済む
GraphQLでは、基本的にエンドポイントは1つで済みます。
REST APIのようにリソースごとに複数のURLを用意して管理する必要がなく、アプリ開発や保守がシンプルになります。

型安全
GraphQLではスキーマで型を定義する必要があります。
そのため、TypeScriptと組み合わせることでフロントエンドでも型安全にデータを扱うことができ、バグを減らせます。

GraphQLのデメリット

サーバー構築が複雑
REST APIと比べると、サーバー側の初期構築や設定が少し手間です。
スキーマ定義やリゾルバの実装など、学習コストがやや高めです。

キャッシュやパフォーマンスの工夫が必要
RESTではURLごとに簡単にキャッシュできますが、GraphQLは 1つのエンドポイントで全てのクエリを受ける ため、キャッシュ戦略を考えないとパフォーマンスが落ちる場合があります。

複雑なクエリがサーバーに負荷をかける
クライアント側で複雑なクエリを投げると、サーバーが大きな処理をしなければならず、負荷が増えることがあります。
そのため、クエリの深さ制限やレート制限などの対策が必要です。

実践

最後に、GraphQLのバックエンド(Apollo Server) とフロントエンド(React + Apollo Client) をそれぞれ構築し、実際にデータを取得して画面に表示するまでを通して学びます。
構成は以下のように、backendfrontendを分けて実装します。

project-root/
├── backend/
│   ├── src/
│   │   └── server.ts
│   └── tsconfig.json
├── frontend/
│   ├── src/
│   │   ├── App.tsx
│   │   └── main.tsx
│   └── index.html

手順概要

・backendにGraphQL サーバー(Apollo Server)を構築
・frontendにReact + Apollo Client を導入
・GraphQLで取得したデータをブラウザに表示

backendの構築(GraphQLサーバー)

まず、ルートディレクトリでbackendフォルダを作成して移動します。

mkdir backend
cd backend

手順1:初期設定

以下のコマンドで初期化します。

npm init --yes
npm pkg set type="module"

次に、Apollo ServerとGraphQLをインストールします。

npm install @apollo/server graphql

TypeScript関連も追加します。

npm install --save-dev typescript @types/node

手順2:tsconfig.jsonの設定

次にbackendディレクトリ配下にtsconfig.jsonを作成し、中身を以下のようにしておきます。

backend/tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["node"]
  },
  "include": ["src"]
}

手順3:package.jsonのスクリプト

package.jsonは以下のようにします。

backend/package.json
{
  "type": "module",
  "scripts": {
    "compile": "tsc",
    "start": "npm run compile && node ./dist/index.js"
  },
  "devDependencies": {
    "@types/node": "^24.10.0",
    "typescript": "^5.9.3"
  },
  "name": "graphql-zenn",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "@apollo/server": "^5.1.0",
    "graphql": "^16.12.0",
    "undici-types": "^7.16.0"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

手順4:index.tsの作成

src/index.ts に以下を記述します

backend/src/index.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

const typeDefs = `#graphql
 type Book {
   title: String
   author: String
 }

 type Query {
   books: [Book]
 }
`;

const books = [
 {
   title: "The Awakening",
   author: "Kate Chopin",
 },
 {
   title: "City of Glass",
   author: "Paul Auster",
 },
];

const resolvers = {
 Query: {
   books: () => books,
 },
};

const server = new ApolloServer({
 typeDefs,
 resolvers,
});

const { url } = await startStandaloneServer(server, {
 listen: { port: 4000 },
});
console.log(`🚀  Server ready at: ${url}`);

手順4解説

①スキーマ(typeDefs)
上記の中で、次の部分が GraphQLのスキーマ定義 です。

  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

このコードは、SDLという構文で書かれており、各フィールドとそれぞれに対応する型が定義されています。名前は基本何でもいいのですが、QueryMutationは特別な名前で、それぞれ取得と、追加・更新・削除に相当します。
今回は、bookの情報を取得したいので、Queryにしています。
また、[Book]は、Bookというオブジェクトをリスト形式で入れるという意味です。
今回の場合、最初にBookという名前でオブジェクトを定義し、次にQueryのbooksフィールドにBookオブジェクトをリスト形式で入れて取得するという意味になります。

②リゾルバ(resolvers)
スキーマで「どんなデータを返すか」を定義しただけでは、まだ実際にデータを返す「中身」は決まっていません。
それを担当するのが リゾルバ(resolver)です。

const resolvers = {
  Query: {
    books: () => books,
  },
};

この部分はSDLではなくTypeScriptで書かれており、Query型で定義したbooksフィールドに対して、
実際にbooksという配列を返すようにしています。
books:部分は、スキーマで定義した名前と同じにしなければなりません。

つまり、「クライアントがbooksを問い合わせたら、この関数が実行されて配列が返る」という対応関係になります。

ここまでで、GraphQLサーバーの基本構成である
「スキーマ(typeDefs)+リゾルバ(resolvers)」が完成しました。
スキーマでデータの形を決め、リゾルバで実際にデータを返す、
これがGraphQLサーバーの最も基本的な考え方です

手順5:実行

npm start

localhost:4000にアクセスして、以下のような画面が表示されれば成功です!

次のページに進むと、以下のようにSDLで書かれたコードが表示されます。

コードを以下のようにして、Example Queryを押下します。
すると、うまく情報を取得できることがわかります。
これは、index.tsでスキーマを定義した際に、booksというtypeDefsresolverを定義したからです。

frontendファイルでReact導入とデータ取得

次に、フロント側の実装をしていきます。
バックエンドで用意したGraphQLサーバーからデータを取得するために、Apollo Clientを使います。

手順1:プロジェクト作成

まず、ルートディレクトリに戻って以下のコマンドを実行します。

npm create vite@latest

以下のように選択します。

Project name:frontend
Select a framework:React
Select a variant:TypeScript
Use rolldown-vite (Experimental)?:No
Install with npm and start now?Yes

作成が完了したら、ディレクトリを移動します。

cd frontend

手順2:Apollo Client のインストール

フロントエンドからGraphQLサーバーにリクエストを送るため、Apollo Clientを導入します。

npm install @apollo/client graphql rxjs

手順3:main.tsx の設定

次に src/main.tsx を以下のように編集します。

frontend\src\main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { ApolloProvider } from "@apollo/client/react";

const client = new ApolloClient({
  link: new HttpLink({ uri: "http://localhost:4000/" }),
  cache: new InMemoryCache(),
});

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

こちらは公式ドキュメント通りなのですが、軽く説明すると以下のようになります。

・ApolloClient:GraphQL通信の中核。
linkでAPIのURLを指定します(バックエンドの http://localhost:4000)。
cacheでクエリ結果をキャッシュする仕組みを設定します。
・ApolloProvider:React全体でApollo Clientを使えるようにするコンポーネント。
→Contextを通して、どのコンポーネントでもGraphQLを実行可能にします。

つまり、この段階で**「Apollo ClientをReactアプリ全体に接続」**できました。

手順4:App.tsx の実装

続いて、GraphQLクエリを実際に実行する部分をApp.tsxに書きます。

frontend\src\App.tsx
// Import everything needed to use the `useQuery` hook
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

export default function App() {
  interface Books {
    title: string;
    author: string;
  }

  interface GETBOOKSData {
    books: Books[];
  }

  const GET_BOOKS = gql`
    query {
      books {
        title
        author
      }
    }
  `;
  function DisplayBooks() {
    const { loading, error, data } = useQuery<GETBOOKSData>(GET_BOOKS);
    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error : {error.message}</p>;
    return data?.books.map(({ title, author }) => (
      <div key={title}>
        <p>{title}</p>
        <p>{author}</p>
      </div>
    ));
  }
  return (
    <div>
      <h2>My first Apollo app 🚀</h2>
      <br />
      <DisplayBooks />
    </div>
  );
}

こちらも公式ドキュメント通りです。

手順4解説

①クエリ定義 (gql)

const GET_BOOKS = gql`
  query {
    books {
      title
      author
    }
  }
`;

バックエンドで定義したbooksフィールドを呼び出すクエリを記述します。
gql はApollo Clientが提供するGraphQL構文専用タグで、文字列を解析してクエリを実行できる形に変換します。

②useQueryフックでデータ取得

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

Reactコンポーネント内でGraphQLクエリを実行するには useQuery フックを使用します。
・loading: 取得中は true
・error: 通信エラー時の情報
・data: 取得結果(books配列)

実行結果

アプリを起動します。

npm run dev

ブラウザで http://localhost:5173にアクセスすると、以下のようにデータが表示されます。

最後に

今回は、GraphQL + React + TypeScriptで簡単なWebアプリを作る手順を紹介しました。
より本格的なアプリを作る場合は、QueryだけでなくMutationを使うことでCRUD操作を実現できます。
新しいデータや複雑なデータ構造を扱う場合も、スキーマを追加するだけで対応でき、RESTのようにエンドポイントを増やす必要はありません。
さらに、Nest.jsやPrismaを組み合わせることで、より実践的なバックエンド開発が可能になります。

参考
クライアント参考
サーバー参考
【完全版】これ1本でGraphQLをマスターできるチュートリアル【React/TypeScript/Prisma】
誰でも理解できるGraphQL入門!REST APIとの違いを分かりやすく解説【ハンズオン形式】

Discussion