GraphQLとREST-GraphQLチュートリアル
公開: 2022-03-11ブロックの周りの新しい子供、GraphQLについて聞いたことがあるかもしれません。 そうでない場合、GraphQLは、一言で言えば、APIをフェッチする新しい方法であり、RESTの代替手段です。 Facebookの内部プロジェクトとして始まり、オープンソースであったため、多くの注目を集めています。
この記事の目的は、すでにGraphQLを考えている場合でも、試してみたい場合でも、RESTからGraphQLに簡単に移行できるようにすることです。 GraphQLの予備知識は必要ありませんが、記事を理解するには、RESTAPIにある程度精通している必要があります。
記事の最初の部分は、GraphQLがRESTよりも優れていると私が個人的に考える3つの理由を説明することから始めます。 2番目の部分は、バックエンドにGraphQLエンドポイントを追加する方法に関するチュートリアルです。
GraphqlとREST:なぜRESTを削除するのですか?
GraphQLがニーズに適しているかどうかまだ迷っている場合は、「RESTとGraphQL」の非常に広範囲で客観的な概要をここに示します。 ただし、GraphQLを使用する理由の上位3つについては、読み進めてください。
理由1:ネットワークパフォーマンス
バックエンドに、名、姓、電子メール、およびその他の10個のフィールドを持つユーザーリソースがあるとします。 クライアントでは、通常、それらのうちの2つだけが必要です。
/users
エンドポイントでREST呼び出しを行うと、ユーザーのすべてのフィールドが返され、クライアントは必要なフィールドのみを使用します。 明らかに、データ転送の無駄がいくらかあります。これは、モバイルクライアントで考慮される可能性があります。
GraphQLはデフォルトで、可能な限り最小のデータをフェッチします。 ユーザーの名前と名前だけが必要な場合は、クエリでその名前を指定します。
以下のインターフェースはGraphiQLと呼ばれ、GraphQLのAPIエクスプローラーのようなものです。 この記事の目的のために小さなプロジェクトを作成しました。 コードはGitHubでホストされており、第2部で詳しく説明します。
インターフェイスの左側のペインにはクエリがあります。 ここでは、すべてのユーザーをフェッチし(RESTを使用してGET /users
を実行します)、名前と名前のみを取得します。
クエリ
query { users { firstname lastname } }
結果
{ "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }
メールも取得したい場合は、「lastname」の下に「email」行を追加するとうまくいきます。
一部のRESTバックエンドは、部分的なリソースを返すために/users?fields=firstname,lastname
のオプションを提供します。 その価値については、Googleがお勧めします。 ただし、デフォルトでは実装されておらず、特に他のクエリパラメータを入力すると、リクエストがほとんど読めなくなります。
-
&status=active
は、アクティブなユーザーをフィルタリングします &sort=createdAat
を使用して、ユーザーを作成日で並べ替えます&sortDirection=desc
明らかに必要なので、-
&include=projects
は、ユーザーのプロジェクトを含めます
これらのクエリパラメータは、クエリ言語を模倣するためにRESTAPIに追加されたパッチです。 GraphQLは何よりもクエリ言語であり、最初からリクエストを簡潔かつ正確にします。
理由2:「インクルードとエンドポイント」の設計上の選択
簡単なプロジェクト管理ツールを構築したいとします。 ユーザー、プロジェクト、タスクの3つのリソースがあります。 また、リソース間の次の関係を定義します。
これが私たちが世界に公開するエンドポイントのいくつかです:
終点 | 説明 |
---|---|
GET /users | すべてのユーザーを一覧表示 |
GET /users/:id | id:idのシングルユーザーを取得します |
GET /users/:id/projects | 1人のユーザーのすべてのプロジェクトを取得 |
エンドポイントはシンプルで読みやすく、よく整理されています。
リクエストがより複雑になると、事態はさらに複雑になります。 GET /users/:id/projects
エンドポイントを見てみましょう。ホームページにはプロジェクトのタイトルのみを表示し、ダッシュボードにはプロジェクトとタスクを表示し、複数のREST呼び出しを行わないとします。 私は電話します:
- ホームページの
GET /users/:id/projects
を取得します。 - ダッシュボードページで
GET /users/:id/projects?include=tasks
(たとえば)を取得して、バックエンドが関連するすべてのタスクを追加するようにします。
これを機能させるには、クエリパラメータ?include=...
を追加するのが一般的であり、JSONAPI仕様でも推奨されています。 ?include=tasks
のようなクエリパラメータはまだ読み取り可能ですが?include=tasks,tasks.owner,tasks.comments,tasks.comments.author
ます。
この場合、これを行うために/projects
エンドポイントを作成する方が賢明でしょうか? /projects?userId=:id&include=tasks
のようなものです。含める関係のレベルが1つ少ないのでしょうか。 または、実際には、 /tasks?userId=:id
エンドポイントも機能する可能性があります。 これは設計上の選択が難しい場合があり、多対多の関係がある場合はさらに複雑になります。
GraphQLは、あらゆる場所でinclude
アプローチを使用します。 これにより、関係をフェッチするための構文が強力で一貫性のあるものになります。
これは、ID1のユーザーからすべてのプロジェクトとタスクをフェッチする例です。
クエリ
{ user(id: 1) { projects { name tasks { description } } } }
結果
{ "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }
ご覧のとおり、クエリ構文は簡単に読み取ることができます。 さらに深く掘り下げて、タスク、コメント、写真、作成者を含めたい場合は、APIを整理する方法について2度考えません。 GraphQLを使用すると、複雑なオブジェクトを簡単にフェッチできます。
理由3:さまざまなタイプのクライアントの管理
バックエンドを構築するときは、常にAPIをすべてのクライアントができるだけ広く使用できるようにすることから始めます。 それでも、クライアントは常に呼び出しを減らしてフェッチを増やしたいと考えています。 深いインクルード、部分的なリソース、およびフィルタリングを使用すると、Webクライアントとモバイルクライアントによって行われる要求は、互いに大きく異なる可能性があります。
RESTには、いくつかの解決策があります。 カスタムエンドポイント(つまり、 /mobile_user
などのエイリアスエンドポイント)、カスタム表現( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
)、さらにはクライアントを作成できます-特定のAPI(Netflixがかつて行ったように)。 これら3つはすべて、バックエンド開発チームによる追加の作業が必要です。
GraphQLは、クライアントにより多くのパワーを提供します。 クライアントが複雑なリクエストを必要とする場合、対応するクエリ自体を構築します。 各クライアントは、同じAPIを異なる方法で使用できます。
GraphQLの開始方法
今日の「GraphQLvs。REST」に関するほとんどの議論では、人々は2つのうちのいずれかを選択する必要があると考えています。 これは単に真実ではありません。
最近のアプリケーションは通常、いくつかのAPIを公開するいくつかの異なるサービスを使用します。 実際、GraphQLは、これらすべてのサービスへのゲートウェイまたはラッパーと考えることができます。 すべてのクライアントはGraphQLエンドポイントにヒットし、このエンドポイントはデータベースレイヤー、ElasticSearchやSendgridなどの外部サービス、またはその他のRESTエンドポイントにヒットします。
両方を使用する2番目の方法は、RESTAPIに個別の/graphql
エンドポイントを設定することです。 これは、REST APIにアクセスするクライアントがすでに多数あるが、既存のインフラストラクチャを損なうことなくGraphQLを試してみたい場合に特に便利です。 そして、これが私たちが今日探求しているソリューションです。
前に述べたように、GitHubで入手できる小さなサンプルプロジェクトを使用してこのチュートリアルを説明します。 これは、ユーザー、プロジェクト、およびタスクを備えた単純化されたプロジェクト管理ツールです。
このプロジェクトで使用されるテクノロジーは、Webサーバー用のNode.jsとExpress、リレーショナルデータベースとしてのSQLite、およびORMとしてのSequelizeです。 ユーザー、プロジェクト、タスクの3つのモデルは、 models
フォルダーで定義されています。 RESTエンドポイント/api/users
、 /api/projects
、および/api/tasks
はワールドに公開され、 rest
フォルダーで定義されます。
GraphQLは、任意のプログラミング言語を使用して、任意のタイプのバックエンドおよびデータベースにインストールできることに注意してください。 ここで使用されるテクノロジーは、単純さと読みやすさのために選択されています。
私たちの目標は、RESTエンドポイントを削除せずに/graphql
エンドポイントを作成することです。 GraphQLエンドポイントはデータベースORMに直接ヒットしてデータをフェッチするため、RESTロジックから完全に独立しています。
タイプ
データモデルは、強く型付けされた型によってGraphQLで表されます。 モデルとGraphQLタイプの間には1対1のマッピングが必要です。 User
タイプは次のようになります。
type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }
クエリ
クエリは、GraphQLAPIで実行できるクエリを定義します。 慣例により、既存のすべてのクエリを含むRootQuery
が必要です。 また、各クエリに相当するRESTについても指摘しました。

type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }
突然変異
クエリがGET
リクエストの場合、ミューテーションはPOST
/ PATCH
/ PUT
/ DELETE
リクエストと見なすことができます(実際には、クエリの同期バージョンですが)。
慣例により、すべてのミューテーションをRootMutation
に配置します。
type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }
ここでは、 UserInput
、 ProjectInput
、およびTaskInput
と呼ばれる新しいタイプを導入したことに注意してください。 これは、リソースを作成および更新するための入力データモデルを作成するための、RESTでも一般的な方法です。 ここで、 UserInput
タイプは、 id
フィールドとprojects
フィールドのないUser
タイプであり、 type
の代わりにキーワードinput
に注意してください。
input UserInput { firstname: String lastname: String email: String }
スキーマ
タイプ、クエリ、ミューテーションを使用して、 GraphQLスキーマを定義します。これは、GraphQLエンドポイントが世界に公開するものです。
schema { query: RootQuery mutation: RootMutation }
このスキーマは強く型付けされており、GraphiQLでこれらの便利なオートコンプリートを使用できるようになりました。
レゾルバ
パブリックスキーマができたので、これらのクエリ/ミューテーションのそれぞれが要求されたときに何をすべきかをGraphQLに指示します。 リゾルバーは大変な作業を行います。 たとえば、次のことができます。
- 内部RESTエンドポイントにヒット
- マイクロサービスを呼び出す
- データベースレイヤーをヒットしてCRUD操作を実行します
サンプルアプリでは3番目のオプションを選択しています。 リゾルバーファイルを見てみましょう。
const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }
つまり、GraphQLでuser(id: ID!)
クエリが要求された場合、データベースからSequelize ORM関数であるUser.findById()
を返します。
リクエストに他のモデルを参加させるのはどうですか? さて、より多くのリゾルバーを定義する必要があります。
User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */
したがって、GraphQLのUser
タイプでprojects
フィールドをリクエストすると、この結合がデータベースクエリに追加されます。
そして最後に、ミューテーションのリゾルバー:
RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }
ここでこれをいじることができます。 サーバー上のデータをクリーンに保つために、ミューテーションのリゾルバーを無効にしました。つまり、ミューテーションはデータベースで作成、更新、または削除操作を実行しません(したがって、インターフェイスでnull
を返します)。
クエリ
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }
結果
{ "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }
既存のアプリのすべてのタイプ、クエリ、リゾルバーを書き直すには時間がかかる場合があります。 ただし、役立つツールはたくさんあります。 たとえば、リゾルバーなど、SQLスキーマをGraphQLスキーマに変換するツールがあります。
すべてをまとめる
明確に定義されたスキーマとスキーマの各クエリで実行するリゾルバーを使用して、バックエンドに/graphql
エンドポイントをマウントできます。
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));
また、バックエンドに見栄えの良いGraphiQLインターフェイスを設定できます。 GraphiQLを使用せずにリクエストを行うには、リクエストのURLをコピーし、cURL、AJAXを使用して、またはブラウザーで直接実行します。 もちろん、これらのクエリの構築に役立つGraphQLクライアントがいくつかあります。 いくつかの例については、以下を参照してください。
次は何ですか?
この記事の目的は、GraphQLがどのように見えるかを味わい、RESTインフラストラクチャを破棄せずにGraphQLを試すことが間違いなく可能であることを示すことです。 GraphQLがニーズに合っているかどうかを知る最良の方法は、自分で試すことです。 この記事があなたにダイビングをしてくれることを願っています。
リアルタイム更新、サーバー側のバッチ処理、認証、承認、クライアント側のキャッシュ、ファイルのアップロードなど、この記事では説明していない機能がたくさんあります。これらの機能について学ぶための優れたリソースGraphQLの方法です。
以下は、その他の役立つリソースです。
サーバー側ツール | 説明 |
---|---|
graphql-js | GraphQLのリファレンス実装。 これをexpress-graphql とともに使用して、サーバーを作成できます。 |
graphql-server | Apolloチームによって作成されたオールインワンのGraphQLサーバー。 |
他のプラットフォームの実装 | Ruby、PHPなど。 |
クライアント側ツール | 説明 |
---|---|
リレー | ReactをGraphQLに接続するためのフレームワーク。 |
apollo-client。 | React、Angular 2、およびその他のフロントエンドフレームワーク用のバインディングを備えたGraphQLクライアント。 |
結論として、GraphQLは誇大広告以上のものであると私は信じています。 明日はまだRESTに取って代わることはありませんが、真の問題に対するパフォーマンスの高いソリューションを提供します。 これは比較的新しく、ベストプラクティスはまだ開発中ですが、これは間違いなく、今後2、3年で耳にするテクノロジーです。