Node.jsとExpressを使用した実用的なGraphQLスタートガイド
序章
GraphQL は、データ要件と相互作用を記述するための直感的で柔軟な構文に基づいてクライアントアプリケーションを構築することを目的として、Facebookによって作成されたクエリ言語です。 GraphQLサービスは、タイプとそれらのタイプのフィールドを定義し、各タイプの各フィールドに関数を提供することによって作成されます。
GraphQLサービスが実行されると(通常はWebサービスのURLで)、GraphQLクエリを受信して検証および実行できます。 受信したクエリは、最初にチェックされ、定義されたタイプとフィールドのみを参照していることを確認してから、提供された関数を実行して結果を生成します。
このチュートリアルでは、 Express を使用してGraphQLサーバーを実装し、それを使用して重要なGraphQL機能を学習します。
GraphQLの機能には次のものがあります。
-
階層-クエリは、返すデータとまったく同じように見えます。
-
クライアント指定のクエリ-クライアントには、サーバーから何をフェッチするかを指示する自由があります。
-
強い型-実行前に構文的におよびGraphQL型システム内でクエリを検証できます。 これは、GraphiQLなどの開発エクスペリエンスを向上させる強力なツールを活用するのにも役立ちます。
-
イントロスペクティブ-GraphQL構文自体を使用して型システムにクエリを実行できます。 これは、受信データを厳密に型指定されたインターフェースに解析するのに最適であり、JSONを解析して手動でオブジェクトに変換する必要はありません。
目標
従来のREST呼び出しの主な課題の1つは、クライアントがカスタマイズされた(制限または拡張された)データのセットを要求できないことです。 ほとんどの場合、クライアントがサーバーに情報を要求すると、すべてのフィールドを取得するか、まったく取得しません。
もう1つの問題は、複数のエンドポイントの操作と保守です。 プラットフォームが成長するにつれて、その結果、その数は増加します。 したがって、クライアントは多くの場合、さまざまなエンドポイントからのデータを要求する必要があります。 GraphQL APIは、エンドポイントではなく、タイプとフィールドの観点から編成されています。 単一のエンドポイントからデータの全機能にアクセスできます。
GraphQLサーバーを構築する場合、すべてのデータのフェッチと変更に必要なURLは1つだけです。 したがって、クライアントは、必要なものを記述したクエリ文字列をサーバーに送信することで、一連のデータを要求できます。
前提条件
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
ステップ1—ノードを使用したGraphQLのセットアップ
まず、基本的なファイル構造とサンプルコードスニペットを作成します。
まず、作成します GraphQL
ディレクトリ:
- mkdir GraphQL
新しいディレクトリに移動します。
- cd GraphQL
初期化 npm
事業:
- npm init -y
次に、を作成します server.js
メインファイルとなるファイル:
- touch server.js
プロジェクトは次のようになります。
必要なパッケージは、実装時にこのチュートリアルで説明します。 次に、HTTPサーバーミドルウェアであるExpressとexpress-graphqlを使用してサーバーをセットアップします。
- npm install graphql express express-graphql
開ける server.js
テキストエディタで、次のコード行を追加します。
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Initialize a GraphQL schema
var schema = buildSchema(`
type Query {
hello: String
}
`);
// Root resolver
var root = {
hello: () => 'Hello world!'
};
// Create an express server and a GraphQL endpoint
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema, // Must be provided
rootValue: root,
graphiql: true, // Enable GraphiQL when server endpoint is accessed in browser
}));
app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));
注:このコードは以前のバージョンの express-graphql
. v0.10.0より前では、 var graphqlHTTP = require('express-graphql');
. v0.10.0以降は、を使用する必要があります var { graphqlHTTP } = require('express-graphql');
.
このスニペットはいくつかのことを実行します。 それは使用しています require
インストールされたパッケージを含めるため。 また、ジェネリックを初期化します schema
と root
値。 さらに、でエンドポイントを作成します /graphql
Webブラウザでアクセスできます。
これらの変更を行った後、ファイルを保存して閉じます。
実行されていない場合は、ノードサーバーを起動します。
- node server.js
注:このチュートリアル全体を通して、次の更新を行います。 server.js
最新の変更を反映するには、ノードサーバーを再起動する必要があります。
訪問 localhost:4000/graphql
Webブラウザで。 Welcome to GraphiQLWebインターフェースが表示されます。
左側にクエリを入力するペインがあります。 表示するためにドラッグしてサイズ変更する必要があるクエリ変数を入力するための追加のペインがあります。 右側のペインには、クエリの実行結果が表示されます。 さらに、クエリの実行は、再生アイコンの付いたボタンを押すことで実行できます。
これまで、GraphQLのいくつかの機能と利点について説明してきました。 この次のセクションでは、GraphQLのいくつかの技術的機能のさまざまな用語と実装について詳しく説明します。 これらの機能を練習するには、Expressサーバーを使用します。
ステップ2—スキーマを定義する
GraphQLでは、スキーマがクエリとミューテーションを管理し、GraphQLサーバーで実行できるものを定義します。 スキーマは、GraphQLAPIの型システムを定義します。 クライアントがアクセスできる可能性のあるデータ(オブジェクト、フィールド、関係など)の完全なセットについて説明します。 クライアントからの呼び出しは、スキーマに対して検証および実行されます。 クライアントは、イントロスペクションを介してスキーマに関する情報を見つけることができます。 スキーマはGraphQLAPIサーバーに存在します。
GraphQLインターフェース定義言語(IDL)またはスキーマ定義言語(SDL)は、GraphQLスキーマを指定するための最も簡潔な方法です。 GraphQLスキーマの最も基本的なコンポーネントは、オブジェクトタイプです。これは、サービスからフェッチできるオブジェクトの種類と、サービスに含まれるフィールドを表します。
GraphQLスキーマ言語では、 user
と id
, name
、 と age
この例のように:
type User {
id: ID!
name: String!
age: Int
}
JavaScriptでは、 buildSchema
GraphQLスキーマ言語からSchemaオブジェクトを構築する関数。 あなたが同じを代表することになった場合 user
上記の例は次のようになります。
var schema = buildSchema(`
type User {
id: Int
name: String!
age: Int
}
`);
タイプの構築
内部でさまざまなタイプを定義できます buildSchema
、ほとんどの場合に気付くかもしれませんが type Query {...}
と type Mutation {...}
. type Query {...}
GraphQLクエリにマップされる関数を保持するオブジェクトであり、データのフェッチに使用されます(RESTのGETと同等)。 type Mutation {...}
ミューテーションにマップされ、データの作成、更新、または削除に使用される関数を保持します(RESTのPOST、UPDATE、およびDELETEと同等)。
いくつかの妥当なタイプを追加することにより、スキーマを少し複雑にします。 たとえば、 user
との配列 users
タイプの Person
、持っている人 id
, name
, age
、そして彼らのお気に入り shark
プロパティ。
の既存のコード行を置き換えます schema
の server.js
この新しいスキーマオブジェクトを使用すると、次のようになります。
// Initialize a GraphQL schema
var schema = buildSchema(`
type Query {
user(id: Int!): Person
users(shark: String): [Person]
},
type Person {
id: Int
name: String
age: Int
shark: String
}
`);
上記の興味深い構文に気付くかもしれませんが、 [Person]
タイプの配列を返すことを意味します Person
の感嘆の間に user(id: Int!)
つまり、 id
提供されなければなりません。 users
クエリはオプションを取ります shark
変数。
ステップ3—リゾルバーの定義
リゾルバーは、操作を実際の関数にマッピングする役割を果たします。 中身 type Query
、という操作があります users
. この操作を、内部に同じ名前の関数にマップします root
.
また、この機能のサンプルユーザーをいくつか作成します。
これらの新しいコード行をに追加します server.js
直後 buildSchema
コードの行ですが、 root
コード行:
...
// Sample users
var users = [
{
id: 1,
name: 'Brian',
age: '21',
shark: 'Great White Shark'
},
{
id: 2,
name: 'Kim',
age: '22',
shark: 'Whale Shark'
},
{
id: 3,
name: 'Faith',
age: '23',
shark: 'Hammerhead Shark'
},
{
id: 4,
name: 'Joseph',
age: '23',
shark: 'Tiger Shark'
},
{
id: 5,
name: 'Joy',
age: '25',
shark: 'Hammerhead Shark'
}
];
// Return a single user
var getUser = function(args) {
// ...
}
// Return a list of users
var retrieveUsers = function(args) {
// ...
}
...
の既存のコード行を置き換えます root
の server.js
この新しいオブジェクトで:
// Root resolver
var root = {
user: getUser, // Resolver function to return user with specific id
users: retrieveUsers
};
コードを読みやすくするには、ルートリゾルバーにすべてを積み上げるのではなく、個別の関数を作成します。 どちらの機能もオプションです args
クライアントクエリから変数を運ぶパラメータ。 リゾルバーの実装を提供し、それらの機能をテストしてみましょう。
のコード行を置き換えます getUser
と retrieveUsers
以前に追加した server.js
次のように:
// Return a single user (based on id)
var getUser = function(args) {
var userID = args.id;
return users.filter(user => user.id == userID)[0];
}
// Return a list of users (takes an optional shark parameter)
var retrieveUsers = function(args) {
if (args.shark) {
var shark = args.shark;
return users.filter(user => user.shark === shark);
} else {
return users;
}
}
Webインターフェイスで、入力ペインに次のクエリを入力します。
query getSingleUser {
user {
name
age
shark
}
}
次の出力が表示されます。
Output{
"errors": [
{
"message": "Cannot query field \"user\" on type \"Query\".",
"locations": [
{
"line": 2,
"column": 3
}
]
}
]
}
上記の例では、という名前の操作を使用しています getSingleUser
単一のユーザーを name
, age
、そしてお気に入り shark
. オプションで、それらが必要であることを指定できます name
必要がなかった場合のみ age
と shark
.
公式ドキュメントによると、コードベース内のクエリは、内容を解読するのではなく、名前で識別するのが最も簡単です。
このクエリは必要なものを提供しません id
GraphQLは、説明的なエラーメッセージを表示します。 ここで、正しいクエリを作成します。 変数と引数の使用に注意してください。
Webインターフェイスで、入力ペインのコンテンツを次の修正されたクエリに置き換えます。
query getSingleUser($userID: Int!) {
user(id: $userID) {
name
age
shark
}
}
Webインターフェースを使用したまま、変数ペインのコンテンツを次のように置き換えます。
Query Variables{
"userID": 1
}
次の出力が表示されます。
Output{
"data": {
"user": {
"name": "Brian",
"age": 21,
"shark": "Great White Shark"
}
}
}
これにより、 id
の 1
, Brian
. また、要求されたものを返します name
, age
、 と shark
田畑。
ステップ4—エイリアスの定義
2人の異なるユーザーを取得する必要がある状況では、各ユーザーをどのように識別するのか疑問に思われるかもしれません。 GraphQLでは、異なる引数を使用して同じフィールドを直接クエリすることはできません。 これをデモンストレーションしましょう。
Webインターフェイスで、入力ペインのコンテンツを次のように置き換えます。
query getUsersWithAliasesError($userAID: Int!, $userBID: Int!) {
user(id: $userAID) {
name
age
shark
},
user(id: $userBID) {
name
age
shark
}
}
Webインターフェースを使用したまま、変数ペインのコンテンツを次のように置き換えます。
Query Variables{
"userAID": 1,
"userBID": 2
}
次の出力が表示されます。
Output{
"errors": [
{
"message": "Fields \"user\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
"locations": [
{
"line": 2,
"column": 3
},
{
"line": 7,
"column": 3
}
]
}
]
}
エラーは説明的であり、エイリアスの使用を示唆しています。 実装を修正しましょう。
Webインターフェイスで、入力ペインのコンテンツを次の修正されたクエリに置き換えます。
query getUsersWithAliases($userAID: Int!, $userBID: Int!) {
userA: user(id: $userAID) {
name
age
shark
},
userB: user(id: $userBID) {
name
age
shark
}
}
Webインターフェースを使用している間に、変数ペインに次のものが含まれていることを確認します。
Query Variables{
"userAID": 1,
"userBID": 2
}
次の出力が表示されます。
Output{
"data": {
"userA": {
"name": "Brian",
"age": 21,
"shark": "Great White Shark"
},
"userB": {
"name": "Kim",
"age": 22,
"shark": "Whale Shark"
}
}
}
これで、各ユーザーをフィールドで正しく識別できます。
ステップ5—フラグメントの作成
上記のクエリはそれほど悪くはありませんが、1つの問題があります。 両方に同じフィールドを繰り返しています userA
と userB
. クエリDRYを作成するものを見つけることができました。 GraphQLには、 Fragments と呼ばれる再利用可能なユニットが含まれています。これにより、フィールドのセットを作成し、必要に応じてクエリに含めることができます。
Webインターフェイスで、変数ペインのコンテンツを次のように置き換えます。
query getUsersWithFragments($userAID: Int!, $userBID: Int!) {
userA: user(id: $userAID) {
...userFields
},
userB: user(id: $userBID) {
...userFields
}
}
fragment userFields on Person {
name
age
shark
}
Webインターフェースを使用している間に、変数ペインに次のものが含まれていることを確認します。
Query Variables{
"userAID": 1,
"userBID": 2
}
次の出力が表示されます。
Output{
"data": {
"userA": {
"name": "Brian",
"age": 21,
"shark": "Great White Shark"
},
"userB": {
"name": "Kim",
"age": 22,
"shark": "Whale Shark"
}
}
}
と呼ばれるフラグメントを作成しました userFields
にのみ適用できます type Person
次に、それを使用してユーザーを取得しました。
ステップ6—ディレクティブの定義
ディレクティブを使用すると、変数を使用してクエリの構造と形状を動的に変更できます。 ある時点で、スキーマを変更せずに一部のフィールドをスキップまたは含めることができます。 使用可能な2つのディレクティブは次のとおりです。
@include(if: Boolean)
-引数がtrueの場合にのみ、このフィールドを結果に含めます。@skip(if: Boolean)
-引数がtrueの場合、このフィールドをスキップします。
ファンであるユーザーを取得したいとします Hammerhead Shark
、しかし彼らを含める id
そして彼らをスキップします age
田畑。 変数を使用して、 shark
包含およびスキップ機能のディレクティブを使用します。
Webインターフェイスで、入力ペインをクリアし、以下を追加します。
query getUsers($shark: String, $age: Boolean!, $id: Boolean!) {
users(shark: $shark){
...userFields
}
}
fragment userFields on Person {
name
age @skip(if: $age)
id @include(if: $id)
}
Webインターフェースを使用したまま、変数ペインをクリアして、以下を追加します。
Query Variables{
"shark": "Hammerhead Shark",
"age": true,
"id": true
}
次の出力が表示されます。
Output{
"data": {
"users": [
{
"name": "Faith",
"id": 3
},
{
"name": "Joy",
"id": 5
}
]
}
}
これにより、2人のユーザーが shark
一致する値 Hammerhead Shark
–Faith
と Joy
.
ステップ7—ミューテーションの定義
これまで、クエリ、つまりデータを取得する操作を扱ってきました。 ミューテーションは、データの作成、削除、更新を処理するGraphQLの2番目の主要な操作です。
突然変異を実行する方法のいくつかの例に焦点を当てましょう。 たとえば、ユーザーを次のように更新したい id == 1
そして彼らを変える age
, name
、次に新しいユーザーの詳細を返します。
スキーマを更新して、既存のコード行に加えてミューテーションタイプを含めます。
// Initialize a GraphQL schema
var schema = buildSchema(`
type Query {
user(id: Int!): Person
users(shark: String): [Person]
},
type Person {
id: Int
name: String
age: Int
shark: String
}
# newly added code
type Mutation {
updateUser(id: Int!, name: String!, age: String): Person
}
`);
後 getUser
と retrieveUsers
、新しいを追加します updateUser
ユーザーの更新を処理する関数:
// Update a user and return new user details
var updateUser = function({id, name, age}) {
users.map(user => {
if (user.id === id) {
user.name = name;
user.age = age;
return user;
}
});
return users.filter(user => user.id === id)[0];
}
また、関連するリゾルバー関数でルートリゾルバーを更新します。
// Root resolver
var root = {
user: getUser,
users: retrieveUsers,
updateUser: updateUser // Include mutation function in root resolver
};
これらが最初のユーザーの詳細であると仮定します。
Output{
"data": {
"user": {
"name": "Brian",
"age": 21,
"shark": "Great White Shark"
}
}
}
Webインターフェイスで、次のクエリを入力ペインに追加します。
mutation updateUser($id: Int!, $name: String!, $age: String) {
updateUser(id: $id, name:$name, age: $age){
...userFields
}
}
fragment userFields on Person {
name
age
shark
}
Webインターフェースを使用したまま、変数ペインをクリアして、以下を追加します。
Query Variables{
"id": 1,
"name": "Keavin",
"age": "27"
}
次の出力が表示されます。
Output{
"data": {
"updateUser": {
"name": "Keavin",
"age": 27,
"shark": "Great White Shark"
}
}
}
ユーザーを更新するための変更の後、新しいユーザーの詳細を取得します。
を持っているユーザー id
の 1
から更新されました Brian
(age 21
) に Keavin
(age 27
).
結論
このガイドでは、GraphQLの基本的な概念から、かなり複雑な例までを取り上げました。 これらの例のほとんどは、RESTを操作したユーザーのGraphQLとRESTの違いを示しています。
GraphQLの詳細については、公式ドキュメントを確認してください。