Next.jsのAPIルートにGraphQL置いて動かしてみた

>前回のつづき
>[Gatsby.js（Next.js）のテーマ制作から学ぶ【React.js × GraphQL】のブログシステムでの投稿記事情報取得](https://qiita.com/oekaki-hoho-ron/items/43467bdcb9f85cfd53e7)

#Next.jsのAPIルートとは

>参考文献
>[APIルート（公式ドキュメント）](https://nextjs.org/docs/api-routes/introduction)

`pages/api/`に配置したファイルは`/api/*`にマッピングされ、APIのエンドポイントとして扱われる。例えば、下記のAPIルート`pages/api/user.js`は単純なJSONを返す。

```pages/api/user.js
export default (req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ name: 'Jhon Doe' }))
}
```

APIルートを機能させるには`export default`で関数（ハンドラ）をエクスポートする必要がある。`req`は`http.IncomingMessage`のインスタンス。`res`は`http.ServerResponse`のインスタンス。

#GraphQLを使用したAPIルート

Next.jsにGraphQLを実装する方法をいろいろ調べたけど、灯台下暗し、公式ドキュメントにシンプルなソースコードが置かれていた。

<img width="665" alt="adf.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278293/c5793078-3782-1b36-ac8d-8dfa16da92c8.png">

>参考文献
>[API routes with GraphQL server - GitHub](https://github.com/zeit/next.js/tree/canary/examples/api-routes-graphql)

###ファイル構造

- package.json
- pages/
    - index.js
    - api/
        - graphql.js

###サンプルコードを見てみる

```package.json
{
  "name": "api-routes-graphql",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "apollo-server-micro": "2.6.7",
    "graphql": "14.4.2",
    "isomorphic-unfetch": "3.0.0",
    "next": "latest",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "license": "ISC"
}
```

```pages/api/graphql.js
import { ApolloServer, gql } from 'apollo-server-micro'

const typeDefs = gql`    // スキーマ定義
  type Query {    // クエリ定義
    users: [User!]!
  }
  type User {    // データ型定義
    name: String
  }
`

const resolvers = {    // データ取得処理
  Query: {
    users(parent, args, context) {
      return [
        { name: 'データ1' },
        { name: 'データ2' }
      ]
    },
  },
}

const apolloServer = new ApolloServer({ typeDefs, resolvers })    // GraphQL設定のインスタンス生成

export const config = {    // ここはなんだ？
  api: {
    bodyParser: false,
  },
}

export default apolloServer.createHandler({ path: '/api/graphql' })    // エンドポイントの指定
```


```pages/index.js
import fetch from 'isomorphic-unfetch'

const Index = ({ users }) => (    // コンポーネント内でJSONデータを使用するので引数に渡している
  <div>
    {users.map((user, i) => (    // 受け取ったJSONデータi個分を網羅して処理する
      <div key={i}>{user.name}</div>
    ))}
  </div>
)

Index.getInitialProps = async () => {    // ここはなんだ？
  const response = await fetch('http://localhost:3000/api/graphql', {
    method: 'POST',
    headers: { 'Content-type': 'application/json' },
    body: JSON.stringify({ query: '{ users { name } }' }),
  })

  const { data: { users }} = await response.json()    // 取得したJSONデータをdataオブジェクトで受け取る
  return { users }    // 取得したJSONを返す
}

export default Index
```

###インストールされているパッケージの管理

`package.json`を見ると、Next.jsのスタンダードなインストール項目に加え、`apollo-server-micro` `praphql` `isomorphic-unfetch`というモジュールがインストールされている。

**apollo-server-micro**
Microを使用して`/graphql`エンドポイント経由でGraphQLリクエストを処理する。

**graphql**
GraphQLのJavaScriptリファレンス実装。

**isomorphic-unfetch**
クライアントとサーバーのアンフェッチとノードフェッチを切り替える、とのこと。？。もしかしてJSONファイルを取得するためとか？

>参考文献
>[apollo-server-micro - npm](https://www.npmjs.com/package/apollo-server-micro)
>[graphql - npm](https://www.npmjs.com/package/graphql)
>[isomorphic-unfetch - npm](https://www.npmjs.com/package/isomorphic-unfetch)

これらを自分でインストールするときはこう。

```zsh
$ npm i --save graphql apollo-server-micro isomorphic-unfetch
```

###スキーマに関する設定

apolloをインポートすれば`gql''`でスキーマを定義できるようになったりするのはこれまでの勉強で理解できる。要は普通のGraphQLが使えるようになるモジュールだと思うが、これまではそれが分かったところでどうやっって実装すればいいか分からなかった。しかし、今回の記事で発見したNext.jsのAPIルートという具体的な実装手段があればGraphQLを動かせそう。

GraphQLのコーディングの基礎についてはすでに勉強したので`graphql.js`のコードでは特に悩まずに済んだ。`typeDefs`はスキーマ定義、`resolvers`はデータ取得処理。

>関連記事
>[【GraphQL 入門】 Next.js製ブログで記事情報を取得できるAPIを実装するための勉強ノート](https://qiita.com/oekaki-hoho-ron/items/10fe2dc3bc8ee66d2ed0)

ここの`typeDefs`の書き方についてまだ理解してなかったので、コードを読むためにざっくりと調べておく。まず`users: [User!]!`という記述は、`users`はデータを入れるために用意しておいた配列、`User`はデータ型定義で独自につけた名前、`User!`は配列の中に入っている値がnullでないという意味、`[ ]!`は配列が空でないという意味、つまりクエリ発行としては「usersに入っている空でない配列の中のnullでない値」という感じになる。もしデータ元からnullが返ってきた場合はエラーを返す。スキーマの理解については[公式ドキュメントの「スキーマとタイプ」の項目](https://graphql.org/learn/schema/)を読んでおく必要がある。

データ型定義における代表的なものを挙げておく。

- `Int`　整数型
- `Float`　浮動小数点型
- `String`　UTF-8文字列型
- `Boolean`　ブーリアン型
- `ID`　一意の識別子を表し、オブジェクトの再取得やキャッシュのキーとして使用

スキーマ部分のコードの意味を意訳してみる。

```pages/api/graphql.js
const typeDefs = gql`
  type Query {
    users: [User!]!
  }
  type User {
    name: String
  }
`
```

`Query`オブジェクトの`users`という名前のクエリで`[User]`という配列を取得するという定義がされている。`User`オブジェクトは`String`型の`name`フィールドを持つオブジェクトという定義がされている。この`Query`オブジェクトがどこから来たかというと、下記コードのようにGraphQLでデフォルトで定義がされているところから。
ちなみにGraphQLのQueryはRESTにおけるGETのようなものと考えるといい。

```graphql
schema {
    query: Query
    mutation: Mutation
}
```

>参考文献
>[Schemas and Types #Lists and Non-Null - 公式ドキュメント](https://graphql.org/learn/schema/#lists-and-non-null)
>[Web API初心者と学ぶGraphQL](https://qiita.com/SiragumoHuin/items/cc58f456bc43a1be41b4)

###GraphQLサーバのリゾルバ関数

GraphQLサーバーはさまざまな言語で構築できる。リゾルバ関数には4つの引数を渡せる。

- `obj`　処理を決定するためのスキーム情報が入っていると思われる。ルートQueryタイプのフィールドでは、多くの場合、使用されない前のオブジェクト。
- `args`　GraphQLクエリのフィールドに渡される引数。
- `context`　全てのリゾルバに渡され、現在ログイン中のユーザやDBへのアクセスなどの重要なコンテキスト情報を保持する値。
- `info`　現在のクエリとスキーマの詳細に関連するフィールド固有の情報を保持する値。詳細は[GraphQLResolveInfo型](https://graphql.org/graphql-js/type/#graphqlobjecttype)を参照。

>参考文献
<[Root fields & resolvers - 公式ドキュメント](https://graphql.org/learn/execution/#root-fields-resolvers)

公式ドキュメントでは上記の4つだったが、サンプルコードにある`parent`という引数は何なのか。調べるとスキーマ情報の`ogj`と違って、`parent`は前回の結果を入れるらしいことがわかった。

- `parent`　前のリゾルバー呼び出しの結果（詳細）。
- `args`　リゾルバーのフィールドの引数。
- `context`　各リゾルバが読み書きできるカスタムオブジェクト。
- `info`　クエリASTおよびその他の実行情報が含まれています

>参考文献
>[api - リゾルバー関数からフィールド引数にアクセスする方法は？](https://tutorialmore.com/questions-1225114.htm)

下記の例では`users`というクエリに3つの引数が渡されているけど、まぁ今はテンプレとして書いておけばいいかと。GraphQLを使っていろいろ開発を重ねる中で具体例を学んでいけばいずれ理解できると信じて。そして`return`でお求めのオブジェクトデータを返すという寸法。本来はDBやJSONからデータを引っ張ってきたいところだけど、まずはNext.jsでGraphQLを動かす段階まで進めるのが先決。ここで用意したオブジェクトデータの中身は`type User`で定義したデータ型である必要がある。

```pages/api/graphql.js
const resolvers = {
  Query: {
    users(parent, args, context) {
      return [
        { name: 'データ1' },
        { name: 'データ2' }
      ]
    },
  },
}
```

###`new ApolloServer()`について

次は`ApolloServer`のインスタンスを生成している部分。`require`しておいた`ApolloServer`に引数としてスキーマの`typeDefs`とリゾルバの`resolvers`を渡して`new`して生成したインスタンスを`apolloServer`に代入、というコード。

```pages/api/graphql.js
const apolloServer = new ApolloServer({ typeDefs, resolvers })
```

apollo-serverの公式ドキュメントを参考にすると、Apollo Serverは、任意のソースからのデータを使用して、GraphQLクライアントの本番用の自己文書化APIを構築する。オープンソースであり、スタンドアロンサーバ、既存のNode.js HTTPサーバへのアドオン、サーバレス環境として動作。

よく分からないです。

`apollo-server`パッケージを使用する場合、インスタンス化された`ApolloServer`の上で`listen`を呼び出すと、指定されたオプションをNode.js `http.Server`に渡すことでサーバが起動される。

よく分からないです。

とりあえずGraphQLサーバに接続するためのモジュールに引数を装備させた状態ということでOK？

>参考文献
>[Apollo Server - 公式ドキュメント](https://www.apollographql.com/docs/apollo-server/)
>[apollo-server - GitHub](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-micro)

とはいえ、apollo-server公式ドキュメントには`resolver`で渡した4つの引数についても深く掘り下げてくれているので、リゾルバの勉強としてはこっちを先に読めばよかったかも。あと`obj`と`parent`の違いはGraphQL式とApollo Server式の違いっぽい。

###アプリケーションコンフィグ

```pages/api/graphql.js
export const config = {
  api: {
    bodyParser: false,
  },
}
```

`config`は開発環境の設定に関係するらしいが、`body-parser`をオフにして何をしたいのか調べてもよく分からなかった。とりあえずう`api`で`body-parser`を使いたくないんだなということを頭のスミにでもおいておくことにした。

>参考文献
>[nodejsで開発/本番の設定を切り替える](https://qiita.com/tomi_linka/items/825ed6189a80abb277f0)

以前から`bodyParser`の存在意義がイマイチ分からなかったが、どうやらHTTPリクエストでやりとりされるデータのうちボディにあたる部分を読み込んで`req.body`にい解析結果を渡すミドルウェアらしい。つまりGraphQLで読み込んだJSONファイルなどを`req.body`に取得するものという解釈でいいと思う。

>参考文献
>[body-parser - Node.js入門](https://nodejs.keicode.com/nodejs/body-parser.php)

###あぽろさーばーくりえーとはんどらー

ググってもしっくりくる答え見つからず。理解は後回し。おそらく`path`にGraphQLのエンドポイントを設定している。

```pages/api/graphql.js
export default apolloServer.createHandler({ path: '/api/graphql' })
```

###公式ドキュメント読もうか

いろいろ調べてみたけど、結局は下記の公式ドキュメントを読み込むのが一番確実かと思った。GraphQL公式ドキュメントと、Apollo Server公式ドキュメント。スキーマやリゾルバなど、ほぼ似た内容が書かれているが微妙に視点が異なるので視野を広げるにはいいかも。

>読むべきドキュメント
>[Apollo Server - 公式ドキュメント](https://www.apollographql.com/docs/apollo-server/)
>[apollo-server - GitHub](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-micro)

###Reactコンポーネントに`users`クエリを渡す

`users: [User!]!`は配列なので`map`メソッドで中身を網羅して`user`という引数に受け取ったデータを入れていく。`i`はidか何かが由来？通し番号？とりあえずこれで欲しかったデータの`name`が手に入るコンポーネントができた。

```pages/index.js
const Index = ({ users }) => (
  <div>
    {users.map((user, i) => (
      <div key={i}>{user.name}</div>
    ))}
  </div>
)

（中略）

export default Index
```

###何かしている部分

Reactコンポーネントに`users`を引数として渡して、user数にmapメソッドをかましてJSONの中身を順に書き出す。

```pages/index.js
Index.getInitialProps = async () => {
  const response = await fetch('http://localhost:3000/api/graphql', {
    method: 'POST',
    headers: { 'Content-type': 'application/json' },
    body: JSON.stringify({ query: '{ users { name } }' }),
  })

  const { data: { users }} = await response.json()
  return { users }
}
```

###アプリ起動

```zsh
$ npm run dev
```

[http://localhost:3000/](http://localhost:3000/)に接続すると「データ1　データ2」と表示される。

<img width="443" alt="dfasdf.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278293/7a9a373a-952e-9948-8bdf-456a918e3fc9.png">


#つまりNext.jsでGraphQLを使う方法は？

1. `apollo-server-micro` `graphql` `isomorphic-unfetch`をインストール
2. APIルートに`graphql.js`を設置してスキーマなどを定義。
3. Reactコンポーネントのファイルで取得したJSONデータをJSX内に吐き出す