最初のGraphQLAPIの作成
公開: 2022-03-11序文
数年前、FacebookはGraphQLと呼ばれるバックエンドAPIを構築する新しい方法を導入しました。これは基本的に、データのクエリと操作のためのドメイン固有言語です。 最初はあまり注意を払っていませんでしたが、最終的には、GraphQLに基づいてバックエンドAPIを実装する必要があるToptalのプロジェクトに携わっていました。 そのとき、私は先に進んで、RESTで学んだ知識をGraphQLに適用する方法を学びました。
これは非常に興味深い経験であり、実装期間中に、RESTAPIで使用される標準的なアプローチと方法論をよりGraphQLに適した方法で再考する必要がありました。 この記事では、GraphQLAPIを初めて実装するときに考慮すべき一般的な問題を要約します。
必要なライブラリ
GraphQLはFacebookによって内部的に開発され、2015年に公開されました。2018年の後半に、GraphQLプロジェクトはFacebookから、GraphQLクエリ言語仕様とリファレンスを維持および開発する非営利のLinuxFoundationによってホストされる新しく設立されたGraphQLFoundationに移動されました。 JavaScriptの実装。
GraphQLはまだ若いテクノロジーであり、最初のリファレンス実装はJavaScriptで利用可能であったため、そのための最も成熟したライブラリはNode.jsエコシステムに存在します。 GraphQL用のオープンソースツールとライブラリを提供している他の2つの会社、ApolloとPrismaもあります。 この記事のサンプルプロジェクトは、JavaScript用のGraphQLのリファレンス実装と、次の2つの会社が提供するライブラリに基づいています。
- Graphql-js –JavaScript用のGraphQLのリファレンス実装
- Apollo-server – Express、Connect、Hapi、KoaなどのGraphQLサーバー
- Apollo-graphql-tools – SDLを使用してGraphQLスキーマを構築、モック、およびステッチします
- Prisma-graphql-ミドルウェア–GraphQLリゾルバーをミドルウェア関数に分割します
GraphQLの世界では、GraphQLスキーマを使用してAPIを記述します。これらの場合、仕様はGraphQLスキーマ定義言語(SDL)と呼ばれる独自の言語を定義します。 SDLは非常にシンプルで直感的に使用できると同時に、非常に強力で表現力豊かです。
GraphQLスキーマを作成するには、コードファーストアプローチとスキーマファーストアプローチの2つの方法があります。
- コードファーストのアプローチでは、GraphQLスキーマをgraphql-jsライブラリに基づくJavaScriptオブジェクトとして記述し、SDLはソースコードから自動生成されます。
- スキーマファーストのアプローチでは、SDLでGraphQLスキーマを記述し、Apollographql-toolsライブラリを使用してビジネスロジックを接続します。
個人的には、スキーマファーストのアプローチを好み、この記事のサンプルプロジェクトで使用します。 従来の書店の例を実装し、著者と本を作成するためのCRUD APIに加えて、ユーザー管理と認証用のAPIを提供するバックエンドを作成します。
基本的なGraphQLサーバーの作成
基本的な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ブラウザーで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-scalarもあります。これにより、高度なサニタイズ/検証/変換を使用してカスタムGraphQLスカラーを定義できます。
必要に応じて、上記のように、カスタムスカラーデータ型を定義してスキーマで使用することもできます。 これは難しいことではありませんが、この記事の範囲外です。興味がある場合は、Apolloのドキュメントでより高度な情報を見つけることができます。
分割スキーマ
スキーマに機能を追加すると、スキーマが拡大し始め、定義のセット全体を1つのファイルに保持することは不可能であることがわかります。コードを整理し、よりスケーラブルにするために、スキーマを小さな部分に分割する必要があります。大きいサイズ。 幸い、Apolloが提供するスキーマビルダー関数makeExecutableSchema
は、配列の形式でスキーマ定義とリゾルバーマップも受け入れます。 これにより、スキーマとリゾルバーのマップをより小さな部分に分割することができます。 これはまさに私がサンプルプロジェクトで行ったことです。 APIを次の部分に分割しました。
-
auth.api.graphql
–ユーザー認証と登録のためのAPI -
author.api.graphql
–作成者エントリ用のCRUD API -
book.api.graphql
–本のエントリ用のCRUD API -
root.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クエリをインターセプトするための何らかの方法が必要であり、この方法はprisma-graphql-middlewareと呼ばれます。 このライブラリを使用すると、リゾルバーが呼び出される前または後に任意のコードを実行できます。 コードの再利用と関心の分離を可能にすることで、コード構造を改善します。
GraphQLコミュニティは、いくつかの特定のユースケースを解決するPrismaミドルウェアライブラリに基づいて多数の素晴らしいミドルウェアをすでに作成しています。ユーザー認証用に、APIのアクセス許可レイヤーを作成するのに役立つgraphql-shieldというライブラリがあります。
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
をtrueに設定します。これは、デフォルトでは、シールドの動作はリゾルバー内で発生するエラーをキャッチして処理することであり、これは私のサンプルアプリケーションでは受け入れられない動作でした。

上記の例では、認証されたユーザーのAPIへのアクセスのみを制限しましたが、シールドは非常に柔軟であり、それを使用して、ユーザーに非常に豊富な承認スキーマを実装できます。 たとえば、サンプルアプリケーションには、 USER
とUSER_MANAGER
の2つの役割があり、 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 })
N+1クエリ
考慮すべきもう1つの問題は、GraphQL APIで発生し、見過ごされがちですが、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つの重要な点を覚えておく必要があります。
- 結果の配列は、要求されたIDの配列と同じ長さである必要があります。 たとえば、ID
[1, 2, 3]
をリクエストした場合、返される結果の配列には、正確に3つのオブジェクトが含まれている必要があります:[{ "id": 1, “fullName”: “user1” }, { “id”: 2, “fullName”: “user2” }, { “id”: 3, “fullName”: “user3” }]
- 結果の配列内の各インデックスは、要求されたIDの配列内の同じインデックスに対応している必要があります。 たとえば、要求されたIDの配列の順序が
[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に基づいて複雑なクエリを作成する場合にも非常に役立ちます。 。 これを無効にするには、Apolloサーバーの作成時にintrospection
パラメーターを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つに制限しました。
Scriptum後:RESTからGraphQLへの移行は興味深い
このチュートリアルでは、GraphQLAPIの実装を開始するときに遭遇する一般的な問題をデモンストレーションしようとしました。 ただし、その一部は非常に浅いコード例を提供し、サイズが原因で、説明した問題の表面のみをスクラッチします。 このため、より完全なコード例を確認するには、サンプルのGraphQLAPIプロジェクトのGitリポジトリーgraphql-exampleを参照してください。
結局、GraphQLは本当に興味深いテクノロジーだと言いたいです。 それはRESTに取って代わりますか? 誰も知らない、おそらく明日、急速に変化するITの世界で、APIを開発するためのより良いアプローチが現れるだろうが、GraphQLは本当に学ぶ価値のある興味深いテクノロジーのカテゴリーに分類される。