データベースへのデータ挿入やデータベース内データの変更など、データを変更するAPIエンドポイントがある場合、そのエンドポイントをQuery
ではなくMutation
にする必要があります。これは、APIエンドポイントをトップレベルのQuery
型ではなく、トップレベルのMutation
型の一部にすることで簡単に実現できます。
例えば、「今日のメッセージ」サーバーがあるとします。誰でも今日のメッセージを更新でき、誰でも現在のメッセージを読むことができます。このGraphQLスキーマは単純に次のようになります。
type Mutation { setMessage(message: String): String}
type Query { getMessage: String}
setMessage
のように、データベースの作成または更新操作にマッピングされるミューテーションが、サーバーに保存されたものと同じものを返すようにすることは、多くの場合便利です。このようにすると、サーバー上のデータが変更された場合、クライアントはその変更について知ることができます。
ミューテーションとクエリはどちらもルートレゾルバーによって処理できるため、このスキーマを実装するルートは単純に次のようになります。
var fakeDatabase = {}var root = { setMessage: ({ message }) => { fakeDatabase.message = message return message }, getMessage: () => { return fakeDatabase.message },}
ミューテーションを実装するには、これ以上何も必要ありません。しかし、多くの場合、すべて同じ入力パラメーターを受け入れる多くの異なるミューテーションが見つかるでしょう。一般的な例としては、データベースでオブジェクトを作成することと、データベースでオブジェクトを更新することが、多くの場合同じパラメーターを使用することです。スキーマを簡素化するために、「入力型」を使用できます。これは、type
キーワードの代わりにinput
キーワードを使用することによって行います。
例えば、1つの「今日のメッセージ」ではなく、id
フィールドでデータベースにインデックス付けされた多くのメッセージがあり、各メッセージにはcontent
文字列とauthor
文字列の両方がある場合を考えてみましょう。新しいメッセージの作成と古いメッセージの更新の両方のミューテーションAPIが必要です。次のようなスキーマを使用できます。
input MessageInput { content: String author: String}
type Message { id: ID! content: String author: String}
type Query { getMessage(id: ID!): Message}
type Mutation { createMessage(input: MessageInput): Message updateMessage(id: ID!, input: MessageInput): Message}
ここでは、ミューテーションはMessage
型を返すため、クライアントは、それを変更するリクエストと同じリクエストで、新しく変更されたMessage
に関する詳細情報を取得できます。
入力型は、他のオブジェクトをフィールドとして持つことはできません。基本的なスカラー型、リスト型、および他の入力型のみです。
末尾にInput
を付けることで入力型に名前を付けることは便利な慣習です。なぜなら、単一の概念オブジェクトに対して、わずかに異なる入力型と出力型の両方を頻繁に必要とするからです。
このスキーマを実装する実行可能なコードを次に示します(データはメモリ内に保持)。
var express = require("express")var { createHandler } = require("graphql-http/lib/use/express")var { buildSchema } = require("graphql")
// Construct a schema, using GraphQL schema languagevar schema = buildSchema(` input MessageInput { content: String author: String }
type Message { id: ID! content: String author: String }
type Query { getMessage(id: ID!): Message }
type Mutation { createMessage(input: MessageInput): Message updateMessage(id: ID!, input: MessageInput): Message }`)
// If Message had any complex fields, we'd put them on this object.class Message { constructor(id, { content, author }) { this.id = id this.content = content this.author = author }}
// Maps username to contentvar fakeDatabase = {}
var root = { getMessage: ({ id }) => { if (!fakeDatabase[id]) { throw new Error("no message exists with id " + id) } return new Message(id, fakeDatabase[id]) }, createMessage: ({ input }) => { // Create a random id for our "database". var id = require("crypto").randomBytes(10).toString("hex")
fakeDatabase[id] = input return new Message(id, input) }, updateMessage: ({ id, input }) => { if (!fakeDatabase[id]) { throw new Error("no message exists with id " + id) } // This replaces all old data, but some apps might want partial update. fakeDatabase[id] = input return new Message(id, input) },}
var app = express()app.all( "/graphql", createHandler({ schema: schema, rootValue: root, }))app.listen(4000, () => { console.log("Running a GraphQL API server at localhost:4000/graphql")})
ミューテーションを呼び出すには、GraphQLクエリの前に関キーワードmutation
を使用する必要があります。入力型を渡すには、JSONオブジェクトのように記述されたデータを提供します。例えば、上記のサーバーが定義されている場合、新しいメッセージを作成し、この操作で新しいメッセージのid
を返すことができます。
mutation { createMessage(input: { author: "andy", content: "hope is a good thing", }) { id }}
クエリと同様に、変数を使用してミューテーションクライアントのロジックを簡素化できます。例えば、このミューテーションを実行するためにサーバーを呼び出すJavaScriptコードは次のようになります。
var author = "andy"var content = "hope is a good thing"var query = `mutation CreateMessage($input: MessageInput) { createMessage(input: $input) { id }}`
fetch("/graphql", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ query, variables: { input: { author, content, }, }, }),}) .then(r => r.json()) .then(data => console.log("data returned:", data))
ミューテーションの特定の1つの型は、新しいユーザーのサインアップなど、ユーザーを変更する操作です。これはGraphQLミューテーションを使用して実装できますが、認証とExpressミドルウェアを使用したGraphQLについて学習すれば、多くの既存のライブラリを再利用できます。