node基於express的GraphQL API伺服器

? JIEYUFENG發表於2019-01-24

相信有很多仁兄在2018年底都看到過 2018 JavaScript 現狀調查報告 這篇文章。其中有一張圖甚是有趣:

node基於express的GraphQL API伺服器
可以看得出來 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 如下圖所示

node基於express的GraphQL API伺服器

我們可以看到頁面分為3欄,左邊的是呼叫api用的,中間是呼叫api返回的結果 右邊實際上就是我們剛才定義介面相關的東西,也就是api文件。 我們在左邊貼上以下程式碼:

query fetchObjectData {
  fetchObjectData(
    isReturn: true
  ) {
    id
    username
  	age
    height
    isMarried
  }
}

mutation updateData {
  updateData(
    num: 2
  )
}
複製程式碼

node基於express的GraphQL API伺服器
我們點一下左上角的按鈕,會發現我們剛才左邊填的2個方法名都在這列出來了,我們分別選擇 fetchObjectData 和 updateData
node基於express的GraphQL API伺服器
api返回結果如下:
fetchObjectData 結果
updateData 結果

自此,我們從建立的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檔案

index.js部分程式碼截圖
省略號這個地方是可以再定義很多引數的,而 rootValue和context這兩個引數正好就對應了介面resolve方法中的root和resolve,我們可以看看官方文件對這兩個引數的解釋:
node基於express的GraphQL API伺服器
一般我們可以通過rootValue來做api認證,context可以傳遞使用者資訊、資料庫連結等,也可以不用,這完全取決於你的業務場景

總結

1、簡單點說,其實我們在編寫GraphQL 程式碼的時候,你可以理解為他跟typescript有些相似,都需要事先定義好傳參的資料結構,返回結果的資料結構,雖然這個比方不合理,但是這樣講更通俗易懂些 2、description 雖然不是必填的,但是建議都寫上,因為它的作用其實就是api文件的解釋。 3、GraphQL 真的很強大,雖然一開始接觸的時候會有點蒙,啥玩意???這定義那定義的,雖然比RESTful風格的api上手難理解很多,但是我相信,當你學會使用GraphQL的時候,你會發現這玩意還是挺有意思的。 4、GraphQL 的用法還有很多,為了文章儘量簡單易懂, 本文只是列出了其中一種用法,在我的demo中還有更多的用法,比如GraphQLEnumType(列舉型別)、GraphQLUnionType(聯合型別)、GraphQLInterfaceType(介面型別)...有興趣的看一下。

附上本人完整的demo: graphql-api

相關文章