Apollo なら爆速で GraphQL サーバーと GraphQL クライアントアプリが作れる

はじめに

最近気になっていた Apollo について、1 から調べてみました。Apollo 入門記事の位置付けを目指しています。
よって、本記事は以下のような読者を対象にしています。

  • Apollo って聞いたことはあるけど、何ができるのかよくわからない
  • 気にはなってたけど、日本語の情報が少なくて調べる気になれない
  • バックエンドのツールなのかフロントエンドのツールなのか、話はそれからだ

本記事の内容

  • Apollo の簡単な概要
  • Apollo を使って GraphQL サーバーを建ててみる
  • Apollo を使って GraphQL サーバーを利用する React アプリを作ってみる

Apollo の簡単な概要

公式ページはここです。
以下の画像が思想の全てを語っていると思います。

すべてが A(pollo)になる。

Apollo を利用することで、あらゆるデータが GraphQL サーバーとして集約されます。
こうすることでバックエンドの複雑性が Apollo によって隠蔽され、クライアントおよびバックエンドは Apollo サーバーとの接続にのみ注意すればよくなるわけです。
例えば REST サーバーから GraphQL サーバーに移行したいのであれば、一度 Apollo を間に挟んで GraphQL サーバーを利用できる状態にしておき、後から段階的に移行していく、などという方法がとれますね。

一方で、集約先となる GraphQL サーバーが複雑かつ巨大になることが予想されます。この問題については、Apollo Federationという GraphQL サービス統合機能で緩和させているようです。雰囲気だけでも知りたい方はこちらの記事がお勧めです。

Apollo でできること

  • GraphQL サーバーが簡単に構築できる
  • 各種バックエンドサービスを統合し、1 つの GraphQL エンドポイントに集約できる(本記事では触れない)
  • 集約されたデータをグラフ化することができる(本記事では触れない)
  • GraphQL を利用する JavaScript クライアントアプリが簡単に作成できる

Apollo Server を使ってみる

それでは、Apollo Server で GraphQL サーバーを作ってみましょう。
公式の Get startedを参考に進めていきます。

$ mkdir server  
$ cd server  
$ yarn init  
$ yarn add apollo-server graphql  
$ touch index.js  
const { ApolloServer, gql } = require("apollo-server");  

// スキーマを定義する  
const typeDefs = gql`  
  type Book {  
    title: String  
    author: String  
  }  

  type Query {  
    books: [Book]  
  }  
`;  

// クエリで取得するデータを定数で置いておく  
const books = [  
  {  
    title: "Harry Potter and the Chamber of Secrets",  
    author: "J.K. Rowling"  
  },  
  {  
    title: "Jurassic Park",  
    author: "Michael Crishton"  
  }  
];  

// booksクエリ発行時の処理を指定する  
const resolvers = {  
  Query: {  
    books: () => books  
  }  
};  

// サーバーを起動する  
const server = new ApolloServer({ typeDefs, resolvers });  

server.listen().then(({ url }) => {  
  console.log(` Server ready at ${url}`);  
});  

node で起動してみましょう。デフォルトで 4000 番ポートを利用するので、URL はhttp://localhost:4000/になるかと思います。この URL にクエリを投げる、あるいはブラウザで直接アクセスできれば OK です。

チュートリアルそのままのコードですが、これってものすごいコードだと思いませんか?
上記から大事な部分だけを取り出してみると、これっぽっちしかありません。

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

const typeDefs = gql`{スキーマ定義}`;  

const resolvers = {  
  Query: {  
    // クエリに応じた関数の登録  
  }  
};  

// サーバーを起動  
const server = new ApolloServer({ typeDefs, resolvers });  
server.listen().then(({ url }) => {  
  // 起動時処理  
});  

これだけで GraphQL サーバーが立ち上がるんです。
しかも、クエリに登録する関数は返り値の型さえ守っていれば OK なわけです。定数を返しても裏で REST を叩いても DB 接続してもお構いなし。開発者の自由です。
本来ライブラリやフレームワークとはそういうものですが、必要十分なスマートさが美しく感じるのは私だけでしょうか。

Apollo Client を使ってみる

GraphQL の API サーバーが用意できたので、次はクライアントアプリを作成します。Angular、Vue、Svelte など、モダンな JavaScript ライブラリは一通り用意してあるようですが、今回は React を使います。
例によって公式の Get startedを参考にしますが、内容はだいぶ変わります。

$ mkdir client  
$ cd client  
$ yarn create react-app .  
$ yarn add apollo-boost @apllo/react-hooks graphql  

src/App.jsの中身を全て消して、以下の通りに書き換えます。

import React, { useMemo } from "react";  
import ApolloClient, { gql } from "apollo-boost";  
import { ApolloProvider, useQuery } from "@apollo/react-hooks";  

const App = () => {  
  // Apollo Clientを初期化する  
  const client = useMemo(  
    () => new ApolloClient({ uri: "http://localhost:4000" }),  
    []  
  );  

  return (  
    <div className="App">  
      <ApolloProvider client={client}>  
        <div>  
          <h2>  
            My first Apollo app{" "}  
            <span role="img" aria-label="Rocket">  
                
            </span>  
          </h2>  
          <Books />  
        </div>  
      </ApolloProvider>  
    </div>  
  );  
};  

const Books = () => {  
  // 発行クエリを定義する  
  const booksQuery = useMemo(  
    () => gql`  
      {  
        books {  
          title  
          author  
        }  
      }  
    `,  
    []  
  );  

  // クエリを発行する  
  const { loading, error, data } = useQuery(booksQuery);  

  // 結果を表示する  
  if (loading) return <p>Loading...</p>;  
  if (error) return <p>Error :(</p>;  

  return data.books.map(({ title, author }, index) => (  
    <div key={title}>  
      <h3>book{index + 1}</h3>  
      <p>title:{title}</p>  
      <p>author:{author}</p>  
    </div>  
  ));  
};  

export default App;  

脇道に逸れますが、ApolloProviderコンテクストを利用しているようですね。useQueryは内部でuseContextを呼び出すカスタムフックのようです。これにより、コンポーネントのどこからでもクエリを発行できるようにしています。

では実行してみましょう。前述の Apollo Server が起動している状態で、Apollo Client を起動します。
以下のような画面が表示されるはずです。

先ほど作成した Apollo Server からデータを取得できているようですね。サクッと GraphQL クライアントアプリが作成できました。

おわりに

というわけで、Apollo を使ってサクッとアプリを作れることがわかりました。
PrismaGraphQL code generatorと組み合わせれば快適な GraphQL ライフを過ごせるようになりそうです。
なお、今回のコードを monorepo 形式および TypeScript でざっくり書いたものはこちらになります。

本来 Apollo が主眼に置いているのはデータグラフのようです。Docs のトップでわざわざ太字にされているくらい。
今回はサーバーとクライアントを作るだけにとどめましたが、また適当なときに触ってみたいと思います。

それでは良いお年を!

参考文献

Apollo
Apollo Documentation Home
Apollo Federation のすゝめ -GraphQL とマイクロサービス-
GraphQL でスキーマファーストな Form Validation
TypeScript + Node.js プロジェクトのはじめかた 2019
monorepo について調べた
yarn ワークスペース