GraphQLをクライアント側で使用した経験またはGraphQLという単語を耳にしたことはあるが実際にGraphQLのサーバ側ではどのような設定が行われているか知らない人も多いかと思います。

本文書ではGraphQLサーバ側ではどのような設定が必要なのかまたクライアントはどのようなコードでGraphQLサーバにアクセスしなければならないかをシンプルなデータを利用して説明を行なっています。シンプルなデータを利用することでGraphQLサーバの基本の基本を完全に理解することができます。また後半ではJavaScript, Vue.js上からfetch関数でGraphQLサーバからのデータ取得の方法も確認を行いVue 3にApollo Clientをインストールしてデータの取得を行なっています。一通り読むことでGraphQLのサーバ設定、クライアントからのデータ取得までの一連の流れを理解することができます。

動作確認にはGraphQLサーバが構築できるオープンソースのApollo Serverを利用しています。Apollo ServerはJavaScriptのライブラリなのでJavaScriptの知識が必要となります。また環境を構築する際はNode.jsをインストールしておく必要があります。

GraphQLとは

GraphQLにQL(Query Language)とという名前がついているようにデータベースから取得する際に利用するSQLのように取得したいデータをクライアント側でクエリーで設定して送信してデータの取得を行います。これまでのみなさんが使い慣れたREST APIのようにあるデータが欲しい場合に決められたエンドポイントに対してアクセスを行うといった形(ユーザ一覧の情報が欲しい場合は/usersにアクセス)ではありません。またエンドポイントに対してGET, POST, PUT, DELETEといったメソッドによって実行したい処理を指定することはありません。送信するエンドポイントは一つだけ存在し、その一つのエンドポイントに対して送信するクエリーを変更することで処理する内容や取得したいデータを変更します。クエリーを使ってデータを取得する場合は欲しい項目(ユーザデータなら名前)のみ指定することができるので利用予定のないデータを取得する必要がなくなります。スキーマを使ってデータの定義を行うため扱うデータが明確化されます。

必要なデータを取得するために複数のエンドポイントをアクセスすることをUnderFetchingといいます。必要なデータ以上のデータを取得することをOverfetchingといいます。どちらの言葉も本文書では出てきませんがGraphQLを説明する際に利用される単語です。
fukidashi

GraphQLのポイントとして以下のような点が挙げられます。

  • エンドポイントは1つ
  • クライアントサイドでクエリーを設定
  • 欲しいデータを指定
  • スキーマによりデータ定義

Apolloサーバ用プロジェクトの作成

Apollo Server用のプロジェクトの作成を行います。任意の名前のディレクトリを作成し、ディレクトリの作成後、作成したディレクトリに移動してください。


 % mkdir graphql-server-example
 % cd graphql-server-example

package.jsonファイルを作成するためにnpm init -yコマンドを実行します。実行するとディレクトリ内にpackage.jsonファイルが作成されます。


 % npm init -y
 % ls
package.json

Apollo Serverには2つのライブラリが必要となります。

  • apollo-server
  • graphql

npmコマンドを使ってインストールを行います。


% npm install apollo-server graphql

インストールが完了したら、index.jsファイルを作成してください。index.jsファイルにApollo Serverに必要なコードを記述していきます。


% touch index.js
touchコマンドを使うと空のファイルを作成することができます。
fukidashi

GraphQLの設定

index.jsファイルにGraphQLサーバの設定を記述していきます。index.jsファイルを開いて、apollo-serverからapollo serverに必要なモジュールをrequireします。


const { ApolloServer, gql } = require('apollo-server');
ApploServerはサーバ、gqlはスキーマを定義する際に利用します。
fukidashi

スキーマの定義

GraphQLはGraphQLのスキーマを記述するためだけに利用させる独自のスキーマ定義言語(Schema Definition Language)を持っています。

通常はスキーマを定義して定義に合わせてデータを作成することになりますがスキーマを元に作成されたデータがどのように形か確認するためにサンプルデータをindex.jsファイルに記述します。booksはtitle, authorを持つデータによって構成されています。


const { ApolloServer, gql } = require('apollo-server');

const books = [
    {
        title: 'Harry Potter and the Chamber of Secrets',
        author: 'J.K. Rowling',
    },
    {
        title: 'Jurassic Park',
        author: 'Michael Crichton',
    },
];

データはtitle, authorで構成されていることは確認済みなのでSDLを利用して定義します。typeを使って名前をつけ以下のように記述を行うことができます。typeの名前は慣例でキャメルケースを利用します。type Bookはtitleとauthorのフィールドで構成されておりそれぞれには文字列が入っているのでデータの型はStringとなります。Stringの後の!マークはそのフィールドが必須であることを表します。


type Book {
  title: String!
  author: String
}

スキーマについてはindex.jsにそのまま記述するのではなくgqlの中に記述します。typeDefsはType Definitionsの略です。


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

GraphQLではクエリーを利用してデータを取得することができます。データ取得する際に利用するQueryタイプのフィールドを設定します。QueryタイプはBookタイプとは異なりクライアントがGraphQLを利用してアクセスする際に利用します。queryでbooksを指定して実行するとBookタイプの配列がクライアントに戻されることを定義しています。何もデータがない時は空の配列が戻されます。Queryタイプは必ず設定を行う必要があり、Queryタイプの中にのフィールドにクライアントが利用するqueryを追加していくことになります。


const typeDefs = gql`
  type Book {
    title: String
    author: String
  }
  type Query {
    books: [Book]
  }
`;

Resolverの設定

サンプルデータとスキーマの設定は行いましたが、アクセスがあった場合はどのようなデータを戻すかが設定されていません。その設定を行うのがResolverです。

下記のコードではresolversのQueryの中ではbooksと指定していますが、これがスキーマのQueryタイプの中で定義したbooksです。クライアントからのqueryでbooksが指定された場合はbooksプロパティの値である関数が実行されます。関数を使うことでどのようなデータをクライアントに戻すのか制御することができます。


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

Apollo Serverの設定

サンプルデータ、スキーマ、Resolverの設定が完了したので、Apolloサーバの設定を行います。


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

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});
起動するポートを変更したい場合はlistenに引数にportプロパティ、値にポート番号を設定したオブジェクトを入れることで変更することができます。
fukidashi

設定が完了したのでサーバの起動を行います。ポート4000で起動することがわかります。


 % node index.js
🚀  Server ready at http://localhost:4000/

Apollo Studioの利用

ブラウザで http://localhost:4000/にアクセスすると下記の画面が表示されます。”Query your server”をクリックします。

Apollo Serverの初期画面
Apollo Serverの初期画面

クリックするとApollo Studioツールでこの画面からGraphQLを実行しAppoloサーバからデータを取得することができます。

v2の時はApollo Palygroundというツールでしたがv3ではApollo Studioというツールに変わっています。
fukidashi
Apollo Studioの初期画面
Apollo Studioの初期画面

Queryの実行

画面中央のOperationにデフォルトでExampleQueryが記述されているので”ExampleQuery”ぼたんをクリックすることでQueryを実行することができます。ボタンをクリックすると右側にはResponseが表示されデータのtitleのみ表示されます。表示されているtitleはindex.jsに追加したbooksの配列のデータです。ExampleQueryではtitleのみを設定しているためtitleのみ表示されています。

ExampleQueryの実行
ExampleQueryの実行

ExampleQueryの中にauthorを追加することでtitle, authorを含むデータを取得することができるようになります。

title, authorを取得
title, authorを取得

Apollo Serverを使うことによって簡単にGraphQLを実行する環境を作成することができました。

booksに保存されているデータを取得することができましたがあるauthorのtitleのみ取得したい場合にどのような設定を行えばいいか確認を行います。スキーマとResorversに追加をおこないます。

スキーマではtype Queryの中にbookを追加し引数にauthorを取り必須とします。結果はBookが戻されるようにしています。


const typeDefs = gql`
  type Book {
    title: String
    author: String
  }
  type Query {
    books: [Book]
    book(author: String!): Book
  }
`;

resolversに実行する処理を追加します。


const resolvers = {
  Query: {
    books: () => books,
    book: (parent, args) => {
      let book = books.find((book) => book.author === args.author);
      return book;
    },
  },
};

設定は完了です。

Apollo Studioでtitleが取得できるのか確認を行います。bookの引数であるauthorをVariableで設定を行います。Operationsの中のqueryも先ほどとは異なり引数の$authorが設定されています。実行すると変数に一致するauthoerのtitleが表示されます。

bookのクエリーの実行
bookのクエリーの実行

しかしここで一つの疑問が浮かびます。クライアントからアクセスする際にはApollo Studioを利用することはできません。どのようにクライアントがGraphQLのデータにアクセスするのでしょう。その方法について確認していきます。

fetch関数によるGraphQLサーバからデータ取得

クライアントからGraphQLへのアクセスにはGraphQL ClientがありますがGraphQL Clientを利用しなくてもアクセスすることは可能です。ここではJavaScriptのfetch関数を利用します。

fetchを利用してGraphQLにアクセスを行いデータが取得できるか確認を行います。

fetchを使ってデータを取得するためにhtmlファイルを作成し、scriptタグにfetchを利用したコードを記述します。


<!DOCTYPE html>
<html lang="ja"">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <h1>Fetchを利用したGraphQLへのアクセス</h1>
  </body>
  <script>
    async function fetchBooks() {
      const response = await fetch("http://localhost:4000", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          query: `query{
                      books{
                          title
                          author
                        }
                    }`,
        }),
      });

      const data = await response.json();

      console.log(data);
    }

    fetchBooks();
  </script>
</html>

fetchを利用してGraphQLにアクセスする場合のポイントがいくつかあります。

  • メソッドにはPOSTを利用
  • bodyにGraphQLのqueryを記述しJSONに変換
  • headersにはContent-Typeのapplication/jsonを指定

console.logの内容を確認するとサンプルデータの内容が表示されていることが確認できます。

fetchでGraphQLサーバからデータ取得
fetchでGraphQLサーバからデータ取得

GraphQLのClientを利用しなくてもGraphQLサーバからfetchを利用してデータが取得できることが確認できました。

Vueの設定

Viteによるプロジェクトの作成

Viteを利用してVueのプロジェクトの作成を行います。Vueのプロジェクトを作成する余裕がない人向けにcdnを使った場合の処理も記述しています。


 % npm init vite@latest vue-apollo -- --template vue

vue-apolloフォルダが作成されるのでvue-apolloフォルダに移動してnpm installを実行します。


 % cd vue-apollo
 % npm install

npm run devコマンドで開発サーバを起動します。

ViteでのVueの初期画面
ViteでのVueの初期画面

fetch関数によるデータに取得

JavaScriptからデータの取得ができたのでVue上でのfetch関数を利用してデータを取得することができます。Composition APIのscriptタグを利用してコードを記述しています。


<script setup>
import { ref } from 'vue';
const books = ref([]);

const fetchBooks = async () => {
  const response = await fetch('http://localhost:4000', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: `query{
               books{
                title
                author
               }
            }`,
    }),
  });
  const { data } = await response.json();
  books.value = data.books;
};


fetchBooks();

</script>

<template>
  <h1>Fetchを利用したGraphQLへのアクセス</h1>
  <ul>
    <li v-for="(book, index) in books" :key="index">
      {{ book.title }}/{{ book.author }}
    </li>
  </ul>
</template>
Apollo Serverから取得したデータをv-forで展開
Apollo Serverから取得したデータをv-forで展開

cdnによるVue.jsでのデータの取得

Vueプロジェクトではなくcdnを利用してfetch関数によりデータの取得を行うこともできます。

fetch関数の内容は変わっていませんが、取得したデータをデータプロパティのbooksに保存して、v-forで展開を行っています。


<!DOCTYPE html>
<html lang="ja"">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <h1>Fetchを利用したGraphQLへのアクセス</h1>
    <div id="app">
        <ul>
            <li v-for="(book,index) in books" :key="book">{{ book.title }}/{{ book.author }}</li>
            </ul>
    </div>
  </body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
  <script>
    new Vue({
        el: "#app",
        data:{
            books:[]
        },
        methods:{
            async fetchBooks() {
                const response = await fetch("http://localhost:4000", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({
                        query: `query{
                            books{
                                title
                                author
                            }
                        }`,
                    }),
                });
                const { data } = await response.json()
                return data;
            }
        },
        mounted(){
            this.fetchBooks().then(({books}) => this.books = books);
        }
    })
  </script>
</html>

ブラウザでidex.htmlファイルを確認するとGraphQLに保存したサンプルデータが表示されることを確認できます。

Apollo Serverから取得したデータをv-forで展開
Apollo Serverから取得したデータをv-forで展開

Apollo Clientによるデータ取得

fetch関数を利用することでApollo Severからデータの取得が行えることがわかりました。Apollo ClientをVueプロジェクトにインストールを行いApollo Clientからのデータの取得方法を確認します。

ライブラリのインストール

Vue 3でApollo Clientを利用するためのライブラリはドキュメントhttps://v4.apollo.vuejs.org/を参考に行なっていきます。


 % npm install --save graphql graphql-tag @apollo/client

その後Composition (Advanced) APIを確認して@vue/apollo-composableをインストールします。


 % npm install --save @vue/apollo-composable

main.jsファイルに更新する内容についてもドキュメントを参考に行なっていきます。


import { createApp, provide, h } from 'vue';
import { DefaultApolloClient } from '@vue/apollo-composable';
import App from './App.vue';
import {
  ApolloClient,
  createHttpLink,
  InMemoryCache,
} from '@apollo/client/core';

const httpLink = createHttpLink({
  uri: 'http://localhost:4000/',
});

const cache = new InMemoryCache();

const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
});

const app = createApp({
  setup() {
    provide(DefaultApolloClient, apolloClient);
  },
  render: () => h(App),
});

app.mount('#app');

main.jsでのApollo Clientの設定が完了したらコンポーネントで利用できるようになります。

App.vueファイルでuseQueryを利用してデータの取得を行います。


<script setup>
import { useQuery } from '@vue/apollo-composable';
import gql from 'graphql-tag';

const { result } = useQuery(gql`
  query {
    books {
      title
      author
    }
  }
`);
</script>

<template>
  <h1>Fetchを利用したGraphQLへのアクセス</h1>
  <ul v-if="result && result.books">
    <li> v-for="(book, index) in result.books" :key="index">
      {{ book.title }}/{{ book.author }}
    </li>
  </ul>
</template>

まとめ

JavaScriptのfetch関数を利用してApollo Serverからデータを取得することができることがわかりました。Vue.jsではApollo Clientのライブラリを利用することでfetch関数よりも短いコードでデータの取得が行えることも確認できました。