相信有很多仁兄在2018年底都看到過 2018 JavaScript 現狀調查報告 這篇文章。其中有一張圖甚是有趣:
可以看得出來 GraphQL 的趨勢大好,而且也越來越受歡迎了,似乎不學習要跟不上時代了。既然這樣我們就來學習一下怎麼用node搭建GraphQL服務端API架構 (本文將會為您講述最全面的node GraphQL知識, 文章末尾會附帶完整的demo供給各位參考)GraphQL是什麼
簡單的說明一下GraphQL是Facebook開源的一種規範應用層查詢語言,它具有很多優點:客戶端可以自定義查詢語句,通過自定義不僅提高了靈活性,而且服務端只返回客戶端所需要的資料,減少網路的開銷,提高了效能;服務端收到客戶端的請求,首先做型別檢查,及時反饋,而且更加安全;自動生成文件,降低維護成本;服務端通過新增欄位,deprecated欄位,避免版本的繁雜和紊亂。 附上官方的文件 GraphQL 附上express實現graphql的文件 express-graphql
GraphQL vs RESTful
RESTful:服務端決定有哪些資料獲取方式,客戶端只能挑選使用,如果資料過於冗餘也只能默默接收再對資料進行處理;而資料不能滿足需求則需要請求更多的介面。 GraphQL:給客戶端自主選擇資料內容的能力,客戶端完全自主決定獲取資訊的內容,服務端負責精確的返回目標資料。
##實現GraphQL伺服器 首先我們先來實現一個最簡單的node GraphQL伺服器
#####專案基礎環境
mkdir graphql-api
# 進入專案資料夾
cd graphql-api
# 初始化package檔案
npm init # 該命令中的所有步驟全部回車
複製程式碼
為了方便下面的步驟,我們在graphql-api資料夾中手動建立下面的目錄結構,並安裝指定的依賴
# 安裝專案依賴
npm install express express-graphql graphql
複製程式碼
######index.js程式碼
const express = require('express')
const expressGraphql = require('express-graphql')
const app = express()
// 配置路由
app.use('/graphql', expressGraphql(req => {
return {
schema: require('./graphql/schema'), // graphql相關程式碼主目錄
graphiql: true // 是否開啟視覺化工具
// ... 此處還有很多引數,為了簡化文章,此處就一一舉出, 具體可以看 剛才開篇提到的 express文件,
// 也可以在文章末尾拉取專案demo進行查閱
}
}))
// 服務使用3000埠
app.listen(3000, () => {
console.log("graphql server is ok");
});
複製程式碼
######graphql/schema.js程式碼
const {
GraphQLSchema,
GraphQLObjectType
} = require('graphql')
// 規範寫法,宣告query(查詢型別介面) 和 mutation(修改型別介面)
module.exports = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
description: '查詢資料',
fields: () => ({
// 查詢型別介面方法名稱
fetchObjectData: require('./queries/fetchObjectData')
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改資料',
fields: () => ({
// 修改型別介面方法名稱
updateData: require('./mutations/updateData')
})
})
})
複製程式碼
######graphql/queries/fetchObjectData.js程式碼 先在graphql/queries資料夾下建立fetchObjectData.js檔案, 並填入以下程式碼
const {
GraphQLID,
GraphQLInt,
GraphQLFloat,
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 定義介面返回的資料結構
const userType = new GraphQLObjectType({
name: 'userItem',
description: '使用者資訊',
fields: () => ({
id: {
type: GraphQLID,
description: '資料唯一標識'
},
username: {
type: GraphQLString,
description: '使用者名稱'
},
age: {
type: GraphQLInt,
description: '年齡'
},
height: {
type: GraphQLFloat,
description: '身高'
},
isMarried: {
type: GraphQLBoolean,
description: '是否已婚',
deprecationReason: "這個欄位現在不需要了"
}
})
})
// 定義介面
module.exports = {
type: userType,
description: 'object型別資料例子',
// 定義介面傳參的資料結構
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回資料'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
if (isReturn) {
// 返回的資料與前面定義的返回資料結構一致
return {
"id": "5bce2b8c7fde05hytsdsc12c",
"username": "Davis",
"age": 23,
"height": 190.5,
"isMarried": true
}
} else {
return null
}
}
}
複製程式碼
######graphql/mutations/updateData.js程式碼 先在graphql/mutations資料夾下建立updateData.js檔案, 並填入以下程式碼
const {
GraphQLInt
} = require('graphql')
let count = 0
module.exports = {
type: GraphQLInt, // 定義返回的資料型別
description: '修改例子',
args: { // 定義傳參的資料結構
num: {
type: GraphQLInt,
description: '數量'
}
},
resolve: (root, params) => {
let { num } = params
count += num
return count
}
}
複製程式碼
好了,到此為止,簡單的GraphQL伺服器就搭建好了,讓我們來啟動看看
node index.js // 啟動專案
複製程式碼
然後我們在瀏覽器開啟 http://localhost:3000/graphql 如下圖所示
我們可以看到頁面分為3欄,左邊的是呼叫api用的,中間是呼叫api返回的結果 右邊實際上就是我們剛才定義介面相關的東西,也就是api文件。 我們在左邊貼上以下程式碼:
query fetchObjectData {
fetchObjectData(
isReturn: true
) {
id
username
age
height
isMarried
}
}
mutation updateData {
updateData(
num: 2
)
}
複製程式碼
我們點一下左上角的按鈕,會發現我們剛才左邊填的2個方法名都在這列出來了,我們分別選擇 fetchObjectData 和 updateData
api返回結果如下:
自此,我們從建立的GraphQL伺服器就算完成了,程式碼也能正常執行,是否有些小激動!! 彆著急,正所謂授人以魚不如授人以漁,接下來我們細細分析一下我們剛才填寫的GraphQL程式碼,瞭解其規範語法,這樣我們才能真正的掌握GraphQL。
##語法規範解析 以下的程式碼僅為詮釋一下GraphQL的規範用法 不要直接拷貝到前面所述的程式碼中!! 不要直接拷貝到前面所述的程式碼中!! 不要直接拷貝到前面所述的程式碼中!!
1、匯入GraphQL.js及型別 graphql 無論在定義介面引數和介面返回結果時, 都需要先定義好其中所包含資料結構的型別, 這不難理解,可以理解為我們定義的就是資料模型,其中常用的型別如下。
const {
GraphQLList, // 陣列列表
GraphQLObjectType, // 物件
GraphQLString, // 字串
GraphQLInt, // int型別
GraphQLFloat, // float型別
GraphQLEnumType, // 列舉型別
GraphQLNonNull, // 非空型別
GraphQLSchema // schema(定義介面時使用)
} = require('graphql')
複製程式碼
2、定義schema schema例項中,一般規範為 query: 定義查詢類的介面 mutation: 定義修改類的介面
new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query', // 查詢例項名稱
description: '查詢資料', // 介面描述
fields: () => ({
// 查詢型別介面方法名稱
fetchDataApi1: require('./queries/fetchDataApi1'),
fetchDataApi2: require('./queries/fetchDataApi2'),
fetchDataApi3: require('./queries/fetchDataApi3'),
...
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改資料',
fields: () => ({
// 修改型別介面方法名稱
updateDataApi1: require('./mutations/updateDataApi1'),
updateDataApi2: require('./mutations/updateDataApi2'),
...
})
})
})
複製程式碼
3、介面方法定義
// 引用需要用到的資料型別
const {
GraphQLID,
GraphQLString,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 第一部分 定義介面返回的資料結構
// 不難看出來,下面定義的是
/*
{
id
username
}
*/
const userType = new GraphQLObjectType({
name: 'userItem',
description: '使用者資訊',
fields: () => ({
id: {
type: GraphQLID,
description: '資料唯一標識'
},
username: {
type: GraphQLString,
description: '使用者名稱'
}
})
})
// 第二部分 定義介面
module.exports = {
type: userType,
description: 'object型別資料例子',
// 定義介面傳參的資料結構
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回資料'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
// 返回的資料與前面定義的返回資料結構一致
return {
"id": "5bce2b8c7fde05hytsdsc12c",
"username": "Davis"
}
}
}
複製程式碼
我們來分析一下上圖中第一部分的內容,GraphQLObjectType是GraphQL.js定義的物件型別,包括name、description 和fields三個屬性: name: 可作為物件的全域性唯一名稱 description: 是物件的描述 fields: 是解析函式也就是定義具體資料結構, 該物件包含什麼鍵值
我們再來看一下第二部分的內容 type: 代表這個介面需要返回的資料結構是什麼 description: 介面的描述 args: 介面的傳引數據結構定義 resolve: 介面內部具體的實現邏輯(需求程式碼都寫在這裡面) 我們在處理完業務邏輯之後只需要返回與 type 中定義的資料結構一樣的資料即可。
相信這時候你會發現resolve中又有三個引數是什麼鬼,穩住,不要慌,這時候我們看回一開始我們的index.js檔案
省略號這個地方是可以再定義很多引數的,而 rootValue和context這兩個引數正好就對應了介面resolve方法中的root和resolve,我們可以看看官方文件對這兩個引數的解釋: 一般我們可以通過rootValue來做api認證,context可以傳遞使用者資訊、資料庫連結等,也可以不用,這完全取決於你的業務場景總結
1、簡單點說,其實我們在編寫GraphQL 程式碼的時候,你可以理解為他跟typescript有些相似,都需要事先定義好傳參的資料結構,返回結果的資料結構,雖然這個比方不合理,但是這樣講更通俗易懂些 2、description 雖然不是必填的,但是建議都寫上,因為它的作用其實就是api文件的解釋。 3、GraphQL 真的很強大,雖然一開始接觸的時候會有點蒙,啥玩意???這定義那定義的,雖然比RESTful風格的api上手難理解很多,但是我相信,當你學會使用GraphQL的時候,你會發現這玩意還是挺有意思的。 4、GraphQL 的用法還有很多,為了文章儘量簡單易懂, 本文只是列出了其中一種用法,在我的demo中還有更多的用法,比如GraphQLEnumType(列舉型別)、GraphQLUnionType(聯合型別)、GraphQLInterfaceType(介面型別)...有興趣的看一下。
附上本人完整的demo: graphql-api