看完復聯四,我整理了這份 GraphQL 入門教程,哈哈真香。。。html
歡迎關注個人 我的主頁 && 我的博客 && 我的知識庫 && 微信公衆號「前端自習課」
首先有請阿爸鎮貼!哈哈哈,須要高清原圖的小夥伴能夠 點我下載 阿爸無敵 。 前端
下面開始本文內容:node
GraphQL
是 Facebook 開發的一種 API 的查詢語言,與 2015 年公開發布,是 REST API 的替代品。 git
GraphQL
既是一種用於 API 的查詢語言也是一個知足你數據查詢的運行時。 GraphQL
對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端可以準確地得到它須要的數據,並且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。 github
官網: http://graphql.org/
中文網: http://graphql.cn/mongodb
如:hero
中有 name
, age
, sex
等,能夠只取得須要的字段。chrome
典型的 REST API
請求多個資源時得載入多個 URL,而 GraphQL
能夠經過一次請求就獲取你應用所需的全部數據。這樣也能保證在較慢的移動網絡鏈接下,使用 GraphQL
的應用也能表現得足夠迅速。數據庫
GraphQL
使用類型來保證應用只請求可能的數據,還提供了清晰的輔助性錯誤信息。應用可使用類型,而避免編寫手動解析代碼。express
這裏先看下簡單案例,體驗下 GraphQL
的神奇之處(後面詳細介紹)。
咱們這樣定義查詢語句:npm
query { hero }
而後獲得的就是咱們所要查詢的 hero
字段:
{ "data": { "hero": "I'm iron man" } }
這樣用起來,是否是更舒服呢?
全稱:Representational State Transfer
表屬性狀態轉移。
本質上就是定義 uri ,經過 API 接口來取得資源。通用系統架構,不受語言限制。
例子: 餓了嗎接口。
如:接口 restapi/shopping/v3/restaurants?latitude=13
就是個典型的 restful
接口,定義資源 + 查詢條件。
restful
一個接口只能返回一個資源,GraphQL
一次能夠獲取多個資源。restful
用不一樣 url 來區分資源,GraphQL
用類型區分資源。首先建立一個文件夾 demo
,並初始化一個 package.json
,安裝 express
/ graphql
/ express-graphql
依賴包:
npm init -y npm install express graphql express-graphql -S
新建一個 hello.js
,引入文件:
const express = require('express') const { buildSchema } = require('graphql') const graphqlHTTP = require('express-graphql')
建立一個 schema
來定義查詢語句和類型,buildSchema()
方法須要傳入的參數是字符串類型,以下面的 hero
查詢字段,後面的 String
類型表示字段返回的數據類型:
const schema = buildSchema(` type Query { hero: String } `)
建立一個 root
處理器,處理對應的查詢,這裏的 hello
處理器對應的是 schema
中的 hero
字段查詢的處理,這裏直接返回 I'm iron man
的結果:
const root = { hero: () => { return "I'm iron man" } }
固然,處理器中也能夠是其餘複雜操做,後面會介紹。
而後實例化 express
,而且將路由轉發給 graphqlHTTP
處理:
const app = express() app.use('/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true })) app.listen(3000)
graphqlHTTP
中的三個參數介紹:
schema
:定義的查詢語句和類型rootValue
:處理對應查詢的處理器graphiql
:是否開啓調試窗口,開發階段開啓,生產階段關閉接下來運行項目,在命令行中執行 node hello.js
,這裏能夠在 graphiql
上作調試,打開地址 localhost:3000/graphiql
就能夠愉快的查詢了。
另外咱們能夠在 graphiql
界面右側打開 Docs 查看咱們定義的全部字段和描述信息。
最終代碼:
const express = require('express') const { buildSchema } = require('graphql') const graphqlHTTP = require('express-graphql') // 構建schema,這裏定義查詢的語句和類型 const schema = buildSchema(` type Query { hero: String } `) // 定義查詢所對應的 resolver,也就是查詢對應的處理器 const root = { hero: () => { return "I'm iron man" } } const app = express() // 將路由轉發給 graphqlHTTP 處理 app.use('/graphql', graphqlHTTP({ schema: schema, rootValue: root, graphiql: true })) app.listen(3000)
咱們前面的查詢中,已經將 hero
字段定義爲 String
類型,可是經常開發中,咱們又會碰到字段是多個類型,即字段也能指代對象類型(Object),好比一個 user
字段會有 name
、age
等字段,而 name
返回字符串類型,age
返回數值類型。
這時候,咱們能夠對這個對象的字段進行次級選擇(sub-selection)。GraphQL 查詢可以遍歷相關對象及其字段,使得客戶端能夠一次請求查詢大量相關數據,而不像傳統 REST 架構中那樣須要屢次往返查詢。
咱們能夠新建一個查詢類型來定義 user
字段返回的類型:
const schema = buildSchema(` type User { # 查詢能夠有備註! name: String age: Int } type Query { hero: String user: User } `)
在處理器中咱們也要加上:
const root = { hero: () => { return "I'm iron man" }, user: () => { return { name: 'leo', age: 18 } } }
這邊 Int/String 參數類型的問題,下一章介紹
String
, Int
, Float
, Boolean
和 ID
,這些基本參數類型能夠在 schema
聲明中直接使用。
Int
:有符號 32 位整數。Float
:有符號雙精度浮點值。String
:UTF‐8
字符序列。Boolean
:true
或者 false
。ID
:ID
標量類型表示一個惟一標識符,一般用以從新獲取對象或者做爲緩存中的鍵。ID
類型使用和 String
同樣的方式序列化;然而將其定義爲 ID
意味着並不須要人類可讀型。另外,咱們可使用 [類型]
來表示一類數組,如:
[Int]
表示整型數組;[String]
表示字符串型數組;使用方式和 JS 參數傳遞同樣,小括號內定義形參,可是參數須要定義類型。
使用 !
表明參數不能爲空。
下面案例:參數 teamName
是 String
類型,必須傳遞,而 number
參數也是 Int
類型,可是是非必須傳遞,最後輸出的結果也是 String
類型。
type Query { getHero(teamName: String!, number: Int): [String] }
下面一個案例:
//...省略其餘 const schema = buildSchema(` type Query { getHero(teamName: String!): [String] } `) const root = { getHero: ({teamName}) => { // 這裏的操做 實際開發中經常用在請求數據庫 const hero = { '三國': ['張飛', '劉備', '關羽'], '復仇者聯盟': ['鋼鐵俠', '美國隊長', '綠巨人'] } return hero[teamName] } } //...省略其餘
這時候咱們在 GraphiQL 上輸入查詢,就會獲得 復仇者聯盟 的英雄數據了。
// 查詢 query { getHero(teamName:"復仇者聯盟") } // 結果 { "data": { "getHero": [ "鋼鐵俠", "美國隊長", "綠巨人" ] } }
在實際開發中,咱們返回的數據類型多是一個對象,對象中可能既有 Int
類型的屬性,也有 String
類型的值,等等,這裏咱們可使用 自定義返回類型 來處理:
//...省略其餘 const schema = buildSchema(` type Hero { name: String age: Int doSomething(thing: String): String } type Query { getSuperHero(heroName: String!): Hero } `) const root = { getSuperHero: ({heroName}) => { // 這裏的操做 實際開發中經常用在請求數據庫 const name = heroName const age = 18 const doSomething = ({thing}) => { return `I'm ${name}, I'm ${thing} now` } return { name, age, doSomething } } } //...省略其餘
這裏指定了 getSuperHero
字段的返回類型是 Hero
類型,隨後在上面定義了 Hero
。
其中 Hero
類型中的 doSomething
也是能夠傳遞指定類型參數,而且指定返回類型。
下面看下輸出狀況:
// 查詢 query { getSuperHero(heroName:"IronMan") { name age doSomething } } // 結果 { "data": { "getSuperHero": { "name": "IronMan", "age": 46, "doSomething": "I'm IronMan, I'm undefined now" } } }
這裏也能夠給 doSomething
傳遞參數,就會獲取到不一樣結果:
// 查詢 query { getSuperHero(heroName:"IronMan") { name age doSomething(thing:"watching TV") } } // 結果 { "data": { "getSuperHero": { "name": "IronMan", "age": 46, "doSomething": "I'm IronMan, I'm watching TV now" } } }
這一節咱們學習如何在客戶端中訪問 graphql
的接口。
咱們先在後端將接口開發完成,這裏跟前面差很少,但須要多一步,使用 express
向外暴露一個文件夾,供用戶訪問靜態資源文件:
這裏直接使用前一節的代碼啦~
// index.js 開發 graphql 接口 //...省略其餘 const schema = buildSchema(` type Hero { name: String age: Int doSomething(thing: String): String } type Query { getSuperHero(heroName: String!): Hero } `) const root = { getSuperHero: ({heroName}) => { // 這裏的操做 實際開發中經常用在請求數據庫 const name = heroName const age = 46 const doSomething = ({thing}) => { return `I'm ${name}, I'm ${thing} now` } return { name, age, doSomething } } } const app = express() app.use('/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true })) // 公開文件夾 使用戶訪問靜態資源 app.use(express.static('public')) app.listen(3000)
這樣咱們就給前端頁面提供一個能夠訪問靜態資源的功能。
這裏還須要在根目錄建立一個 public 文件夾,並在文件夾中添加 index.html
文件,此時的目錄結構:
|-node_modules |-public |---index.html |-index.js |-package.json
而後給 index.html
添加按鈕和事件綁定:
這裏的變量 query
是個字符串類型,定義查詢條件,在條件 GetSuperHero
中的參數,須要用 $
符號來標識,並在實際查詢 getSuperHero
中,做爲參數的參數類型設置進來。
而後定義變量 variables
,指定屬性的值,以後經過 fetch
發起請求:
<button onclick="getData()">獲取數據</button> <script> function getData(){ const query = ` query GetSuperHero($heroName: String, $thing: String){ getSuperHero(heroName: $heroName){ name age doSomething(thing: $thing) } } ` // 若是不須要其餘參數 至少要傳一個參數 不然會報錯 // const query = ` // query GetSuperHero($heroName: String){ // getSuperHero(heroName: $heroName){ // name // } // } // ` const variables = {heroName: '鋼鐵俠', thing: 'watching TV'} fetch('./graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ query, variables }) }) .then(res => res.json()) .then(json => { console.log(json) }) } </script>
當咱們寫完之後,點擊 獲取數據 就會在控制檯打印下面的數據:
{ "data":{ "getSuperHero":{ "name":"鋼鐵俠", "age":46, "doSomething": "I'm 鋼鐵俠, I'm watching TV now" } } }
query
參數須要對照好有 $
符號的變量。查詢語句 query GetSuperHero($heroName: String)
裏參數 $heroName
中的 heroName
;
查詢語句 getSuperHero(heroName: $heroName)
裏類型 $heroName
中的 heroName
;
變量 variables
中的 heroName
屬性;
這三個名稱須要同樣。
body: JSON.stringify({ query, variables })
根據前面的學習,咱們知道,要作查詢操做,須要使用 Query
來聲明:
type Query { queryHero(heroName: String): String }
當咱們要作修改操做,須要用到的是 Mutation
:
type Mutation { createHero(heroName: String): String }
若是 Mutation
中字段的形參是自定義類型,則類型須要用 input
標識:
const schema = buildSchema(` # 輸入類型 用 input 標識 input HeroInput { name: String age: Int } # 查詢類型 type Hero { name: String age: Int } type Mutation { createHero(heroName: String): Hero updateHero(heroName: String, hero: HeroInput): Hero } `)
注意下:這裏須要至少定義一個 Query
否則 GraphiQL
會不顯示查詢:
type Query { hero: [Hero] }
先建立一個 schema
,內容爲上一步【1. Mutation 使用】中定義的內容,這裏不重複寫。
而後模擬建立一個本地數據庫 localDb
, 用於模擬存放添加的超級英雄數據:
const localDb = {}
接下來聲明 root
實現 schema
中的字段方法:
const root = { hero() { // 這裏須要轉成數組 由於前面定義了返回值是 [Hero] 類型 let arr = [] for(const key in localDb){ arr.push(localDb[key]) } return arr }, createHero({ input }) { // 至關於數據庫的添加操做 localDb[input.name] = input return localDb[input.name] }, updateHero({ id, input }) { // 至關於數據庫的更新操做 const update = Object.assign({}, localDb[id], input) localDb[id] = update return update } }
最後配置 graphqlHTTP
方法和啓動服務器,這裏就很少重複咯。
最終代碼:
//...省略其餘 const schema = buildSchema(` # 輸入類型 用 input 標識 input HeroInput { name: String age: Int } # 查詢類型 type Hero { name: String age: Int } type Mutation { createHero(input: HeroInput): Hero updateHero(id: ID!, input: HeroInput): Hero } # 須要至少定義一個 Query 不要GraphiQL會不顯示查詢 type Query { hero: [Hero] } `) const localDb = {} const root = { hero() { // 這裏須要轉成數組 由於前面定義了返回值是 [Hero] 類型 let arr = [] for(const key in localDb){ arr.push(localDb[key]) } return arr }, createHero({ input }) { // 至關於數據庫的添加操做 localDb[input.name] = input return localDb[input.name] }, updateHero({ id, input }) { // 至關於數據庫的更新操做 const update = Object.assign({}, localDb[id], input) localDb[id] = update return update } } //...省略其餘
如今咱們能夠啓動服務器,在 GraphiQL
上測試下效果了。
咱們是使用 mutation
的 createHero
字段添加兩條數據:
mutation { createHero(input: { name: "鋼鐵俠" age: 40 }){ name age } }
mutation { createHero(input: { name: "美國隊長" age: 41 }){ name age } }
而後使用 query
的 hero
字段查詢添加的結果:
query { hero { name age } }
這樣咱們就獲取到剛纔的添加結果:
{ "data": { "hero": [ { "name": "鋼鐵俠", "age": 40 }, { "name": "美國隊長", "age": 41 } ] } }
而後咱們開始更新數據,使用 mutation
的 updateHero
字段將 美國隊長 的 age
值修改成 18:
mutation { updateHero(id: "美國隊長", input: { age: 18 }){ age } }
再使用 query
的 hero
字段查詢下新的數據,會發現 美國隊長 的 age
值已經更新爲 18:
{ "data": { "hero": [ { "name": "鋼鐵俠", "age": 40 }, { "name": "美國隊長", "age": 18 } ] } }
咱們知道,修改數據的接口不能讓全部人隨意訪問,因此須要添加權限認證,讓有權限的人才能夠訪問。
在 express
中,能夠很簡單的使用中間件來將請求進行攔截,將沒有權限的請求過濾並返回錯誤提示。
中間件其實是一個函數,在接口執行以前,先攔截請求,再決定咱們是否接着往下走,仍是返回錯誤提示。
這在【6、使用Mutations修改數據】的最終代碼上,在添加這個中間件:
//... 省略其餘 const app = express() const middleWare = (req, res, next) => { // 這裏是簡單模擬權限 // 實際開發中 更多的是和後端進行 token 交換來判斷權限 if(req.url.indexOf('/graphql') !== -1 && req.headers.cookie.indexOf('auth') === -1){ // 向客戶端返回一個錯誤信息 res.send(JSON.stringify({ err: '暫無權限' })) return } next() // 正常下一步 } // 註冊中間件 app.use(middleWare) //... 省略其餘
這裏的權限判斷,只是簡單模擬,實際開發中,更多的是和後端進行 token
交換來判斷權限(或者其餘形式)。
咱們重啓服務器,打開 http://localhost:3000/graphql
,發現頁面提示錯誤了,由於 cookies
中沒有含有 auth
字符串。
若是這裏提示 TypeError: Cannot read property 'indexOf' of undefined
,能夠先不用管,由於瀏覽器中沒有 cookies
的緣由,其實前面的權限判斷邏輯須要根據具體業務場景判斷。
爲了方便測試,咱們在 chrome 瀏覽器控制檯的 application
下,手動設置一個含有 auth
字符串的一個 cookies
,只是測試使用哦。
設置完成後,咱們就能正常進入頁面。
在前面的介紹中,咱們要建立一個 schema
都是使用 buildSchema
方法來定義,但咱們也可使用另一種定義方式。
就是這裏要學習使用的構造函數 graphql.GraphQLObjectType
定義,它有這麼幾個優勢和缺點:
這裏先將前面定義的 Hero
類型進行改造:
const graphql = require('graphql') // 須要引入 const HeroType = new graphql.GraphQLObjectType({ name: 'Hero', fields: { name:{ type: graphql.GraphQLString }, age:{ type: graphql.GraphQLInt }, } })
二者區別在於:
區別 | buildSchema |
graphql.GraphQLObjectType |
---|---|---|
參數類型 | 字符串 | 對象 |
類名 | 跟在 type 字符後面,這裏是 type Hero |
在參數對象的 name 屬性上 |
屬性定義 | 定義在類型後,鍵值對形式 | 定義在參數對象 fields 屬性中,值爲對象,每一個屬性名爲鍵名,值也是對象,其中 type 屬性的值爲 graphql 中的屬性,下面會補充 |
補充: fields
屬性中的子屬性的類型一般有:
graphql.GraphQLString
graphql.GraphQLInt
graphql.GraphQLBoolean
....
即在 GraphQL
後面跟上基本類型名稱。
定義查詢的時候,跟以前相似,能夠參照下面對比圖理解,這裏比較不一樣的是,多了個 resolve
的方法,這個方法是用來執行處理查詢的邏輯,其實就是以前的 root
中的方法。
const QueryType = new graphql.GraphQLObjectType({ name: 'Query', fields: { // 一個個查詢方法 getSuperHero: { type: HeroType, args: { heroName: { type: graphql.GraphQLString } }, // 方法實現 查詢的處理函數 resolve: function(_, { heroName }){ const name = heroName const age = 18 return { name, age } } } } })
建立的時候只需實例化而且將參數傳入便可:
// step3 構造 schema const schema = new graphql.GraphQLSchema({ query: QueryType})
最後使用也是和前面同樣:
const app = express() app.use('/graphql', graphqlHTTP({ schema, graphiql: true })) app.listen(3000)
咱們試着使用前面所學的內容,開發一個簡單的實踐項目:
經過 GraphiQL
頁面,往 Mongodb
中插入和更新數據,主要用到【6、使用Mutations修改數據】章節的操做。
首先咱們能夠到 Mongodb 官網 選擇對應平臺和版本下載安裝。
下載安裝步驟,能夠參考 mongoDB下載、安裝和配置,這裏就很少介紹喲~~
安裝完成後,咱們打開兩個終端,分別執行下面兩行命令:
// 終端1 啓動數據庫 mongod --dbpath c:\leo\app\mongodb\data\db // 終端2 進入數據庫命令行操做模式 mongo
首先咱們新建一個文件 db.js
,並 npm install mongoose
安裝 mongoose
,而後寫入下面代碼,實現鏈接數據庫:
const express = require('express') const { buildSchema } = require('graphql') const graphqlHTTP = require('express-graphql') const mongoose = require('mongoose') const DB_PATH = 'mongodb://127.0.0.1:27017/hero_table' const connect = () => { // 鏈接數據庫 mongoose.connect(DB_PATH) // 鏈接斷開 mongoose.connection.on('disconnected', () => { mongoose.connect(DB_PATH) }) // 鏈接失敗 mongoose.connection.on('error', err => { console.error(err) }) // 鏈接成功 mongoose.connection.on('connected', async () => { console.log('Connected to MongoDB connected', DB_PATH) }) } connect()
而後建立 Schema
和 Model
:
let HeroSchema = new mongoose.Schema({ name: String, age: Number }) let HeroModel = mongoose.model('hero',HeroSchema, 'hero_table')
這一步,仍是先使用【6、使用Mutations修改數據】章節的操做邏輯,也就是先用字符串建立查詢,而不使用 GraphQLObjectType
建立:
const schema = buildSchema(` # 輸入類型 用 input 標識 input HeroInput { name: String age: Int } # 查詢類型 type Hero { name: String age: Int } type Mutation { createHero(input: HeroInput): Hero updateHero(hero: String!, input: HeroInput): Hero } # 須要至少定義一個 Query 不要GraphiQL會不顯示查詢 type Query { hero: [Hero] } `)
這邊案例有稍做修改
這邊處理添加數據和更新數據的邏輯,就要修改以前聲明的 root
的操做內容了:
const root = { hero() { return new Promise( (resolve, reject) => { HeroModel.find((err, res) => { if(err) { reject(err) return } resolve(res) }) }) }, createHero({ input }) { // 實例化一個Model const { name, age } = input const params = new HeroModel({ name, age }) return new Promise( (resolve, reject) => { params.save((err, res) => { if(err) { reject(err) return } resolve(res) }) }) }, updateHero({ hero, input }) { const { age } = input return new Promise ((resolve, reject) => { HeroModel.update({name: hero}, {age}, (err, res) => { if(err) { reject(err) return } resolve(res) }) }) } }
最後咱們在 GraphiQL
頁面上模擬測試一下,首先添加兩個英雄,鋼鐵俠和美國隊長,並設置他們的 age / name
屬性:
mutation { createHero(input: { name: "鋼鐵俠" age: 40 }){ name age } }
mutation { createHero(input: { name: "美國隊長" age: 20 }){ name age } }
頁面和接口沒有報錯,說明咱們添加成功,數據庫中也有這兩條數據了:
在測試下查詢:
query { hero { name age } }
查詢也正常,接下來測試下更新,將美國隊長的 age
修改成 60:
mutation { updateHero(hero: "美國隊長", input: { age: 60 }){ age } }
到這一步,咱們也算是將這個練習作完了。
GraphQL
是一種 API 的查詢語言,是 REST API 的替代品。GraphQL
可使用一個請求,獲取全部想要的數據。buildSchema
或者 GraphQLObjectType
。Query
,修改操做用 Mutations
。type
,輸入類型用 input
。其實 GraphQL
仍是很簡單好用的呢~~~
本文首發在 pingan8787我的博客,如需轉載請保留我的介紹