GraphQL LogoGraphQL

突變和輸入類型

如果您有一個會變更資料的 API 端點,例如將資料插入資料庫或變更資料庫中已存在的資料,您應該讓這個端點成為一個 Mutation,而不是一個 Query。這就像讓 API 端點成為頂層 Mutation 類型的一部分,而不是頂層 Query 類型一樣簡單。

假設我們有一個「每日訊息」伺服器,任何人都可以更新每日訊息,任何人都可以閱讀目前的訊息。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
},
}

您不需要比這更多來實作突變。但在許多情況下,您會發現許多不同的突變都接受相同的輸入參數。一個常見的範例是,在資料庫中建立物件和更新資料庫中的物件通常需要相同的參數。為了簡化您的規範,您可以使用「輸入類型」,方法是使用 input 關鍵字,而不是 type 關鍵字。

例如,假設我們不是只有單一每日訊息,而是有許多訊息,並以資料庫中的 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 language
var 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 content
var 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))

變異的其中一種特定類型是會變更使用者的操作,例如註冊新使用者。雖然你可以使用 GraphQL 變異來實作這個功能,但如果你瞭解 具有驗證和 Express 中介軟體的 GraphQL,你可以重複使用許多現有的函式庫。

繼續閱讀 →驗證和 Express 中介軟體