数年前、FacebookはGraphQLと呼ばれるバックエンドAPIを構築する新しい方法を導入しました。これは基本的に、データのクエリと操作のためのドメイン固有言語です。最初はあまり注意を払っていませんでしたが、最終的にはApeeScapeでプロジェクトに参加し、GraphQLに基づいてバックエンドAPIを実装する必要がありました。そのとき、RESTで学んだ知識をGraphQLに適用する方法を学びました。
これは非常に興味深い経験であり、実装期間中に、RESTAPIで使用される標準的なアプローチと方法論をよりGraphQLに適した方法で再考する必要がありました。この記事では、GraphQLAPIを初めて実装するときに考慮すべき一般的な問題を要約しようとしています。
GraphQLはFacebookによって社内で開発され、2015年に公開されました。2018年の後半に、GraphQLプロジェクトはFacebookから新しく設立されたプロジェクトに移されました。 GraphQL Foundation 、非営利のLinux Foundationによってホストされており、 GraphQLクエリ言語仕様 そして JavaScriptのリファレンス実装 。
GraphQLはまだ若いテクノロジーであり、JavaScriptの最初のリファレンス実装が利用可能であったため、そのための最も成熟したライブラリはNode.jsエコシステムに存在します。他に2つの会社があります。 アポロ そして プリズム 、GraphQL用のオープンソースツールとライブラリを提供します。この記事のサンプルプロジェクトは、JavaScript用のGraphQLのリファレンス実装と、次の2つの会社が提供するライブラリに基づいています。
GraphQLの世界では、GraphQLスキーマを使用してAPIを記述します。これらについては、仕様でGraphQLスキーマ定義言語(SDL)と呼ばれる独自の言語が定義されています。 SDLは非常にシンプルで直感的に使用できると同時に、非常に強力で表現力豊かです。
GraphQLスキーマを作成するには、コードファーストアプローチとスキーマファーストアプローチの2つの方法があります。
個人的に、私はスキーマファーストのアプローチを好み、それを サンプルプロジェクト 記事上で。古典的な書店の例を実装し、著者と本を作成するためのCRUD APIに加えて、ユーザー管理と認証のためのAPIを提供するバックエンドを作成します。
基本的なGraphQLサーバーを実行するには、新しいプロジェクトを作成し、npmで初期化して、Babelを構成する必要があります。 Babelを設定するには、最初に次のコマンドを使用して必要なライブラリをインストールします。
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
Babelをインストールした後、.babelrc
という名前のファイルを作成します。プロジェクトのルートディレクトリに、次の構成をコピーします。
{ 'presets': [ [ '@babel/env', { 'targets': { 'node': 'current' } } ] ] }
package.json
も編集しますファイルを作成し、次のコマンドをscripts
に追加します。セクション:
{ ... 'scripts': { 'serve': 'babel-node index.js' }, ... }
Babelを構成したら、次のコマンドを使用して必要なGraphQLライブラリをインストールします。
npm install --save express apollo-server-express graphql graphql-tools graphql-tag
必要なライブラリをインストールした後、最小限のセットアップでGraphQLサーバーを実行するには、このコードスニペットをindex.js
にコピーします。ファイル:
import gql from 'graphql-tag'; import express from 'express'; import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'; const port = process.env.PORT || 8080; // Define APIs using GraphQL SDL const typeDefs = gql` type Query { sayHello(name: String!): String! } type Mutation { sayHello(name: String!): String! } `; // Define resolvers map for API definitions in SDL const resolvers = { Query: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } }, Mutation: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } } }; // Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolvers maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); });
この後、コマンドnpm run serve
を使用してサーバーを実行できます。また、WebブラウザーでURLに移動すると http://localhost:8080/graphql
、Playgroundと呼ばれるGraphQLのインタラクティブなビジュアルシェルが開き、GraphQLクエリとミューテーションを実行して結果データを確認できます。
GraphQLの世界では、API関数は、クエリ、ミューテーション、サブスクリプションと呼ばれる3つのセットに分けられます。
この記事では、クエリとミューテーションについてのみ説明します。サブスクリプションは大きなトピックです。サブスクリプションは独自の記事に値するものであり、すべてのAPI実装で必要なわけではありません。
GraphQLを試してみるとすぐに、SDLはプリミティブデータ型のみを提供し、すべてのAPIの重要な部分であるDate、Time、DateTimeなどの高度なスカラーデータ型が欠落していることがわかります。幸い、この問題を解決するのに役立つライブラリがあります。 graphql-iso-date 。インストール後、スキーマに新しい高度なスカラーデータ型を定義し、ライブラリによって提供される実装に接続する必要があります。
import { GraphQLDate, GraphQLDateTime, GraphQLTime } from 'graphql-iso-date'; // Define APIs using GraphQL SDL const typeDefs = gql` scalar Date scalar Time scalar DateTime type Query { sayHello(name: String!): String! } type Mutation { sayHello(name: String!): String! } `; // Define resolvers map for API definitions in SDL const resolvers = { Date: GraphQLDate, Time: GraphQLTime, DateTime: GraphQLDateTime, Query: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } }, Mutation: { sayHello: (obj, args, context, info) => { return `Hello ${ args.name }!`; } } };
日付と時刻に加えて、他の興味深いスカラーデータ型の実装もあります。これは、ユースケースに応じて役立つ場合があります。たとえば、そのうちの1つは graphql-type-json 、これにより、GraphQLスキーマで動的型付けを使用し、APIを使用して型指定されていないJSONオブジェクトを渡したり返したりすることができます。ライブラリもあります graphql-スカラー 、高度なサニタイズ/検証/変換を使用してカスタムGraphQLスカラーを定義する機能を提供します。
必要に応じて、上記のように、カスタムスカラーデータ型を定義してスキーマで使用することもできます。これは難しいことではありませんが、それについての説明はこの記事の範囲外です。興味がある場合は、 Apolloドキュメント 。
スキーマに機能を追加すると、スキーマが拡大し始め、定義のセット全体を1つのファイルに保持することは不可能であることがわかります。コードを整理し、よりスケーラブルにするために、スキーマを小さな部分に分割する必要があります。より大きなサイズ。幸い、Apolloが提供するスキーマビルダー関数makeExecutableSchema
は、配列の形式でスキーマ定義とリゾルバーマップも受け入れます。これにより、スキーマとリゾルバーマップをより小さな部分に分割することができます。これはまさに私のサンプルプロジェクトで行ったことです。 APIを次の部分に分割しました。
auth.api.graphql
–ユーザー認証と登録のためのAPIauthor.api.graphql
–作成者エントリ用のCRUD APIbook.api.graphql
–本のエントリ用のCRUD APIroot.api.graphql
–スキーマのルートと一般的な定義(高度なスカラー型など)user.api.graphql
–ユーザー管理用のCRUD API分割スキーマでは、考慮しなければならないことが1つあります。パーツの1つはルートスキーマである必要があり、他のパーツはルートスキーマを拡張する必要があります。これは複雑に聞こえますが、実際には非常に単純です。ルートスキーマでは、クエリとミューテーションは次のように定義されています。
type Query { ... } type Mutation { ... }
そして他のものでは、それらは次のように定義されています:
extend type Query { ... } extend type Mutation { ... }
そしてそれがすべてです。
API実装の大部分では、グローバルアクセスを制限し、ある種のルールベースのアクセスポリシーを提供する必要があります。このために、コードで導入する必要があります。 認証 —ユーザーIDを確認するため—および 承認 、ルールベースのアクセスポリシーを適用します。
GraphQLの世界では、RESTの世界と同様に、一般的に認証に使用します JSONWebトークン 。渡されたJWTトークンを検証するには、すべての着信リクエストをインターセプトし、それらの認証ヘッダーを確認する必要があります。このため、Apolloサーバーの作成中に、関数をコンテキストフックとして登録できます。このフックは、すべてのリゾルバー間で共有されるコンテキストを作成する現在のリクエストで呼び出されます。これは次のように実行できます。
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, context: ({ req, res }) => { const context = {}; // Verify jwt token const parts = req.headers.authorization ? req.headers.authorization.split(' ') : ['']; const token = parts.length === 2 && parts[0].toLowerCase() === 'bearer' ? parts[1] : undefined; context.authUser = token ? verify(token) : undefined; return context; } }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); });
ここで、ユーザーが正しいJWTトークンを渡す場合、それを検証し、ユーザーオブジェクトをコンテキストに保存します。これは、リクエストの実行中にすべてのリゾルバーがアクセスできるようになります。
ユーザーIDを確認しましたが、APIは引き続きグローバルにアクセス可能であり、ユーザーが許可なく呼び出すことを妨げるものは何もありません。これを防ぐ1つの方法は、すべてのリゾルバーでコンテキスト内のユーザーオブジェクトを直接チェックすることですが、ボイラープレートコードを大量に作成する必要があり、新しいリゾルバーを追加するときにチェックを追加するのを忘れることができるため、これは非常にエラーが発生しやすいアプローチです。 。 REST APIフレームワークを見ると、一般にこのような問題はHTTPリクエストインターセプターを使用して解決されますが、GraphQLの場合、1つのHTTPリクエストに複数のGraphQLクエリを含めることができるため、意味がありません。それでは、クエリの生の文字列表現にのみアクセスでき、手動で解析する必要があります。これは間違いなく良いアプローチではありません。この概念は、RESTからGraphQLにうまく変換されません。
したがって、GraphQLクエリをインターセプトするための何らかの方法が必要であり、この方法は プリズム-graphql-ミドルウェア 。このライブラリを使用すると、リゾルバーが呼び出される前または後に任意のコードを実行できます。コードの再利用と関心の分離を可能にすることで、コード構造を改善します。
GraphQLコミュニティは、Prismaミドルウェアライブラリに基づいて、いくつかの特定のユースケースを解決する素晴らしいミドルウェアをすでに作成しています。ユーザー認証用に、というライブラリがあります。 graphql-シールド 、APIの権限レイヤーを作成するのに役立ちます。
graphql-shieldをインストールした後、次のようにAPIのアクセス許可レイヤーを導入できます。
import { allow } from 'graphql-shield'; const isAuthorized = rule()( (obj, args, { authUser }, info) => authUser && true ); export const permissions = { Query: { '*': isAuthorized, sayHello: allow }, Mutation: { '*': isAuthorized, sayHello: allow } }
そして、このレイヤーをミドルウェアとしてスキーマに次のように適用できます。
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); const schemaWithMiddleware = applyMiddleware(schema, shield(permissions, { allowExternalErrors: true })); // Build Apollo server const apolloServer = new ApolloServer({ schemaWithMiddleware }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
ここでシールドオブジェクトを作成するとき、allowExternalErrors
を設定しますなぜなら、デフォルトでは、シールドの動作はリゾルバー内で発生するエラーをキャッチして処理することであり、これは私のサンプルアプリケーションでは受け入れられない動作だったからです。
上記の例では、認証されたユーザーのAPIへのアクセスのみを制限しましたが、シールドは非常に柔軟性があり、それを使用して、ユーザーに非常に豊富な承認スキーマを実装できます。たとえば、サンプルアプリケーションでは、次の2つの役割があります。USER
およびUSER_MANAGER
、およびロールUSER_MANAGER
を持つユーザーのみユーザー管理機能を呼び出すことができます。これは次のように実装されます。
export const isUserManager = rule()( (obj, args, { authUser }, info) => authUser && authUser.role === 'USER_MANAGER' ); export const permissions = { Query: { userById: isUserManager, users: isUserManager }, Mutation: { editUser: isUserManager, deleteUser: isUserManager } }
もう1つ言及したいのは、プロジェクトでミドルウェア機能を編成する方法です。スキーマ定義とリゾルバーマップと同様に、スキーマごとに分割して別々のファイルに保持することをお勧めしますが、スキーマ定義とリゾルバーマップの配列を受け入れてそれらをステッチするApolloサーバーとは異なり、Prismaミドルウェアライブラリはこれを行いません。ミドルウェアマップオブジェクトを1つだけ受け入れるため、それらを分割する場合は、手動でステッチバックする必要があります。この問題の解決策を確認するには、ApiExplorer
を参照してください。サンプルプロジェクトのクラス。
GraphQL SDLは、ユーザー入力を検証するための非常に限られた機能を提供します。必須フィールドとオプションフィールドのみを定義できます。それ以上の検証要件がある場合は、手動で実装する必要があります。検証ルールをリゾルバー関数に直接適用できますが、この機能は実際にはここには属していません。これは、ユーザーGraphQLミドルウェアのもう1つの優れたユースケースです。たとえば、ユーザー登録リクエストの入力データを使用してみましょう。ここでは、ユーザー名が正しいメールアドレスであるかどうか、パスワード入力が一致するかどうか、パスワードが十分に強力かどうかを検証する必要があります。これは次のように実装できます。
import { UserInputError } from 'apollo-server-express'; import passwordValidator from 'password-validator'; import { isEmail } from 'validator'; const passwordSchema = new passwordValidator() .is().min(8) .is().max(20) .has().letters() .has().digits() .has().symbols() .has().not().spaces(); export const validators = { Mutation: { signup: (resolve, parent, args, context) => { const { email, password, rePassword } = args.signupReq; if (!isEmail(email)) { throw new UserInputError('Invalid Email address!'); } if (password !== rePassword) { throw new UserInputError('Passwords don't match!'); } if (!passwordSchema.validate(password)) { throw new UserInputError('Password is not strong enough!'); } return resolve(parent, args, context); } } }
そして、次のような権限レイヤーとともに、バリデーターレイヤーをミドルウェアとしてスキーマに適用できます。
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); const schemaWithMiddleware = applyMiddleware(schema, validators, shield(permissions, { allowExternalErrors: true })); // Build Apollo server const apolloServer = new ApolloServer({ schemaWithMiddleware }); apolloServer.applyMiddleware({ app })
GraphQL APIで発生し、見過ごされがちなもう1つの考慮すべき問題は、N +1クエリです。この問題は、スキーマで定義されたタイプ間に1対多の関係がある場合に発生します。たとえば、それを示すために、サンプルプロジェクトのbookAPIを使用してみましょう。
extend type Query { books: [Book!]! ... } extend type Mutation { ... } type Book { id: ID! creator: User! createdAt: DateTime! updatedAt: DateTime! authors: [Author!]! title: String! about: String language: String genre: String isbn13: String isbn10: String publisher: String publishDate: Date hardcover: Int } type User { id: ID! createdAt: DateTime! updatedAt: DateTime! fullName: String! email: String! }
ここに、User
が表示されます。タイプはBook
と1対多の関係にありますタイプ。この関係は、Book
の作成者フィールドとして表されます。このスキーマのリゾルバーマップは、次のように定義されています。
export const resolvers = { Query: { books: (obj, args, context, info) => { return bookService.findAll(); }, ... }, Mutation: { ... }, Book: { creator: ({ creatorId }, args, context, info) => { return userService.findById(creatorId); }, ... } }
このAPIを使用して本のクエリを実行し、SQLステートメントのログを見ると、次のようになります。
select `books`.* from `books` select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? select `users`.* from `users` where `users`.`id` = ? ...
推測は簡単です。実行中に、最初に本のクエリに対してリゾルバーが呼び出され、本のリストが返され、次に各本のオブジェクトが作成者フィールドリゾルバーと呼ばれ、この動作によりN +1個のデータベースクエリが発生しました。データベースを爆発させたくない場合、そのような動作はあまり良くありません。
N + 1クエリの問題を解決するために、Facebook開発者はと呼ばれる非常に興味深いソリューションを作成しました DataLoader 、これは次のようにREADMEページで説明されています。
「DataLoaderは、アプリケーションのデータフェッチレイヤーの一部として使用される汎用ユーティリティであり、バッチ処理やキャッシュを介して、データベースやWebサービスなどのさまざまなリモートデータソースに対して簡素化された一貫性のあるAPIを提供します。」
DataLoaderがどのように機能するかを理解するのはそれほど簡単ではないため、最初に上記の問題を解決する例を見てから、その背後にあるロジックについて説明しましょう。
サンプルプロジェクトでは、DataLoaderは作成者フィールドに対して次のように定義されています。
export class UserDataLoader extends DataLoader { constructor() { const batchLoader = userIds => { return userService .findByIds(userIds) .then( users => userIds.map( userId => users.filter(user => user.id === userId)[0] ) ); }; super(batchLoader); } static getInstance(context) { if (!context.userDataLoader) { context.userDataLoader = new UserDataLoader(); } return context.userDataLoader; } }
UserDataLoaderを定義したら、次のように作成者フィールドのリゾルバーを変更できます。
export const resolvers = { Query: { ... }, Mutation: { ... }, Book: { creator: ({ creatorId }, args, context, info) => { const userDataLoader = UserDataLoader.getInstance(context); return userDataLoader.load(creatorId); }, ... } }
適用された変更後、booksクエリを再度実行し、SQLステートメントログを見ると、次のようになります。
select `books`.* from `books` select `users`.* from `users` where `id` in (?)
ここでは、N + 1データベースクエリが2つのクエリに削減されたことがわかります。最初のクエリは本のリストを選択し、2番目のクエリは本のリストで作成者として提示されたユーザーのリストを選択します。次に、DataLoaderがこの結果を達成する方法を説明しましょう。
DataLoaderの主な機能はバッチ処理です。単一の実行フェーズ中に、DataLoaderはすべての個別のロード関数呼び出しのすべての個別のIDを収集し、要求されたすべてのIDでバッチ関数を呼び出します。覚えておくべき重要なことの1つは、DataLoaderのインスタンスは再利用できないことです。バッチ関数が呼び出されると、戻り値はインスタンスに永久にキャッシュされます。この動作のため、実行フェーズごとにDataLoaderの新しいインスタンスを作成する必要があります。これを実現するために、静的なgetInstance
を作成しました。 DataLoaderのインスタンスがコンテキストオブジェクトに表示されているかどうかを確認し、見つからない場合は作成する関数。新しいコンテキストオブジェクトが実行フェーズごとに作成され、すべてのリゾルバー間で共有されることに注意してください。
DataLoaderのバッチ読み込み関数は、要求された個別のIDの配列を受け入れ、対応するオブジェクトの配列に解決されるPromiseを返します。バッチロード関数を作成するときは、次の2つの重要な点を覚えておく必要があります。
[1, 2, 3]
を要求した場合、返される結果の配列には、正確に3つのオブジェクトが含まれている必要があります。[{ 'id': 1, “fullName”: “user1” }, { “id”: 2, “fullName”: “user2” }, { “id”: 3, “fullName”: “user3” }]
[3, 1, 2]
の場合、返される結果の配列には、まったく同じ順序のオブジェクトが含まれている必要があります:[{ 'id': 3, “fullName”: “user3” }, { “id”: 1, “fullName”: “user1” }, { “id”: 2, “fullName”: “user2” }]
この例では、結果の順序が次のコードで要求されたIDの順序と一致することを確認します。
then( users => userIds.map( userId => users.filter(user => user.id === userId)[0] ) )
最後になりましたが、セキュリティについて触れておきたいと思います。 GraphQLを使用すると、非常に柔軟なAPIを作成し、データのクエリ方法に関する豊富な機能をユーザーに提供できます。これにより、アプリケーションのクライアント側にかなりの権限が付与され、ベンおじさんが言ったように、「大きな権限には大きな責任が伴います」。適切なセキュリティがないと、悪意のあるユーザーが高額なクエリを送信し、サーバーにDoS(サービス拒否)攻撃を引き起こす可能性があります。
APIを保護するために最初にできることは、GraphQLスキーマのイントロスペクションを無効にすることです。デフォルトでは、GraphQL APIサーバーは、スキーマ全体をイントロスペクトする機能を公開します。これは、GraphiQLやApollo Playgroundなどのインタラクティブなビジュアルシェルで一般的に使用されますが、悪意のあるユーザーがAPIに基づいて複雑なクエリを作成する場合にも非常に役立ちます。 。 introspection
を設定することで、これを無効にできます。 Apolloサーバーの作成時にパラメーターをfalseに設定します。
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, introspection: false }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
APIを保護するために次にできることは、クエリの深さを制限することです。これは、データ型間に循環的な関係がある場合に特に重要です。たとえば、このサンプルでは、プロジェクトタイプAuthor
フィールドブックがあり、タイプBook
フィールド作成者がいます。これは明らかに循環的な関係であり、悪意のあるユーザーが次のようなクエリを作成することを妨げるものは何もありません。
query { authors { id, fullName books { id, title authors { id, fullName books { id, title, authors { id, fullName books { id, title authors { ... } } } } } } } }
十分なネストがあれば、そのようなクエリはサーバーを簡単に爆発させる可能性があることは明らかです。クエリの深さを制限するために、次のライブラリを使用できます。 graphql-depth-limit 。インストールしたら、次のように、Apolloサーバーを作成するときに深度制限を適用できます。
// Configure express const app = express(); // Build GraphQL schema based on SDL definitions and resolver maps const schema = makeExecutableSchema({ typeDefs, resolvers }); // Build Apollo server const apolloServer = new ApolloServer({ schema, introspection: false, validationRules: [ depthLimit(5) ] }); apolloServer.applyMiddleware({ app }); // Run server app.listen({ port }, () => { console.log(`Server ready at http://localhost:${ port }${ apolloServer.graphqlPath }`); })
ここでは、クエリの最大深度を5つに制限しました。
このチュートリアルでは、GraphQLAPIの実装を開始するときに遭遇する一般的な問題をデモンストレーションしようとしました。ただし、その一部は非常に浅いコード例を提供し、サイズが原因で、説明した問題の表面のみをスクラッチします。このため、より完全なコード例を確認するには、サンプルのGraphQLAPIプロジェクトのGitリポジトリを参照してください。 graphql-例 。
結局、GraphQLは本当に興味深いテクノロジーだと言いたいです。 RESTに取って代わりますか?誰も知らない、おそらく明日、急速に変化するITの世界で、APIを開発するためのより良いアプローチが現れるだろうが、GraphQLは本当に学ぶ価値のある興味深いテクノロジーのカテゴリーに分類される。
GraphQLは、APIのドメイン固有のデータクエリおよび操作言語であり、既存のデータを使用してクエリを実行するためのランタイムです。
主な利点の1つはGraphQLSDLです。これは、強く型付けされたAPIを記述し、優れたドキュメントとして機能するために使用されます。もう1つの利点は、GraphQLがデータモデルをグラフに変換し、クライアントがサーバーから必要なデータを正確にクエリできるようにすることです。
サーバーとしてのラズベリーパイ
ユースケースによって異なります。 GraphQLは、データのオーバーフェッチやアンダーフェッチなど、RESTの問題のいくつかを解決しましたが、複雑さを犠牲にして、RESTほど単純で単純ではありません。もう1つの欠点は、HTTPステータスコードを使用できないことです。これにより、エラー処理がより困難になります。
いいえ、GraphQLはRESTを使用しません。これは、APIを作成するための代替アプローチです。ただし、RESTAPIはGraphQLを使用してラップできます。
RESTに取って代わったGraphQLはありません。それはまだ若い技術であり、RESTほど成熟していません。
一般的に、GraphQLはRESTより遅くはありません。必要なデータを正確に要求し、データのオーバーフェッチやアンダーフェッチを回避できるため、RESTよりも高速な場合があります。
それは興味深いテクノロジーであり、いくつかのユースケースを非常にうまく解決するからです。