GraphQL入門有這一篇就足夠了:http://www.javashuo.com/article/p-hfjoxbmn-mg.htmlhtml
版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/qq_41882147/article/details/82966783
本文將從GraphQL是什麼,爲何要使用GraphQL,使用GraphQL建立簡單例子,以及GraphQL實戰,四個方面對GraphQL進行闡述。說得不對的地方,但願你們指出斧正。前端
github項目地址:https://github.com/Charming2015/graphql-todolistvue
1、GraphQL是什麼?
關於GraphQL是什麼,網上一搜一大堆。根據官網的解釋就是一種用於 API 的查詢語言。node
一看到用於API的查詢語言,我也是一臉懵逼的。博主你在開玩笑吧?你的翻譯水平不過關?API還能查嗎?API不是後端寫好,前端調用的嗎?ios
的確能夠,這就是GraphQL強大的地方。
引用官方文檔的一句話:git
ask exactly what you want.程序員
2、爲何要使用GraphQL?
在實際工做中每每會有這種情景出現:好比說我須要展現一個遊戲名的列表,可接口卻會把遊戲的詳細玩法,更新時間,建立者等各類各樣的 (無用的) 信息都一同返回。es6
問了後端,緣由大概以下:github
原來是爲了兼容PC端和移動端用同一套接口
或者在整個頁面,這裏須要顯示遊戲的標題,但是別的地方須要顯示遊戲玩法啊,避免屢次請求我就所有返回咯
或者是由於有時候項目經理想要顯示「標題+更新時間」,有時候想要點擊標題展開遊戲玩法等等需求,因此把遊戲相關的信息都一同返回
簡單說就是:web
兼容多平臺致使字段冗餘
一個頁面須要屢次調用 API 聚合數據
需求常常改動致使接口很難爲單一接口精簡邏輯
有同窗可能會說那也不必定要用GraphQL啊,比方說第一個問題,不一樣平臺不一樣接口不就行了嘛
http://api.xxx.com/web/getGameInfo/:gameID
http://api.xxx.com/app/getGameInfo/:gameID
http://api.xxx.com/mobile/getGameInfo/:gameID
1
2
3
或者加個參數也行
http://api.xxx.com/getGameInfo/:gameID?platfrom=web
1
這樣處理的確能夠解決問題,可是無疑加大了後端的處理邏輯。你真的不怕後端程序員打你?
這個時候咱們會想,接口能不能不寫死,把靜態變成動態?
回答是能夠的,這就是GraphQL所作的!
3、GraphQL嚐嚐鮮——(GraphQL簡單例子)
下面是用GraphQL.js和express-graphql搭建一個的普通GraphQL查詢(query)的例子,包括講解GraphQL的部分類型和參數,已經掌握了的同窗能夠跳過。
1. 先跑個hello world
新建一個graphql文件夾,而後在該目錄下打開終端,執行npm init --y初始化一個packjson文件。
安裝依賴包:npm install --save -D express express-graphql graphql
新建sehema.js文件,填上下面的代碼
//schema.js
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
} = require('graphql');
const queryObj = new GraphQLObjectType({
name: 'myFirstQuery',
description: 'a hello world demo',
fields: {
hello: {
name: 'a hello world query',
description: 'a hello world demo',
type: GraphQLString,
resolve(parentValue, args, request) {
return 'hello world !';
}
}
}
});
module.exports = new GraphQLSchema({
query: queryObj
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
這裏的意思是新建一個簡單的查詢,查詢名字叫hello,會返回字段hello world !,其餘的是定義名字和查詢結果類型的意思。
同級目錄下新建server.js文件,填上下面的代碼
// server.js
const express = require('express');
const expressGraphql = require('express-graphql');
const app = express();
const schema = require('./schema');
app.use('/graphql', expressGraphql({
schema,
graphiql: true
}));
app.get('/', (req, res) => res.end('index'));
app.listen(8000, (err) => {
if(err) {throw new Error(err);}
console.log('*** server started ***');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
這部分代碼是用express跑起來一個服務器,並經過express-graphql把graphql掛載到服務器上。
運行一下node server,並打開http://localhost:8000/
如圖,說明服務器已經跑起來了
打開http://localhost:8000/graphql,是相似下面這種界面說明已經graphql服務已經跑起來了!
在左側輸入 (graphql的查詢語法這裏不作說明)
{
hello
}
1
2
3
點擊頭部的三角形的運行按鈕,右側就會顯示你查詢的結果了
2. 不只僅是hello world
先簡單講解一下代碼:
const queryObj = new GraphQLObjectType({
name: 'myFirstQuery',
description: 'a hello world demo',
fields: {}
});
1
2
3
4
5
GraphQLObjectType是GraphQL.js定義的對象類型,包括name、description 和fields三個屬性,其中name和description 是非必填的。fields是解析函數,在這裏能夠理解爲查詢方法
hello: {
name: 'a hello world query',
description: 'a hello world demo',
type: GraphQLString,
resolve(parentValue, args, request) {
return 'hello world !';
}
}
1
2
3
4
5
6
7
8
對於每一個fields,又有name,description,type,resolve參數,這裏的type能夠理解爲hello方法返回的數據類型,resolve就是具體的處理方法。
說到這裏有些同窗可能還不知足,若是我想每次查詢都想帶上一個參數該怎麼辦,若是我想查詢結果有多條數據又怎麼處理?
下面修改schema.js文件,來一個增強版的查詢(固然,你能夠整理一下代碼,我這樣寫是爲了方便閱讀)
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLBoolean
} = require('graphql');
const queryObj = new GraphQLObjectType({
name: 'myFirstQuery',
description: 'a hello world demo',
fields: {
hello: {
name: 'a hello world query',
description: 'a hello world demo',
type: GraphQLString,
args: {
name: { // 這裏定義參數,包括參數類型和默認值
type: GraphQLString,
defaultValue: 'Brian'
}
},
resolve(parentValue, args, request) { // 這裏演示如何獲取參數,以及處理
return 'hello world ' + args.name + '!';
}
},
person: {
name: 'personQuery',
description: 'query a person',
type: new GraphQLObjectType({ // 這裏定義查詢結果包含name,age,sex三個字段,而且都是不一樣的類型。
name: 'person',
fields: {
name: {
type: GraphQLString
},
age: {
type: GraphQLInt
},
sex: {
type: GraphQLBoolean
}
}
}),
args: {
name: {
type: GraphQLString,
defaultValue: 'Charming'
}
},
resolve(parentValue, args, request) {
return {
name: args.name,
age: args.name.length,
sex: Math.random() > 0.5
};
}
}
}
});
module.exports = new GraphQLSchema({
query: queryObj
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
重啓服務後,繼續打開http://localhost:8000/graphql,在左側輸入
{
hello(name:"charming"),
person(name:"charming"){
name,
sex,
age
}
}
1
2
3
4
5
6
7
8
右側就會顯示出:
你能夠在左側僅輸入person方法的sex和age兩個字段,這樣就會只返回sex和age的信息。動手試一試吧!
{
person(name:"charming"){
sex,
age
}
}
1
2
3
4
5
6
固然,結果的順序也是按照你輸入的順序排序的。
定製化的數據,徹底根據你查什麼返回什麼結果。這就是GraphQL被稱做API查詢語言的緣由。
4、GraphQL實戰
下面我將搭配koa實現一個GraphQL查詢的例子,逐步從簡單koa服務到mongodb的數據插入查詢,再到GraphQL的使用,最終實現用GraphQL對數據庫進行增刪查改。
項目效果大概以下:
有點意思吧?那就開始吧~
先把文件目錄建構建好
1. 初始化項目
初始化項目,在根目錄下運行npm init --y,
而後安裝一些包:npm install koa koa-static koa-router koa-bodyparser --save -D
新建config、controllers、graphql、mongodb、public、router這幾個文件夾。裝逼的操做是在終端輸入mkdir config controllers graphql mongodb public router回車,ok~
2. 跑一個koa服務器
新建一個server.js文件,寫入如下代碼
// server.js
import Koa from 'koa'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'
const app = new Koa()
const router = new Router();
const port = 4000
app.use(bodyParser());
router.get('/hello', (ctx, next) => {
ctx.body="hello world"
});
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log('server listen port: ' + port)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
執行node server跑起來服務器,發現報錯了:
這是正常的,這是由於如今的node版本並無支持es6的模塊引入方式。
百度一下就會有解決方案了,比較通用的作法是用babel-polyfill進行轉譯。
詳細的能夠看這一個參考操做:How To Enable ES6 Imports in Node.JS
具體操做是:新建一個start.js文件,寫入:
// start.js
require('babel-register')({
presets: [ 'env' ]
})
require('babel-polyfill')
require('./server.js')
1
2
3
4
5
6
安裝相關包:npm install --save -D babel-preset-env babel-polyfill babel-register
修改package.json文件,把"start": "start http://localhost:4000 && node start.js"這句代碼加到下面這個位置:
運行一下npm run start,打開http://localhost:4000/hello,結果如圖:
說明koa服務器已經跑起來了。
那麼前端頁面呢?
(因爲本文內容不是講解前端,因此前端代碼自行去github複製)
在public下新建index.html文件和js文件夾,代碼直接查看個人項目public目錄下的 index.html 和 index-s1.js 文件
修改server.js,引入koa-static模塊。koa-static會把路由的根目錄指向本身定義的路徑(也就是本項目的public路徑)
//server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'
const app = new Koa()
const router = new Router();
const port = 4000
app.use(bodyParser());
router.get('/hello', (ctx, next) => {
ctx.body="hello world"
});
app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log('server listen port: ' + port)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
打開http://localhost:4000/,發現是相似下面的頁面:
這時候頁面已經能夠進行簡單的交互,可是尚未和後端進行數據交互,因此是個靜態頁面。
3. 搭一個mongodb數據庫,實現數據增刪改查
注意: 請先自行下載好mongodb並啓動mongodb。
a. 寫好連接數據庫的基本配置和表設定
在config文件夾下面創建一個index.js,這個文件主要是放一下連接數據庫的配置代碼。
// config/index.js
export default {
dbPath: 'mongodb://localhost/todolist'
}
1
2
3
4
在mongodb文件夾新建一個index.js和 schema文件夾, 在 schema文件夾文件夾下面新建list.js
在mongodb/index.js下寫上連接數據庫的代碼,這裏的代碼做用是連接上數據庫
// mongodb/index.js
import mongoose from 'mongoose'
import config from '../config'
require('./schema/list')
export const database = () => {
mongoose.set('debug', true)
mongoose.connect(config.dbPath)
mongoose.connection.on('disconnected', () => {
mongoose.connect(config.dbPath)
})
mongoose.connection.on('error', err => {
console.error(err)
})
mongoose.connection.on('open', async () => {
console.log('Connected to MongoDB ', config.dbPath)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在mongodb/schema/list.js定義表和字段:
//mongodb/schema/list.js
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
const ListSchema = new Schema({
title: String,
desc: String,
date: String,
id: String,
checked: Boolean,
meta: {
createdAt: {
type: Date,
default: Date.now()
},
updatedAt: {
type: Date,
default: Date.now()
}
}
})
ListSchema.pre('save', function (next) {// 每次保存以前都插入更新時間,建立時插入建立時間
if (this.isNew) {
this.meta.createdAt = this.meta.updatedAt = Date.now()
} else {
this.meta.updatedAt = Date.now()
}
next()
})
mongoose.model('List', ListSchema)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
b. 實現數據庫增刪查改的控制器
建好表,也連接好數據庫以後,咱們就要寫一些方法來操做數據庫,這些方法都寫在控制器(controllers)裏面。
在controllers裏面新建list.js,這個文件對應操做list數據的控制器,單獨拿出來寫是爲了方便後續項目複雜化的模塊化管理。
// controllers/list.js
import mongoose from 'mongoose'
const List = mongoose.model('List')
// 獲取全部數據
export const getAllList = async (ctx, next) => {
const Lists = await List.find({}).sort({date:-1}) // 數據查詢
if (Lists.length) {
ctx.body = {
success: true,
list: Lists
}
} else {
ctx.body = {
success: false
}
}
}
// 新增
export const addOne = async (ctx, next) => {
// 獲取請求的數據
const opts = ctx.request.body
const list = new List(opts)
const saveList = await list.save() // 保存數據
console.log(saveList)
if (saveList) {
ctx.body = {
success: true,
id: opts.id
}
} else {
ctx.body = {
success: false,
id: opts.id
}
}
}
// 編輯
export const editOne = async (ctx, next) => {
const obj = ctx.request.body
let hasError = false
let error = null
List.findOne({id: obj.id}, (err, doc) => {
if(err) {
hasError = true
error = err
} else {
doc.title = obj.title;
doc.desc = obj.desc;
doc.date = obj.date;
doc.save();
}
})
if (hasError) {
ctx.body = {
success: false,
id: obj.id
}
} else {
ctx.body = {
success: true,
id: obj.id
}
}
}
// 更新完成狀態
export const tickOne = async (ctx, next) => {
const obj = ctx.request.body
let hasError = false
let error = null
List.findOne({id: obj.id}, (err, doc) => {
if(err) {
hasError = true
error = err
} else {
doc.checked = obj.checked;
doc.save();
}
})
if (hasError) {
ctx.body = {
success: false,
id: obj.id
}
} else {
ctx.body = {
success: true,
id: obj.id
}
}
}
// 刪除
export const delOne = async (ctx, next) => {
const obj = ctx.request.body
let hasError = false
let msg = null
List.remove({id: obj.id}, (err, doc) => {
if(err) {
hasError = true
msg = err
} else {
msg = doc
}
})
if (hasError) {
ctx.body = {
success: false,
id: obj.id
}
} else {
ctx.body = {
success: true,
id: obj.id
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
c. 實現路由,給前端提供API接口
數據模型和控制器都已經設計好了,下面就利用koa-router路由中間件,來實現請求的接口。
咱們回到server.js,在上面添加一些代碼。以下:
// server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'
import {database} from './mongodb'
import {addOne, getAllList, editOne, tickOne, delOne} from './controllers/list'
database() // 連接數據庫而且初始化數據模型
const app = new Koa()
const router = new Router();
const port = 4000
app.use(bodyParser());
router.get('/hello', (ctx, next) => {
ctx.body = "hello world"
});
// 把對請求的處理交給處理器。
router.post('/addOne', addOne)
.post('/editOne', editOne)
.post('/tickOne', tickOne)
.post('/delOne', delOne)
.get('/getAllList', getAllList)
app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log('server listen port: ' + port)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
上面的代碼,就是作了:
1. 引入mongodb設置、list控制器,
2. 連接數據庫
3. 設置每個設置每個路由對應的咱們定義的的控制器。
1
2
3
安裝一下mongoose:npm install --save -D mongoose
運行一下npm run start,待咱們的服務器啓動以後,就能夠對數據庫進行操做了。咱們能夠經過postman來模擬請求,先插幾條數據:
查詢所有數據:
d. 前端對接接口
前端直接用ajax發起請求就行了,平時工做中都是用axios的,可是我懶得弄,因此直接用最簡單的方法就行了。
引入了JQuery以後,改寫public/js/index.js文件:略(項目裏的public/index-s2.js的代碼)
項目跑起來,發現已經基本上實現了前端發起請求對數據庫進行操做了。
至此你已經成功打通了前端後臺數據庫,能夠不要臉地稱本身是一個小全棧了!
不過咱們的目的尚未達到——用grapql實現對數據的操做!
4. 用grapql實現對數據的操做
GraphQL 的大部分討論集中在數據獲取(query),可是任何完整的數據平臺也都須要一個改變服務端數據的方法。
REST 中,任何請求均可能最後致使一些服務端反作用,可是約定上建議不要使用 GET 請求來修改數據。GraphQL 也是相似 —— 技術上而言,任何查詢均可以被實現爲致使數據寫入。然而,建一個約定來規範任何致使寫入的操做都應該顯式經過變動(mutation)來發送。
簡單說就是,GraphQL用mutation來實現數據的修改,雖然mutation能作的query也能作,但仍是要區分開這連個方法,就如同REST中約定用GET來請求數據,用其餘方法來更新數據同樣。
a. 實現查詢
查詢的話比較簡單,只須要在接口響應時,獲取數據庫的數據,而後返回;
const objType = new GraphQLObjectType({
name: 'meta',
fields: {
createdAt: {
type: GraphQLString
},
updatedAt: {
type: GraphQLString
}
}
})
let ListType = new GraphQLObjectType({
name: 'List',
fields: {
_id: {
type: GraphQLID
},
id: {
type: GraphQLString
},
title: {
type: GraphQLString
},
desc: {
type: GraphQLString
},
date: {
type: GraphQLString
},
checked: {
type: GraphQLBoolean
},
meta: {
type: objType
}
}
})
const listFields = {
type: new GraphQLList(ListType),
args: {},
resolve (root, params, options) {
return List.find({}).exec() // 數據庫查詢
}
}
let queryType = new GraphQLObjectType({
name: 'getAllList',
fields: {
lists: listFields,
}
})
export default new GraphQLSchema({
query: queryType
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
把增刪查改都講完再更改代碼~
b. 實現增刪查改
一開始說了,其實mutation和query用法上沒什麼區別,這只是一種約定。
具體的mutation實現方式以下:
const outputType = new GraphQLObjectType({
name: 'output',
fields: () => ({
id: { type: GraphQLString},
success: { type: GraphQLBoolean },
})
});
const inputType = new GraphQLInputObjectType({
name: 'input',
fields: () => ({
id: { type: GraphQLString },
desc: { type: GraphQLString },
title: { type: GraphQLString },
date: { type: GraphQLString },
checked: { type: GraphQLBoolean }
})
});
let MutationType = new GraphQLObjectType({
name: 'Mutations',
fields: () => ({
delOne: {
type: outputType,
description: 'del',
args: {
id: { type: GraphQLString }
},
resolve: (value, args) => {
console.log(args)
let result = delOne(args)
return result
}
},
editOne: {
type: outputType,
description: 'edit',
args: {
listObj: { type: inputType }
},
resolve: (value, args) => {
console.log(args)
let result = editOne(args.listObj)
return result
}
},
addOne: {
type: outputType,
description: 'add',
args: {
listObj: { type: inputType }
},
resolve: (value, args) => {
console.log(args.listObj)
let result = addOne(args.listObj)
return result
}
},
tickOne: {
type: outputType,
description: 'tick',
args: {
id: { type: GraphQLString },
checked: { type: GraphQLBoolean },
},
resolve: (value, args) => {
console.log(args)
let result = tickOne(args)
return result
}
},
}),
});
export default new GraphQLSchema({
query: queryType,
mutation: MutationType
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
c. 完善其他代碼
在實現前端請求Graphql服務器時,最困擾個人就是參數以什麼樣的格式進行傳遞。後來在Graphql界面玩Graphql的query請求時發現了其中的訣竅…
關於前端請求格式進行一下說明:
如上圖,在玩Graphql的請求時,咱們就能夠直接在控制檯network查看請求的格式了。這裏咱們只須要模仿這種格式,當作參數發送給Graphql服務器便可。
記得用反引號: `` ,來拼接參數格式。而後用data: {query: params}的格式傳遞參數,代碼以下:
let data = {
query: `mutation{
addOne(listObj:{
id: "${that.getUid()}",
desc: "${that.params.desc}",
title: "${that.params.title}",
date: "${that.getTime(that.params.date)}",
checked: false
}){
id,
success
}
}`
}
$.post('/graphql', data).done((res) => {
console.log(res)
// do something
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最後更改server.js,router/index.js,controllers/list.js,public/index.js改爲github項目對應目錄的文件代碼便可。
完整項目的目錄以下:
5、後記
對於Vue開發者,可使用vue-apollo使得前端傳參更加優雅~
對上文有疑問或者有建議意見的,能夠加我QQ:820327571,並備註:Graphql
6、參考文獻graphql官網教程GraphQL.js30分鐘理解GraphQL核心概念個人前端故事----我爲何用GraphQLGraphQL 搭配 Koa 最佳入門實踐————————————————版權聲明:本文爲CSDN博主「__Charming__」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/qq_41882147/article/details/82966783