ブロックの周りの新しい子供、GraphQLについて聞いたことがあるかもしれません。そうでない場合、GraphQLは一言で言えば、フェッチするための新しい方法です API 、RESTの代替。 Facebookの内部プロジェクトとして始まり、オープンソース化されて以来、 たくさんの牽引力 。
この記事の目的は、すでにGraphQLを考えている場合でも、試してみたい場合でも、RESTからGraphQLに簡単に移行できるようにすることです。 GraphQLの予備知識は必要ありませんが、記事を理解するには、RESTAPIにある程度精通している必要があります。
記事の最初の部分は、私が個人的にGraphQLがRESTより優れていると思う3つの理由を説明することから始めます。 2番目の部分は、バックエンドにGraphQLエンドポイントを追加する方法に関するチュートリアルです。
GraphQLがニーズに適しているかどうかまだ迷っている場合は、「RESTとGraphQL」の非常に広範囲で客観的な概要を示します。 ここに 。ただし、GraphQLを使用する理由の上位3つについては、読み進めてください。
バックエンドに、名、名前、電子メール、およびその他の10個のフィールドを持つユーザーリソースがあるとします。クライアントでは、通常、それらのうちの2つだけが必要です。
/users
でREST呼び出しを行うエンドポイントはユーザーのすべてのフィールドを返し、クライアントは必要なフィールドのみを使用します。明らかに、データ転送の無駄がいくらかあります。これは、モバイルクライアントで考慮される可能性があります。
GraphQLはデフォルトで、可能な限り最小のデータをフェッチします。ユーザーの名前と名前だけが必要な場合は、クエリでそれを指定します。
以下のインターフェースはGraphiQLと呼ばれ、GraphQLのAPIエクスプローラーのようなものです。この記事の目的のために小さなプロジェクトを作成しました。コードはホストされています GitHubで 、そして第2部で詳しく説明します。
サーバー側のレンダリングとクライアント側のレンダリング
インターフェイスの左側のペインにはクエリがあります。ここでは、すべてのユーザーを取得しています。GET /users
を実行します。 RESTを使用して、名前と名前のみを取得します。
クエリ
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は何よりもクエリ言語であり、最初からリクエストを簡潔かつ正確にします。
簡単なプロジェクト管理ツールを構築したいとします。ユーザー、プロジェクト、タスクの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=...
これを機能させるために、そしてによってさえ推奨されています JSON API 仕様。 ?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を使用すると、複雑なオブジェクトを簡単にフェッチできます。
バックエンドを構築するとき、私たちは常にAPIをすべてのクライアントができるだけ広く使用できるようにすることから始めます。それでも、クライアントは常に呼び出しを減らしてフェッチを増やしたいと考えています。深いインクルード、部分的なリソース、およびフィルタリングにより、Webクライアントとモバイルクライアントによって行われる要求は、互いに大きく異なる可能性があります。
RESTには、いくつかの解決策があります。カスタムエンドポイント(つまり、エイリアスエンドポイント、たとえば/mobile_user
)、カスタム表現(Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
)、またはクライアント固有のAPI( Netflixはかつて )。これら3つはすべて、バックエンド開発チームによる追加の作業が必要です。
GraphQLは、クライアントにより多くのパワーを提供します。クライアントが複雑なリクエストを必要とする場合、対応するクエリ自体を構築します。各クライアントは、同じAPIを異なる方法で使用できます。
今日の「GraphQLvs。REST」に関するほとんどの議論では、人々は2つのうちのいずれかを選択する必要があると考えています。これは単に真実ではありません。
最近のアプリケーションは通常、いくつかのAPIを公開するいくつかの異なるサービスを使用します。実際、GraphQLは、これらすべてのサービスへのゲートウェイまたはラッパーと考えることができます。すべてのクライアントはGraphQLエンドポイントにヒットし、このエンドポイントはデータベースレイヤー、ElasticSearchやSendgridなどの外部サービス、またはその他のRESTエンドポイントにヒットします。
両方を使用する2番目の方法は、個別の/graphql
を使用することです。 RESTAPIのエンドポイント。これは、REST APIにアクセスするクライアントがすでに多数あるが、既存のインフラストラクチャを損なうことなくGraphQLを試してみたい場合に特に便利です。そして、これが私たちが今日探求しているソリューションです。
サーバー側のWebアプリケーションを保護することが難しい理由ではないのはどれですか?
前に述べたように、私はこのチュートリアルを利用可能な小さなサンプルプロジェクトで説明します GitHubで 。これは、ユーザー、プロジェクト、およびタスクを備えた単純化されたプロジェクト管理ツールです。
このプロジェクトで使用されるテクノロジーは、Webサーバー用のNode.jsとExpress、リレーショナルデータベースとしてのSQLite、およびORMとしてのSequelizeです。ユーザー、プロジェクト、タスクの3つのモデルは、 models
フォルダ。 RESTエンドポイント /api/users
、 /api/projects
そして /api/tasks
世界にさらされており、 rest
フォルダ。
GraphQLは、任意のプログラミング言語を使用して、任意のタイプのバックエンドおよびデータベースにインストールできることに注意してください。ここで使用されているテクノロジーは、単純さと読みやすさのために選択されています。
私たちの目標は/graphql
を作成することですRESTエンドポイントを削除せずにエンドポイント。 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
タイプは私たちのUser
id
なしで入力しますおよびprojects
フィールド、およびキーワードinput
に注意してくださいtype
の代わりに:
input UserInput { firstname: String lastname: String email: String }
タイプ、クエリ、ミューテーションを使用して、 GraphQLスキーマ 、これはGraphQLエンドポイントが世界に公開するものです。
schema { query: RootQuery mutation: RootMutation }
このスキーマは強く型付けされており、これらの便利なオートコンプリートを使用できるようになりました。 GraphiQL 。
パブリックスキーマができたので、これらのクエリ/ミューテーションのそれぞれが要求されたときに何をすべきかをGraphQLに指示します。 リゾルバ 大変な仕事をします。たとえば、次のことができます。
サンプルアプリでは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 }
これは、user(id: ID!)
の場合を意味しますGraphQLでクエリが要求されると、データベースからSequelizeORM関数である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 } */
したがって、projects
を要求するとUser
のフィールドGraphQLと入力すると、この結合がデータベースクエリに追加されます。
そして最後に、突然変異のリゾルバ:
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クライアントがいくつかあります。いくつかの例については、以下を参照してください。
資本予算は1つです。
この記事の目的は、GraphQLがどのように見えるかを味わい、RESTインフラストラクチャを破棄せずにGraphQLを試すことが間違いなく可能であることを示すことです。 GraphQLがニーズに合っているかどうかを知る最良の方法は、自分で試すことです。この記事があなたにダイビングをしてくれることを願っています。
リアルタイム更新、サーバー側のバッチ処理、認証、承認、クライアント側のキャッシュ、ファイルのアップロードなど、この記事では説明していない機能がたくさんあります。これらの機能について学ぶための優れたリソースは GraphQLの方法 。
以下は、その他の役立つリソースです。
サーバー側ツール | 説明 |
---|---|
graphql-js | GraphQLのリファレンス実装。 express-graphql で使用できますサーバーを作成します。 |
graphql-server | によって作成されたオールインワンGraphQLサーバー アポロチーム 。 |
他のプラットフォームの実装 | Ruby、PHPなど。 |
クライアント側ツール | 説明 |
---|---|
リレー | ReactをGraphQLに接続するためのフレームワーク。 |
apollo-client 。 | のバインディングを持つGraphQLクライアント React 、 Angular 2 、および 他のフロントエンドフレームワーク 。 |
結論として、GraphQLは誇大広告以上のものであると私は信じています。明日はまだRESTに取って代わることはありませんが、真の問題に対するパフォーマンスの高いソリューションを提供します。これは比較的新しく、ベストプラクティスはまだ開発中ですが、これは間違いなく、今後2、3年で耳にするテクノロジーです。
GraphQLはクエリ言語であり、RESTの代替手段です。 Facebookの内部プロジェクトとして始まりました。
GraphQLは、すべてのRESTfulエンドポイントを1つにマージし、使用するネットワークトラフィックを減らします。