GraphQLとREST-GraphQLチュートリアル

公開: 2022-03-11

ブロックの周りの新しい子供、GraphQLについて聞いたことがあるかもしれません。 そうでない場合、GraphQLは、一言で言えば、APIをフェッチする新しい方法であり、RESTの代替手段です。 Facebookの内部プロジェクトとして始まり、オープンソースであったため、多くの注目を集めています。

この記事の目的は、すでにGraphQLを考えている場合でも、試してみたい場合でも、RESTからGraphQLに簡単に移行できるようにすることです。 GraphQLの予備知識は必要ありませんが、記事を理解するには、RESTAPIにある程度精通している必要があります。

GraphQLとREST-GraphQLチュートリアル

記事の最初の部分は、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エンドポイントにヒットします。

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

ここでは、 UserInputProjectInput 、および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年で耳にするテクノロジーです。