アニメオタクのためのGraphQL


![graphql-basic01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/bb87afc0-3b4a-9d4c-bfc6-8b57a9d8c02a.jpeg)


どうも、**涼宮ハルヒ生まれ、けいおん育ち**
ファンタジスタ☆みゆきです。

みなさん、GraphQLを使っていますでしょうか？
REST APIしか実装したことがないという人はこれを機に一度GraphQLを実装してみてはと思います。

本稿では[Annict](https://annict.com/)というアニメ管理アプリにGraphQLでクエリを送信してみたりApollo Serverをローカルで立ててGraphQLを実装していきたいと思います。

## まずは叩いてみる

百聞は一見に如かず。まずはGraphQLを実際に叩いてみましょう。

では、Annictのエンドポイントにアクセスするために簡単な個人用トークンを発行していきます。
[こちら](https://developers.annict.jp/graphql-api/personal-access-token/)を参考にトークンを発行しましょう。

トークンが発行できたら手始めにターミナルからcurlでクエリを叩いてみます。
※ACCESS_TOKENはご自身のものを入力ください。

```
$ curl https://api.annict.com/graphql \
-H "Authorization: bearer ACCESS_TOKEN" \
-X POST \
-d "query=query { viewer { name } }"
```

以下のようにJSONデータが返ってきたらOKです！

```
{"data":{"viewer":{"name":"USER_NAME"}}}
```

次に、GUIクライアントでqueryを送ってみましょう。
[GraphQL公式](https://graphql.org/)が紹介しているのは[GraphiQL](https://github.com/graphql/graphiql)か[Insomnia](https://github.com/Kong/insomnia)ですが、私はChrome拡張の[Altair](https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja?utm_source=chrome-ntp-icon)を使っていきます。

まず下図のように、メソッドをPOSTにし、URLにhttps://api.annict.com/graphqlを入力します。

![graphql-basic02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/e1918202-dad6-1281-7dd6-fb14b5f1c6b0.png)


次に、ヘッダーに認証情報を入力します。
GUIクライアントによって多少UIは違いますがAltairでは左ペインから設定が可能です。
Authorizationとして、認証情報にBearer + 先ほどの個人用アクセストークンを入力します。

![graphql-basic03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/36ef18a3-eadf-50f5-8118-fa71e8bb6d23.png)

![graphql-basic04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/a18fb033-d42e-26ee-504b-36b56475ad88.png)


それではQuery画面から先ほどのqueryを再送してみます。

```
# request

query {
 viewer {
    username,
    name,
  }
}
```

```
# response

{
  "data": {
    "viewer": {
      "username": "USER_NAME",
      "name": "NAME"
    }
  }
}
```

このように返ってきていたらOKです！

また、Annictではアニメ作品情報なども取得できます。
取得できるものについはは[リファレンス](https://developers.annict.jp/graphql-api/reference/)から確認可能です。

では、最新の2021年秋アニメの作品情報を取得していきましょう。

```
# request

query {
  searchWorks(seasons: ["2021-autumn"], orderBy: { field: WATCHERS_COUNT, direction: DESC }, first: 5) {
    edges {
      node {
        annictId
        title
        watchersCount
      }
    }
  }
}
```

```
# response

{
  "data": {
    "searchWorks": {
      "edges": [
        {
          "node": {
            "annictId": 8200,
            "title": "無職転生 ～異世界行ったら本気だす～  第2部",
            "watchersCount": 815
          }
        },
        {
          "node": {
            "annictId": 7969,
            "title": "鬼滅の刃 遊郭編",
            "watchersCount": 805
          }
        },
        {
          "node": {
            "annictId": 7669,
            "title": "劇場版 ソードアート・オンライン プログレッシブ 星なき夜のアリア",
            "watchersCount": 537
          }
        },
        {
          "node": {
            "annictId": 7917,
            "title": "ブルーピリオド",
            "watchersCount": 508
          }
        },
        {
          "node": {
            "annictId": 8181,
            "title": "劇場版 呪術廻戦 0",
            "watchersCount": 507
          }
        }
      ]
    }
  }
}
```

今期はAnnictでは今のところ無職転生が期待されてることがわかりますね。
ロキシーかわいいよ、ロキシー。~~あー、ご神体が欲しい~~

![graphql-basic05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/ec105479-3123-9a09-22f9-9c5c86de5d09.png)


少しqueryの内容についても触れておきます。
基本的にオブジェクトをネストしていくような書き方になります。まず最初の文ですがqueryとだけ入力しています。GraphQLには命令としてqueryとmutationが用意されていてqueryがGETメソッド、mutationがPOSTメソッドのように動作します。
今回アニメ情報をGETしたいのでqueryと記述しています。

次に、searchWorks関数で取得する作品を引数で設定しています。シーズンと検索条件を入力していますね。今回は2021年秋クールで、視聴登録数が多い順に5件取得しています。

そして、edgesの次にnodeで取得するフィールドを記述しています。
今回はアニメIDとタイトルと視聴登録数を返すようにしています。

それでは、せっかくなんで2021年夏アニメの結果も取得してみましょう。

```
# request

query {
  searchWorks(seasons: ["2021-summer"], orderBy: { field: WATCHERS_COUNT, direction: DESC }, first: 5) {
    edges {
      node {
        annictId
        title
        watchersCount
      }
    }
  }
}
```

```
# response

{
  "data": {
    "searchWorks": {
      "edges": [
        {
          "node": {
            "annictId": 6492,
            "title": "小林さんちのメイドラゴンS",
            "watchersCount": 2014
          }
        },
        {
          "node": {
            "annictId": 7411,
            "title": "転生したらスライムだった件 第2期 第2部",
            "watchersCount": 1472
          }
        },
        {
          "node": {
            "annictId": 7922,
            "title": "白い砂のアクアトープ",
            "watchersCount": 1453
          }
        },
        {
          "node": {
            "annictId": 7547,
            "title": "乙女ゲームの破滅フラグしかない悪役令嬢に転生してしまった…X",
            "watchersCount": 1277
          }
        },
        {
          "node": {
            "annictId": 7973,
            "title": "探偵はもう、死んでいる。",
            "watchersCount": 1196
          }
        }
      ]
    }
  }
}
```

京アニのメイドラゴンや転スラの2期、PAのアクアトープが人気でしたね。
ちなみに、私は[探偵はもう、死んでいる](https://tanmoshi-anime.jp/)が一番好みでした。

銀髪ヒロインしか勝たん。

![graphql-basic06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208060/9c847f5f-9730-898f-2075-9040fbda9fb7.png)



## 実装編

ここからは実際にGraphQLサーバを実装していきたいと思います。

### 環境

- サーバサイド言語: node v12.19.0
- GraphQLサーバ: Apollo server

さっそく任意のパスにプロジェクトディレクトリを作成していきます。

```
mkdir graphql-server
```

```
npm init
```

モジュールのインストール。

```
npm install express apollo-server-express graphql --save
```

ES6で記述するのでbabelのインストール。

```
npm install @babel/core @babel/node @babel/preset-env --save-dev
```

.babelrcというbasel設定ファイルを作成して以下のように記述します。

```
{
  "presets": [
    "@babel/preset-env"
  ]
}
```

プロジェクトのrootディレクトリにsrcフォルダを作成してindex.jsに以下のように記述。

```js:src/index.js
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import http from 'http';

const app = express();
const httpServer = http.createServer(app);

const server = new ApolloServer();

const port = process.env.PORT || 4000;

const start = async () => {
    await server.start();
    server.applyMiddleware({ app, path: '/graphql' });
    await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve));
    console.log(`Server ready at http://localhost:${port}${server.graphqlPath}`);
};

start();
```

上記のコードに加えて、スキーマとリゾルバを記述してあげる必要があります。
**スキーマ**とは、フィールドに対して型を定義したもので、
**リゾルバ**とは、エンドポイントに対して関数を定義するものとなります。

では、srcフォルダに新たにschemasフォルダとresolversフォルダを作成して、両方にindex.jsを記述していきます。
まずはresolversフォルダのindex.jsを記述します。

```js:src/resolvers/index.js
const db = [{ id: 1, name: 'Siesta'}]; // in memory data store
const resolvers = {
  Query: {
    getMe: () => {
      return db[0];
    },
    getUserById: (parents, args) => {
      const { id } = args
      if (id <= 0 || id > db.length) throw new Error('User Not found.');
      else {
        const i = id - 1;
        return db[i];
      }
    },
    getUserByName: (parents, args) => {
      const { name } = args
      if (!name) throw new Error('User Not found.');
      else {
        for (let i in db) {
          if (db[i]['name'] === name) {
            const id = db[i]['id'];
            const j = id - 1
            return db[j];
          }
        }
      }
    },
  },
  Mutation: {
    createUser: (parent, args) => {
      const count = db.length;
      const id =  count + 1;
      let { name } = args;
      if (!name) name = 'no name';
      const newUser = { id, name };
      db.push(newUser);
      return newUser;
    },
  }
};
export default resolvers;
```

次にschemasフォルダのindex.jsを記述します。

```js:src/schemas/index.js
import { gql } from 'apollo-server-express';

const schema = gql`
  type Query {
    getMe: User
    getUserById(id: ID!): User
    getUserByName(name: String!): User
  }

  type Mutation {
    createUser(
      name: String!
    ): User!
  }

  type User {
    id: ID!
    name: String!
  }
`;
export default schema;
```

このresolversとschemasを用いて最初のsrc/index.jsに追記します。

```js:src/index.js
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import http from 'http';
/// 追記
import schemas from './schemas';
import resolvers from './resolvers';
///

const app = express();
const httpServer = http.createServer(app);

// 修正
const server = new ApolloServer({
  typeDefs: schemas,
  resolvers,
});
//
const port = process.env.PORT || 4000;

const starter = async () => {
    await server.start();
    server.applyMiddleware({ app, path: '/graphql' });
    await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve));
    console.log(`Server ready at http://localhost:${port}${server.graphqlPath}`);
};

starter();

```

では、rootディレクトリのpackage.jsonのscriptsがkeyのオブジェクトに以下を追記してnpm startを使えるようにします。

```package.json
"start": "babel-node src/index.js"
```

コンソール画面に以下のように表示されたら成功です！

```
Server ready at http://localhost:4000/graphql
```

では、resolversで定義したqueryとmutationが実際に実行できるか確認していきましょう。
ローカルホストにブラウザから接続して以下のqueryを実行します。

```
# request

query {
  getUserById(id: 1) {
    id, name
  }
}
```
```
# response

{
  "data": {
      "getUser": {
          "id": "1",
          "name": "Siesta"
      }
  }
}
```

queryは通常のREST APIではGETの動作にあたります。
それでは、REST APIではPOSTの動作にあたるmutationを実行していきます。
※GraphQLではqueryもmutationもPOSTメソッドになる点に注意しておいてください。

```
# request

query {
  createUser(name: "Roxy") {
    id, name
  }
}
```
```
# response

{
  "data": {
      "createUser": {
          "id": "2",
          "name": "Roxy"
      }
  }
}
```

最後に、ユーザをIDの検索だけでなく、ユーザ名で検索できるようにも定義していたのでそれを確認していきましょう。

```
# request

query {
  getUserByName(name: "Roxy") {
    id,
    name,
  }
}
```
```
# response

{
  "data": {
    "getUserByName": {
      "id": "2",
      "name": "Roxy"
    }
  }
}
```

無事に確認できましたね！


## おわりに

GraphQLはサーバサイドで実装していってなんぼだと思いますので、これからドンドンサービスに組み込んでいきたいですね。

## 参考

1. [Annict Developers](https://developers.annict.jp/graphql-api/)
2. [How to Build a Simple GraphQl Server in NodeJs](https://ugo.hashnode.dev/how-to-build-a-simple-graphql-server-in-nodejs)
3. [Schemas and Types](https://graphql.org/learn/schema/)
4. [GraphQLのスキーマと型定義](https://qiita.com/NagaokaKenichi/items/d341dc092012e05d6606)
5. [🔥 GraphQL Crash Course (in 10 pics!)](https://dev.to/methodcoder/graphql-crash-course-in-10-pics-3b04)