GraphQL一種用爲你 API 而生的查詢語言,2018已經到來,PWA尚未大量投入生產應用之中就已經火起來了,GraphQL的應用或許也不會太遠了。前端的發展的最大一個特色就是變化快,有時候應對各類需求場景的變化,不得不去對接口開發不少版本或者修改。各類業務依賴強大的基礎數據平臺快速生長,如何高效地爲各類業務提供數據支持,是全部人關心的問題。並且如今前端的解決方案是將視圖組件化,各個業務線既能夠是組件的使用者,也能夠是組件的生產者,若是可以將其中通用的內容抽取出來提供給各個業務方反覆使用,必然可以節省寶貴的開發時間和開發人力。那麼問題來了,前端經過組件實現了跨業務的複用,後端接口如何相應地提升開發效率呢?GraphQL,就是應對複雜場景的一種新思路。javascript
官方解釋:css
GraphQL 既是一種用於 API 的查詢語言也是一個知足你數據查詢的運行時。 GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端可以準確地得到它須要的數據,並且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。html
下面介紹一下GraphQL的有哪些好處:前端
請求你所要的數據很少很多java
獲取多個資源只用一個請求node
自定義接口數據的字段jquery
強大的開發者工具git
API 演進無需劃分版本es6
本篇文章中將搭配koa實現一個GraphQL查詢的例子,逐步從簡單kao服務到mongodb的數據插入查詢再到GraphQL的使用, 讓你們快速看到:github
項目以下圖所示
一、搭建GraphQL工具查詢界面。
二、前端用jq發送ajax的使用方式
入門項目咱們都已是預覽過了,下面咱們動手開發吧!!!
首先創建一個項目文件夾,而後在這個項目文件夾新建一個server.js
(node服務)、config文件夾
、mongodb文件夾
、router文件夾
、controllers文件夾
以及public文件夾
(這個主要放前端靜態數據展現頁面),好啦,項目的結構咱們都已經創建好,下面在server.js
文件夾裏寫上
server.js
// 引入模塊
import Koa from 'koa'
import KoaStatic from 'koa-static'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'
const app = new Koa()
const router = new Router();
// 使用 bodyParser 和 KoaStatic 中間件
app.use(bodyParser());
app.use(KoaStatic(__dirname + '/public'));
// 路由設置test
router.get('/test', (ctx, next) => {
ctx.body="test page"
});
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(4000);
console.log('graphQL server listen port: ' + 4000)
複製代碼
在命令行npm install koa koa-static koa-router koa-bodyparser --save
安裝好上面幾個模塊,
而後運行node server.js
,不出什麼意外的話,你會發現報以下圖的一個error
緣由是如今的node版本並無支持es6的模塊引入方式。
放心 咱們用神器babel-polyfill
轉譯一下就闊以了。詳細的請看阮一峯老師的這篇文章
下面在項目文件夾新建一個start.js
,而後在裏面寫上如下代碼:
start.js
require('babel-core/register')({
'presets': [
'stage-3',
["latest-node", { "target": "current" }]
]
})
require('babel-polyfill')
require('./server')
複製代碼
而後 在命令行,運行npm install babel-core babel-polyfill babel-preset-latest-node babel-preset-stage-3 --save-dev
安裝幾個開發模塊。
安裝完畢以後,在命令行運行 node start.js
,以後你的node服務安靜的運行起來了。用koa-router中間件作咱們項目路由模塊的管理,後面會寫到router文件夾
中統一管理。
打開瀏覽器,輸入localhost:4000/test
,你就會發現訪問這個路由node服務會返回test page
文字。以下圖
yeah~~kao服務器基本搭建好以後,下面就是,連接mongodb
而後把數據存儲到mongodb
數據庫裏面啦。
tip:這裏咱們須要mongodb
存儲數據以及利用mongoose
模塊操做mongodb
數據庫
在mongodb文件夾
新建一個index.js
和 schema文件夾
, 在 schema文件夾
文件夾下面新建info.js
和student.js
。
在config文件夾
下面創建一個index.js
,這個文件主要是放一下配置代碼。
又一波文件創建好以後,先在config/index.js
下寫上連接數據庫配置的代碼。
config/index.js
export default {
dbPath: 'mongodb://localhost/graphql'
}
複製代碼
而後在mongodb/index.js
下寫上連接數據庫的代碼。
mongodb/index.js
// 引入mongoose模塊
import mongoose from 'mongoose'
import config from '../config'
// 同步引入 info model和 studen model
require('./schema/info')
require('./schema/student')
// 連接mongodb
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)
})
}
複製代碼
上面咱們咱們代碼還加載了info.js
和 studen.js
這兩個分別是學生的附加信息和基本信息的數據模型,爲何會分紅兩個信息表?緣由是順便給你們介紹一下聯表查詢的基本方法(嘿嘿~~~)
下面咱們分別完成這兩個數據模型
mongodb/schema/info.js
// 引入mongoose
import mongoose from 'mongoose'
//
const Schema = mongoose.Schema
// 實例InfoSchema
const InfoSchema = new Schema({
hobby: [String],
height: String,
weight: Number,
meta: {
createdAt: {
type: Date,
default: Date.now()
},
updatedAt: {
type: Date,
default: Date.now()
}
}
})
// 在保存數據以前跟新日期
InfoSchema.pre('save', function (next) {
if (this.isNew) {
this.meta.createdAt = this.meta.updatedAt = Date.now()
} else {
this.meta.updatedAt = Date.now()
}
next()
})
// 創建Info數據模型
mongoose.model('Info', InfoSchema)
複製代碼
上面的代碼就是利用mongoose
實現了學生的附加信息的數據模型,用一樣的方法咱們實現了student數據模型
mongodb/schema/student.js
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
const StudentSchema = new Schema({
name: String,
sex: String,
age: Number,
info: {
type: ObjectId,
ref: 'Info'
},
meta: {
createdAt: {
type: Date,
default: Date.now()
},
updatedAt: {
type: Date,
default: Date.now()
}
}
})
StudentSchema.pre('save', function (next) {
if (this.isNew) {
this.meta.createdAt = this.meta.updatedAt = Date.now()
} else {
this.meta.updatedAt = Date.now()
}
next()
})
mongoose.model('Student', StudentSchema)
複製代碼
數據模型都連接好以後,咱們就添加一些存儲數據的方法,這些方法都寫在控制器裏面。而後在controler裏面新建info.js
和student.js
,這兩個文件分別對象,操做info和student數據的控制器,分開寫爲了方便模塊化管理。
controlers/info.js
import mongoose from 'mongoose'
const Info = mongoose.model('Info')
// 保存info信息
export const saveInfo = async (ctx, next) => {
// 獲取請求的數據
const opts = ctx.request.body
const info = new Info(opts)
const saveInfo = await info.save() // 保存數據
console.log(saveInfo)
// 簡單判斷一下 是否保存成功,而後返回給前端
if (saveInfo) {
ctx.body = {
success: true,
info: saveInfo
}
} else {
ctx.body = {
success: false
}
}
}
// 獲取全部的info數據
export const fetchInfo = async (ctx, next) => {
const infos = await Info.find({}) // 數據查詢
if (infos.length) {
ctx.body = {
success: true,
info: infos
}
} else {
ctx.body = {
success: false
}
}
}
複製代碼
上面的代碼,就是前端用post(路由下面一會在寫)請求過來的數據,而後保存到mongodb數據庫,在返回給前端保存成功與否的狀態。也簡單實現了一下,獲取所有附加信息的的一個方法。下面咱們用一樣的道理實現studen數據的保存以及獲取。
controllers/sdudent.js
import mongoose from 'mongoose'
const Student = mongoose.model('Student')
// 保存學生數據的方法
export const saveStudent = async (ctx, next) => {
// 獲取前端請求的數據
const opts = ctx.request.body
const student = new Student(opts)
const saveStudent = await student.save() // 保存數據
if (saveStudent) {
ctx.body = {
success: true,
student: saveStudent
}
} else {
ctx.body = {
success: false
}
}
}
// 查詢全部學生的數據
export const fetchStudent = async (ctx, next) => {
const students = await Student.find({})
if (students.length) {
ctx.body = {
success: true,
student: students
}
} else {
ctx.body = {
success: false
}
}
}
// 查詢學生的數據以及附加數據
export const fetchStudentDetail = async (ctx, next) => {
// 利用populate來查詢關聯info的數據
const students = await Student.find({}).populate({
path: 'info',
select: 'hobby height weight'
}).exec()
if (students.length) {
ctx.body = {
success: true,
student: students
}
} else {
ctx.body = {
success: false
}
}
}
複製代碼
數據模型和控制器在上面咱們都已是完成了,下面就利用koa-router
路由中間件,來實現請求的接口。咱們回到server.js
,在上面添加一些代碼。以下
server.js
import Koa from 'koa'
import KoaStatic from 'koa-static'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'
import {database} from './mongodb' // 引入mongodb
import {saveInfo, fetchInfo} from './controllers/info' // 引入info controller
import {saveStudent, fetchStudent, fetchStudentDetail} from './controllers/student' // 引入 student controller
database() // 連接數據庫而且初始化數據模型
const app = new Koa()
const router = new Router();
app.use(bodyParser());
app.use(KoaStatic(__dirname + '/public'));
router.get('/test', (ctx, next) => {
ctx.body="test page"
});
// 設置每個路由對應的相對的控制器
router.post('/saveinfo', saveInfo)
router.get('/info', fetchInfo)
router.post('/savestudent', saveStudent)
router.get('/student', fetchStudent)
router.get('/studentDetail', fetchStudentDetail)
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(4000);
console.log('graphQL server listen port: ' + 4000)
複製代碼
上面的代碼,就是作了,引入mongodb設置,info以及student控制器,而後連接數據庫,而且設置每個設置每個路由對應的咱們定義的的控制器。
安裝一下mongoose模塊 npm install mongoose --save
而後在命令行運行node start
,咱們服務器運行以後,而後在給info和student添加一些數據。這裏是經過postman
的谷歌瀏覽器插件來請求的,以下圖所示
yeah~~~保存成功,繼續按照步驟多保存幾條,而後按照接口查詢一下。以下圖
嗯,如圖都已經查詢到咱們保存的所有數據,而且所有返回前端了。不錯不錯。下面繼續保存學生數據。
tip: 學生數據保存的時候關聯了信息裏面的數據哦。因此把id寫上去了。
一樣的一波操做,咱們多保存學生幾條信息,而後查詢學生信息,以下圖所示。
好了 ,數據咱們都已經保存好了,鋪墊也作了一大把了,下面讓咱們真正的進入,GrapgQL查詢的騷操做吧~~~~
別忘了,下面咱們創建了一個router文件夾
,這個文件夾就是統一管理咱們路由的模塊,分離了路由個應用服務的模塊。在router文件夾
新建一個index.js
。而且改造一下server.js
裏面的路由所有複製到router/index.js
。
順便在這個路由文件中加入,graphql-server-koa模塊,這是koa集成的graphql服務器模塊。graphql server是一個社區維護的開源graphql服務器,能夠與全部的node.js http服務器框架一塊兒工做:express,connect,hapi,koa和restify。能夠點擊連接查看詳細知識點。
加入graphql-server-koa
的路由文件代碼以下:
router/index.js
import { graphqlKoa, graphiqlKoa } from 'graphql-server-koa'
import {saveInfo, fetchInfo} from '../controllers/info'
import {saveStudent, fetchStudent, fetchStudentDetail} from '../controllers/student'
const router = require('koa-router')()
router.post('/saveinfo', saveInfo)
.get('/info', fetchInfo)
.post('/savestudent', saveStudent)
.get('/student', fetchStudent)
.get('/studentDetail', fetchStudentDetail)
.get('/graphiql', async (ctx, next) => {
await graphiqlKoa({endpointURL: '/graphql'})(ctx, next)
})
module.exports = router
複製代碼
以後把server.js
的路由代碼去掉以後的的代碼以下:
server.js
import Koa from 'koa'
import KoaStatic from 'koa-static'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'
import {database} from './mongodb'
database()
const GraphqlRouter = require('./router')
const app = new Koa()
const router = new Router();
const port = 4000
app.use(bodyParser());
app.use(KoaStatic(__dirname + '/public'));
router.use('', GraphqlRouter.routes())
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log('GraphQL-demo server listen port: ' + port)
複製代碼
恩,分離以後簡潔,明瞭了不少。而後咱們在從新啓動node服務。在瀏覽器地址欄輸入http://localhost:4000/graphiql
,就會獲得下面這個界面。如圖:
沒錯,什麼都沒有 就是GraphQL查詢服務的界面。下面咱們把這個GraphQL查詢服務完善起來。
看一下咱們第一張圖,咱們須要什麼數據,在GraphQL查詢界面就編寫什麼字段,就能夠查詢到了,然後端須要定義好這些數據格式。這就須要咱們定義好GraphQL Schema。
首先咱們在根目錄新建一個graphql文件夾
,這個文件夾用於存放管理graphql相關的js文件。而後在graphql文件夾
新建一個schema.js
。
這裏咱們用到graphql模塊,這個模塊就是用javascript參考實現graphql查詢。向須要詳細學習,請使勁戳連接。
咱們先寫好info
的查詢方法。而後其餘都差很少滴。
graphql/schema.js
// 引入GraphQL各類方法類型
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLID,
GraphQLList,
GraphQLNonNull,
isOutputType
} from 'graphql';
import mongoose from 'mongoose'
const Info = mongoose.model('Info') // 引入Info模塊
// 定義日期時間 類型
const objType = new GraphQLObjectType({
name: 'mete',
fields: {
createdAt: {
type: GraphQLString
},
updatedAt: {
type: GraphQLString
}
}
})
// 定義Info的數據類型
let InfoType = new GraphQLObjectType({
name: 'Info',
fields: {
_id: {
type: GraphQLID
},
height: {
type: GraphQLString
},
weight: {
type: GraphQLString
},
hobby: {
type: new GraphQLList(GraphQLString)
},
meta: {
type: objType
}
}
})
// 批量查詢
const infos = {
type: new GraphQLList(InfoType),
args: {},
resolve (root, params, options) {
return Info.find({}).exec() // 數據庫查詢
}
}
// 根據id查詢單條info數據
const info = {
type: InfoType,
// 傳進來的參數
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID) // 參數不爲空
}
},
resolve (root, params, options) {
return Info.findOne({_id: params.id}).exec() // 查詢單條數據
}
}
// 導出GraphQLSchema模塊
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Queries',
fields: {
infos,
info
}
})
})
複製代碼
看代碼的時候建議從下往上看~~~~,上面代碼所說的就是,創建info和infos的GraphQLSchema,而後定義好數據格式,查詢到數據,或者根據參數查詢到單條數據,而後返回出去。
寫好了info schema以後 咱們在配置一下路由,進入router/index.js
裏面,加入下面幾行代碼。
router/index.js
import { graphqlKoa, graphiqlKoa } from 'graphql-server-koa'
import {saveInfo, fetchInfo} from '../controllers/info'
import {saveStudent, fetchStudent, fetchStudentDetail} from '../controllers/student'
// 引入schema
import schema from '../graphql/schema'
const router = require('koa-router')()
router.post('/saveinfo', saveInfo)
.get('/info', fetchInfo)
.post('/savestudent', saveStudent)
.get('/student', fetchStudent)
.get('/studentDetail', fetchStudentDetail)
router.post('/graphql', async (ctx, next) => {
await graphqlKoa({schema: schema})(ctx, next) // 使用schema
})
.get('/graphql', async (ctx, next) => {
await graphqlKoa({schema: schema})(ctx, next) // 使用schema
})
.get('/graphiql', async (ctx, next) => {
await graphiqlKoa({endpointURL: '/graphql'})(ctx, next) // 重定向到graphiql路由
})
module.exports = router
複製代碼
詳細請看註釋,而後被忘記安裝好npm install graphql-server-koa graphql --save
這兩個模塊。安裝完畢以後,從新運行服務器的node start
(你可使用nodemon來啓動本地node服務,省得來回啓動。)
而後刷新http://localhost:4000/graphiql
,你會發現右邊會有查詢文檔,在左邊寫上查詢方式,以下圖
如今是咱們把schema和type都寫到一個文件上面了去了,若是數據多了,字段多了變得特別很差維護以及review,因此咱們就把定義type的和schema分離開來,說作就作。
在graphql文件夾
新建info.js
,studen.js
,文件,先把info type 寫到info.js
代碼以下
graphql/info.js
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLID,
GraphQLList,
GraphQLNonNull,
isOutputType
} from 'graphql';
import mongoose from 'mongoose'
const Info = mongoose.model('Info')
const objType = new GraphQLObjectType({
name: 'mete',
fields: {
createdAt: {
type: GraphQLString
},
updatedAt: {
type: GraphQLString
}
}
})
export let InfoType = new GraphQLObjectType({
name: 'Info',
fields: {
_id: {
type: GraphQLID
},
height: {
type: GraphQLString
},
weight: {
type: GraphQLString
},
hobby: {
type: new GraphQLList(GraphQLString)
},
meta: {
type: objType
}
}
})
export const infos = {
type: new GraphQLList(InfoType),
args: {},
resolve (root, params, options) {
return Info.find({}).exec()
}
}
export const info = {
type: InfoType,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve (root, params, options) {
return Info.findOne({
_id: params.id
}).exec()
}
}
複製代碼
分離好info type 以後,一氣呵成,咱們順便把studen type 也完成一下,代碼以下,原理跟info type 都是相通的,
graphql/student.js
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLID,
GraphQLList,
GraphQLNonNull,
isOutputType,
GraphQLInt
} from 'graphql';
import mongoose from 'mongoose'
import {InfoType} from './info'
const Student = mongoose.model('Student')
let StudentType = new GraphQLObjectType({
name: 'Student',
fields: {
_id: {
type: GraphQLID
},
name: {
type: GraphQLString
},
sex: {
type: GraphQLString
},
age: {
type: GraphQLInt
},
info: {
type: InfoType
}
}
})
export const student = {
type: new GraphQLList(StudentType),
args: {},
resolve (root, params, options) {
return Student.find({}).populate({
path: 'info',
select: 'hobby height weight'
}).exec()
}
}
複製代碼
tips: 上面由於有了聯表查詢,因此引用了
info.js
而後調整一下schema.js
的代碼,以下:
import {
GraphQLSchema,
GraphQLObjectType
} from 'graphql';
// 引入 type
import {info, infos} from './info'
import {student} from './student'
// 創建 schema
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Queries',
fields: {
infos,
info,
student
}
})
})
複製代碼
看到代碼是如此的清新脫俗,是否是深感欣慰。好了,graophql數據查詢都已是大概比較完善了。 課程的數據你們能夠本身寫一下,或者直接到個人github項目裏面copy過來我就不一一重複的說了。
下面寫一下前端接口是怎麼查詢的,而後讓數據返回瀏覽器展現到頁面的。
在public文件夾
下面新建一個index.html
,js文件夾
,css文件夾
,而後在js文件夾
創建一個index.js
, 在css文件夾
創建一個index.css
,代碼以下
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GraphQL-demo</title>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<h1 class="app-title">GraphQL-前端demo</h1>
<div id="app">
<div class="course list">
<h3>課程列表</h3>
<ul id="courseList">
<li>暫無數據....</li>
</ul>
</div>
<div class="student list">
<h3>班級學生列表</h3>
<ul id="studentList">
<li>暫無數據....</li>
</ul>
</div>
</div>
<div class="btnbox">
<div class="btn" id="btn1">點擊常規獲取課程列表</div>
<div class="btn" id="btn2">點擊常規獲取班級學生列表</div>
<div class="btn" id="btn3">點擊graphQL一次獲取全部數據,問你怕不怕?</div>
</div>
<div class="toast"></div>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
複製代碼
咱們主要看js請求方式 代碼以下
window.onload = function () {
$('#btn2').click(function() {
$.ajax({
url: '/student',
data: {},
success:function (res){
if (res.success) {
renderStudent (res.data)
}
}
})
})
$('#btn1').click(function() {
$.ajax({
url: '/course',
data: {},
success:function (res){
if (res.success) {
renderCourse(res.data)
}
}
})
})
function renderStudent (data) {
var str = ''
data.forEach(function(item) {
str += '<li>姓名:'+item.name+',性別:'+item.sex+',年齡:'+item.age+'</li>'
})
$('#studentList').html(str)
}
function renderCourse (data) {
var str = ''
data.forEach(function(item) {
str += '<li>課程:'+item.title+',簡介:'+item.desc+'</li>'
})
$('#courseList').html(str)
}
// 請求看query參數就能夠了,跟查詢界面的參數差很少
$('#btn3').click(function() {
$.ajax({
url: '/graphql',
data: {
query: `query{
student{
_id
name
sex
age
}
course{
title
desc
}
}`
},
success:function (res){
renderStudent (res.data.student)
renderCourse (res.data.course)
}
})
})
}
複製代碼
css的代碼 我就不貼出來啦。你們能夠去項目直接拿嘛。
全部東西都已經完成以後,從新啓動node服務,而後訪問,http://localhost:4000/
就會看到以下界面。界面醜,沒什麼設計美化細胞,求輕噴~~~~
操做點擊以後就會想第二張圖同樣了。
全部效果都出來了,本篇文章也就到此結束了。
附上項目地址: github.com/naihe138/Gr…
ps:喜歡的話丟一個小星星(star)給我嘛