8
4

More than 3 years have passed since last update.

【GraphQL 入門】 Next.js製ブログで記事情報を取得できるAPIを実装するための勉強ノート

Last updated at Posted at 2019-12-29

GraphQL入門したいのだけどなかなか理解できずに四苦八苦した記録。もしGraphQL入門でつまづく人がいたら参考になればいいと思う。対象は完全に0からプログラミングを初めて今まさに勉強中というレベル。基礎知識のない状態で参考記事を読んでいるので、理解できるまでの解釈を独り言のように記録しているので無駄が多いかもしれないけど、ご勘弁ください。

一応具体的な目標はあって、Next.jsでブログシステムを自作したくて、記事情報を取得する処理を実装する方法を勉強中。GraphQLなら、記事をmdファイルで作成してfrontbatterの記事情報を取得できると聞いて入門することに。だいたいのイメージは分かるのだけど、自作でカスタマイズして動作させるには知識が足りない。まずはGraphQLの処理をコピペでなく自作できるレベルを目指す。

↓初心者には怪文書でしかない。これをどうにかして読めるレベルになりたい。

参考文献
Getting Started With GraphQL.js(公式ドキュメント)

GraphQLとは

スクリーンショット 2019-12-28 22.02.07.png

とりあえず基本情報に触れておく。

参考文献
The GitHub GraphQL API

GraphQLとは、2015年にFacebookが発表したAPIのクエリ言語であ、データ用に定義した型システムを使用してクエリを実行するためのサーバー側のランタイム。ウェブAPIの開発においてRESTやその他のWebサービスと比較して、効率的、堅牢、フレキシブルなアプローチを提供する。

GraphQLがどういう位置付けの技術なのかというと、GitHubのエンジニアのブログを読むと大体わかる。Node.jsなどを採用したWebアプリではサイト内のデータを扱うのにJSONを採用するのが一般的なイメージだけど、かつてはXMLが使われていたらしい(多分)。XMLはHTMLに似た構文で記述するものなのでコード量が半端なく多くて面倒で、しかも読みにくい。< >でタグを付けないといけないので煩わしいしタイピングも面倒い。なのでJSONの方が嬉しい、という挨拶に続けて、APIはRESTfulで設計されているが、RESTでは痒いところに手が届かない。一方でGraphQLはデータリクエストの柔軟性やデータ型チェックに長けているし、通信の無駄が解消される。みたいなことが書かれていた。

REST に 比べて GraphQL のクエリ発行が効率的という話

参考文献
GraphQL を RESTful API と比較しながら実装して理解する

クエリで指定されるフィールドごとにresolverという関数を定義し、対応するデータをDBなどのデータストア等から取得して返すようにする。

REST APIとの違い。例えば/post/a/post/b/post/cというリソースをリクエストしたいとする。REST APIではソース毎にHTTPリクエストを発行する必要がある。

  1. get /post/a
  2. get /post/b
  3. get /post/c

しかし、GraphQLでは/graphqlに問い合わせてJSONファイルとして一括でリクエストできる。

  1. /graphql {a, b, c}

必要なデータだけを単純なクエリ発行で取得できるので効率的。

気になった用語

エンドポイント
APIにアクセスするためのURIを指す。基本的にはURIがリソースを指し、URIとHTTPメソッドの組み合わせで処理の内容を表すのが良い設計であるとされている。REST APIではリソース毎にURLを指定してリクエストする必要があるが、GraphQL APIではエンドポイントが一つしかない上に一回のリクエストで複雑なデータ取得要求が可能。

クエリ Query
データの取得要求。GETメソッドのようなもの。

ミューテーション Mutation
データの更新要求。POSTメソッドのようなもの。

type Querytype Mutation の違い

参考文献
Apolloでの綺麗なAPI実装(GraphQL)を試す

データの取得にはQuery、データの更新にはMutationを使う。スキーマの定義の仕方は下記コード。

server.js

const typeDefs = `
  type Query {
    フィールド名(引数): 返却データ型
  }
  type Mutation {
    フィールド名(引数): 返却データ型
  }
}
`

// resolvers
const resolvers = {
  Query: {
    フィールド名: (引数) => 返却データ,
  },
  Mutation: {
    フィールド名: (引数) => 返却データ,
  },
}

データの基本型。

Int 32bit整数型
Float 浮動小数型
String UTF-8 文字列型
Boolean true もしくは false
ID ユニークなスカラー値、キャッシュに使われる。Stringをシリアライズ(直列化)したデータで保存される。

type Querytype Mutationのブロック内にメソッドを追加。複数メソッドを定義できる。コメントアウトは"""で囲む。

server.js
const typeDefs = `
  type Query { 
    books: [Book],
    items: [Item],
  }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
  type Item { title: String }
`

// resolvers
const resolvers = {
  Query: {
    books: () => books,
    items: () => items,
  },
}

とりあえずここまで情報収集したけどさっぱり理解できてない

ここまではGraphQLの雰囲気を掴んでおこうという趣旨。他にも公式ドキュメントや多くの記事を参考にしたけど、素人が理解できるほど優しい世界ではなかった。できればシンプルなコードで順を追ってコーディングしていけば動作するアプリが作れる日本語の教材が欲しいところ。いくらググって考えても理解できないから贅沢言うしかないのである。

謎が解けていったときの勉強過程の覚書

しばらくはGraphQLに入門したくてもなかなか理解できずに入り口でウロウロさせられていた。一体何が悩みかと言うと、Queryでデータ要求とか、Mutationでデータ更新とか、どこかにあるオブジェクトデータ群に対してリクエストするんだということは何となく分かっていたけど、GraphQLを介してリクエストを出すというイメージが全然浮かんでこなくて、結局アプリの中でGraphQLを起動するにはどうすんの、と思っていた。オブジェクトデータ群は外部ファイルやDBに書いておいてインポートしたりすればいいし、Queryの出し方も普通に途中にコードを書けばいいし、スキーマもそのそばに書けばいい。しかし、GraphQLの起動はどのコードが発端なんだ、Expressがないといけないのか、どうなんだ。

しばらくは下記の記事を参考に模索。apollo-server-expressというモジュールを使ってるらしい。ググってると「apollo」という単語は何度も見かけたけど、これが必須なのかいわゆる便利ツールなのかはまだよく分からん。プレーンな状態でGraphQLを動かす方法とかはそのうち全貌が分かってくることに期待。

参考文献
Apolloでの綺麗なAPI実装(GraphQL)を試す

この記事では以下のモジュールを使っている。

  • apollo-server-express
  • graphql-tools
  • graphql
  • express
  • body-parser
$ npm install --save apollo-server-express graphql-tools graphql express body-parser

この記事のコードは下記。コメントアウトのおかげでどの部分が何を意味するのかわかりやすかった。しかしGraphQLへの接続のあたりがやっぱりよく分からなかった。

server.js
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const app = express()

process.on('uncaughtException', (err) => console.error(err))
process.on('unhandledRejection', (err) => console.error(err))

app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())


// GraphQLスキーマ定義
const typeDefs = `
  """
  type Query (必須)
  """
  type Query { books: [Book] }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
`

// ダミーデータ
const books = [
  {
    title: 'Harry Potter and the Sorcerer\'s stone',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
]

// resolvers
const resolvers = {
  Query: { books: () => books },
}

// スキーマ生成
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

// GraphQLエンドポイント
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema }))

// GraphiQL:GraphQLクエリのvisual editor
// TODO: 本番デプロイ時はアクセス出来ないようにする
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

app.listen(5000, () => {
  console.log('Access to http://localhost:5000')
})

STEP 1 もう少しシンプルなサンプルコードで勉強したいと思った

このコードを解読しようといろいろ調べたけど決定打はなかった。そこでapolloとexpressに絞ってググるとこの記事に行き着いた。以降、この2つの記事のコードを比較しながら勉強していく。

参考文献
Apollo+Expressで始めるGraphQL超入門 ~ GraphQLをざっくり理解する

この記事で使うモジュールは以下。さきほどの記事よりインストールする数が少なくてうれしい。

  • express
  • apollo-server-express
  • graphql
$ $ npm i --save express apollo-server-express graphql

この記事のコードを参考にする。

app.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);

とりあえずコピペしてサーバー起動すれば動く状態ではある。

$ node app.js

そしてここであることに気づく。

STEP 2 エンドポイント/graphqlってどゆこと?

REST APIはリソースをリクエストするたびにそのURL(エンドポイント)にアクセスする必要があるというのはどこを見ても書かれていることだけど、GraphQLのエンドポイントは/graphqlの一つしかないという意味がよく分からなかった。でもテストアプリを走らせてhttp://localhost:4000/graphqlにアクセスするとGraphQLのビジュアルエディタが開かれた。なるほど、GraphQLのAPIが動作するURLが/graphqlで、リクエストをここのアプリで捌いていっただけだったのか。プログラムとして動作するのは当たり前だけど、ビジュアルエディタとして手動で操作することもできるというわけだった。

スクリーンショット 2019-12-29 22.11.16.png

STE 3 ちなみにbody-parserについて

一つ目の参考記事と、二つ目の参考記事では、インストールするモジュールの数に差があった。一つ目の記事では以下のモジュールを多くインストールしていた。

  • graphql-tools
  • body-parser

一つ目の記事のサンプルコード中にあるbodyParser.json()という記述は、Express4.xでは外部ミドルウェアだったものを標準搭載してexpress.json()で仕様できるらしい。ググっていろんなバージョンやいろんな方針でコーディングしている記事があるけど、一応どちらも使えるし同じ機能を実装しているということを把握しておきたい。ちなみに標準機能として実装しておけばconst bodyParser = require('body-parser')を宣言する必要がなくなって効率的。

というわけで二つ目の記事のやり方の方が最新情報に近いと判断して、そっちを参考に勉強してくことに。

参考文献
Body-ParserがExpressにexpress.json()として標準搭載されている話

STEP 4 ビジュアルエディタを使ってみる

いろいろスッキリしたところでGraphQlエディタを動かしてみた。{hello}を要求するとHello World!が返ってきた。

スクリーンショット 2019-12-29 22.50.40.png

このHello Worldはapp.jsresolversに書いておいたHello Worldが渡されてるだけ。試しに適当な名前のデータを要求したら「そんな名前のデータはねえ」と言われる。

スクリーンショット 2019-12-29 22.50.55.png

ちなみにエディタの右端についている「SCHEMA」タブを開くと要求したデータの型を定義したスキーマを自動生成だか読み込みだかしてくれている。

スクリーンショット 2019-12-29 23.25.26.png

STEP 5 オブジェクトデータ群を用意する方法

ではGraphQLでデータ要求する相手となるオブジェクトデータ群はどこに存在するのかと言うと、DBを実装するのはまだ難しいので、外部ファイルにオブジェクトを記述しておいてexportsすればいいとのこと。なるほどこれがデータ保管庫か。

とりあえず手本通りにusersというオブジェクトを作り、id name age created_dateという4つのkeyを定義した。このオブジェクトデータの構造は作りたいアプリによって自由に定義できる、と思う。

users.js
exports.users = [
  {
    id: 1,
    name: 'Zonomaa',
    age: 25,
    created_date: '2019-04-05 01:35:25.000'
  },
  {
    id: 2,
    name: 'Masa',
    age: 28,
    created_date: '2019-04-10 08:23:51.000'
  },
  {
    id: 3,
    name: 'Ms',
    age: 30,
    created_date: '2019-04-10 08:32:09.000'
  }
  // ... 適当にいくつか作ってみましょう
];

メインのapp.jsのファイルの冒頭でrequireを追記し、オブジェクトデータ群を記述したファイルを読み込んでおく。メインのapp.jsでこのファイルを読み込んでおき、app.jsの中でエンドポイントの/graphqlにリクエストすればデータを引っ張ってこれるというわけか。

app.js
const express = require('express');  // いつもの
const { ApolloServer, gql } = require('apollo-server-express');  // ここまだよく分からん
const { users } = require('./users');  // ここでオブジェクトデータ群を読み込み

へぇ、importじゃないんだ。importとrequireの使い分けまだよく分からん。Reactコンポーネントでよくimportを見かけるなぁ程度の知識しかない。クラスやメソッドはimportとexport? そしてファイルやモジュールはrequireとexports? あとで勉強しとこう。

参考文献
Apollo+Expressで始めるGraphQL超入門 ~ GraphQLをざっくり理解する

STE 6 スキーマを定義してデータ要求をカスタマイズ

データの用意の仕方、GraphQLの起動の仕方、この2つのイメージが掴めた。ここでGraphQLでどのようにデータを要求するかを考えていく。ここで要求するデータは先ほど作成したusersなので、そのオブジェクトデータのvalueの型を定義していく。app.jstypeDefsを編集する(この時点ではhelloがはいっているだけなので)。

app.js
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type User {
    id: Int
    name: String
    age: Int
    created_date: String
  }
  type Query {
    users: [User]
  }
`;

型の定義はtype User{}、データ要求はtype Query{}に書かれているのが分かる(よく分からんが)。ええと、つまりusers.jsに盛り込んでいるオブジェクトデータの型に合わせてtypeDefsの中で新たにtype User{}というオブジェクトの型を定義することでusers.jsから要求通りのデータの受け渡し時の作法を決定した、ということっぽい。そしてデータを要求するには「さっき定義した型の[User]で頼んます」とやっているっぽい。

これで要求するデータの型の定義のtypeDefsと、どのデータを要求するかのQueryが完成した。これでアプリが動くと思いきや、動かないらしい。参考記事の著者も驚いて演出してくれている。

STEP 7 resolversでデータ取得のロジックを定義する

一つ目の参考記事の中でもresolversとあって「これ何やねん」と悩んでいたけど、2つ目の参考記事でも登場した。どちらでもresolversについて触れているけど、1つ目の記事では具体的な仕様が、2つ目の記事では具体的なコーディングの順序が分かるような内容になっている。今はイメージが掴めてない状態なので2つ目の記事がありがたい。

GraphQLでは、データ取得のロジック(実際に処理を発火させる関数のこと?)は手動で記述するのがことになっているとのこと。普通は自動でやるものなのか? とにかくここでやるべきは、resolversの中のQueryusersをreturnする関数書く。このusersはさっきtype Queryで定義したやつ。

app.js
const resolvers = {
  Query: {
    users: () => users
  },
};

つまり、typeDefsではtype Userでデータの型を定義し、type Queryで要求するデータを指定する。そして、resolversではQueryでデータ取得の処理を記述する。これがGraphQLによるデータ要求の一連の流れになる。

STEP 8 いろいろ自作したアプリでGraphQLの動作を確認する方法

ここでアプリ起動。

$ node app.js

そしてhttp://localhost:4000/graphqlにアクセスしてエディタを開く。そこでクエリを発行する。要は欲しいデータを書いて要求するということ。例えばこう。

{
  users {
    id
    name
  }
}

さっき定義してexportsしておいたusersというデータ群から、Userで定義したデータ型で、id name の2つのkeyのデータを要求する、という意味になる。

スクリーンショット 2019-12-30 0.04.04.png

動いた。お見事!!めっちゃスッキリした。
今回の勉強で分かったことはこんな感じ。

  • インストールが必要なnodeモジュールの種類はとりあえず3つでいい
  • GraphQLにアクセスする「エンドポイント」とはエディタが起動するURLだった
  • オブジェクトデータ群は外部ファイルに書いてrequireしとけばGraphQLのエンドポイントからアクセスできる
  • typeDefsの中で、データ型のtype objNameと、要求するデータのtype Queryを定義
  • resolversの中で、データ取得処理のQueryを定義
  • 今回はエディタでリクエスト出したけど実際のアプリではどうすんの?

基礎をすっ飛ばして知識0からの勉強は大変すぎるという愚痴

こういう外部データの読み込みとか、APIへの接続とか、普通のエンジニアなら当たり前のように理解して、説明を省略した記事でも読み解くことができるのだろうけど、全くの素人からするとコードの1行1行の相関関係がわからないので理解に到ることのできる記事というのがなかなか見つからない。今回参考にした記事はとてもわかりやすくて助けられた。おかげでWebアプリでモジュールやらJSONデータを活用する作法というものが少し見えてきた。

今回学んだことを取り入れて自分が作りたいものに活かすにはどうすべきか。Next.jsでブログシステムを作ろうとしているが、記事情報の取得はGraphQLでどうにかできそうだ。記事はmdファイルで管理し、記事情報はfrontmatterに記述しているので、おそらく記事のmdファイルが入ったディレクトリ以下をrequireしておき、/graphqlからアクセスしてfrontmatter.idとかで取得できる。うん、多分いける。

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

8
4
0

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
8
4