前回のつづき
Gatsby.js(Next.js)のテーマ制作から学ぶ【React.js × GraphQL】のブログシステムでの投稿記事情報取得
#Next.jsのAPIルートとは
参考文献
APIルート(公式ドキュメント)
pages/api/
に配置したファイルは/api/*
にマッピングされ、APIのエンドポイントとして扱われる。例えば、下記のAPIルートpages/api/user.js
は単純なJSONを返す。
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を実装する方法をいろいろ調べたけど、灯台下暗し、公式ドキュメントにシンプルなソースコードが置かれていた。
###ファイル構造
- package.json
- pages/
- index.js
- api/
- graphql.js
###サンプルコードを見てみる
{
"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"
}
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' }) // エンドポイントの指定
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
はデータ取得処理。
ここのtypeDefs
の書き方についてまだ理解してなかったので、コードを読むためにざっくりと調べておく。まずusers: [User!]!
という記述は、users
はデータを入れるために用意しておいた配列、User
はデータ型定義で独自につけた名前、User!
は配列の中に入っている値がnullでないという意味、[ ]!
は配列が空でないという意味、つまりクエリ発行としては「usersに入っている空でない配列の中のnullでない値」という感じになる。もしデータ元からnullが返ってきた場合はエラーを返す。スキーマの理解については公式ドキュメントの「スキーマとタイプ」の項目を読んでおく必要がある。
データ型定義における代表的なものを挙げておく。
-
Int
整数型 -
Float
浮動小数点型 -
String
UTF-8文字列型 -
Boolean
ブーリアン型 -
ID
一意の識別子を表し、オブジェクトの再取得やキャッシュのキーとして使用
スキーマ部分のコードの意味を意訳してみる。
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型を参照。
公式ドキュメントでは上記の4つだったが、サンプルコードにあるparent
という引数は何なのか。調べるとスキーマ情報のogj
と違って、parent
は前回の結果を入れるらしいことがわかった。
-
parent
前のリゾルバー呼び出しの結果(詳細)。 -
args
リゾルバーのフィールドの引数。 -
context
各リゾルバが読み書きできるカスタムオブジェクト。 -
info
クエリASTおよびその他の実行情報が含まれています
下記の例ではusers
というクエリに3つの引数が渡されているけど、まぁ今はテンプレとして書いておけばいいかと。GraphQLを使っていろいろ開発を重ねる中で具体例を学んでいけばいずれ理解できると信じて。そしてreturn
でお求めのオブジェクトデータを返すという寸法。本来はDBやJSONからデータを引っ張ってきたいところだけど、まずはNext.jsでGraphQLを動かす段階まで進めるのが先決。ここで用意したオブジェクトデータの中身はtype User
で定義したデータ型である必要がある。
const resolvers = {
Query: {
users(parent, args, context) {
return [
{ name: 'データ1' },
{ name: 'データ2' }
]
},
},
}
###new ApolloServer()
について
次はApolloServer
のインスタンスを生成している部分。require
しておいたApolloServer
に引数としてスキーマのtypeDefs
とリゾルバのresolvers
を渡してnew
して生成したインスタンスをapolloServer
に代入、というコード。
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公式ドキュメントにはresolver
で渡した4つの引数についても深く掘り下げてくれているので、リゾルバの勉強としてはこっちを先に読めばよかったかも。あとobj
とparent
の違いはGraphQL式とApollo Server式の違いっぽい。
###アプリケーションコンフィグ
export const config = {
api: {
bodyParser: false,
},
}
config
は開発環境の設定に関係するらしいが、body-parser
をオフにして何をしたいのか調べてもよく分からなかった。とりあえずうapi
でbody-parser
を使いたくないんだなということを頭のスミにでもおいておくことにした。
以前からbodyParser
の存在意義がイマイチ分からなかったが、どうやらHTTPリクエストでやりとりされるデータのうちボディにあたる部分を読み込んでreq.body
にい解析結果を渡すミドルウェアらしい。つまりGraphQLで読み込んだJSONファイルなどをreq.body
に取得するものという解釈でいいと思う。
###あぽろさーばーくりえーとはんどらー
ググってもしっくりくる答え見つからず。理解は後回し。おそらくpath
にGraphQLのエンドポイントを設定している。
export default apolloServer.createHandler({ path: '/api/graphql' })
###公式ドキュメント読もうか
いろいろ調べてみたけど、結局は下記の公式ドキュメントを読み込むのが一番確実かと思った。GraphQL公式ドキュメントと、Apollo Server公式ドキュメント。スキーマやリゾルバなど、ほぼ似た内容が書かれているが微妙に視点が異なるので視野を広げるにはいいかも。
読むべきドキュメント
Apollo Server - 公式ドキュメント
apollo-server - GitHub
###Reactコンポーネントにusers
クエリを渡す
users: [User!]!
は配列なのでmap
メソッドで中身を網羅してuser
という引数に受け取ったデータを入れていく。i
はidか何かが由来?通し番号?とりあえずこれで欲しかったデータのname
が手に入るコンポーネントができた。
const Index = ({ users }) => (
<div>
{users.map((user, i) => (
<div key={i}>{user.name}</div>
))}
</div>
)
(中略)
export default Index
###何かしている部分
Reactコンポーネントにusers
を引数として渡して、user数にmapメソッドをかましてJSONの中身を順に書き出す。
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」と表示される。
#つまりNext.jsでGraphQLを使う方法は?
-
apollo-server-micro
graphql
isomorphic-unfetch
をインストール - APIルートに
graphql.js
を設置してスキーマなどを定義。 - Reactコンポーネントのファイルで取得したJSONデータをJSX内に吐き出す