24
20

More than 3 years have passed since last update.

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

Posted at

前回のつづき
Gatsby.js(Next.js)のテーマ制作から学ぶ【React.js × GraphQL】のブログシステムでの投稿記事情報取得

Next.jsのAPIルートとは

参考文献
APIルート(公式ドキュメント)

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で関数(ハンドラ)をエクスポートする必要がある。reqhttp.IncomingMessageのインスタンス。reshttp.ServerResponseのインスタンス。

GraphQLを使用したAPIルート

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

adf.png

参考文献
API routes with GraphQL server - GitHub

ファイル構造

  • 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
graphql - npm
isomorphic-unfetch - npm

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

$ 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を実装するための勉強ノート

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

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

  • 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のようなものと考えるといい。

schema {
    query: Query
    mutation: Mutation
}

参考文献
Schemas and Types #Lists and Non-Null - 公式ドキュメント
Web API初心者と学ぶGraphQL

GraphQLサーバのリゾルバ関数

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

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

参考文献
<Root fields & resolvers - 公式ドキュメント

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

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

参考文献
api - リゾルバー関数からフィールド引数にアクセスする方法は?

下記の例では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 - 公式ドキュメント
apollo-server - GitHub

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

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

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

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

参考文献
nodejsで開発/本番の設定を切り替える

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

参考文献
body-parser - Node.js入門

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

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

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

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

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

読むべきドキュメント
Apollo Server - 公式ドキュメント
apollo-server - GitHub

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 }
}

アプリ起動

$ npm run dev

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

dfasdf.png

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
20