GraphQL一種用爲你 API 而生的查詢語言,2018已經到來,PWA尚未大量投入生產應用之中就已經火起來了,GraphQL的應用或許也不會太遠了。前端的發展的最大一個特色就是變化快,有時候應對各類需求場景的變化,不得不去對接口開發不少版本或者修改。各類業務依賴強大的基礎數據平臺快速生長,如何高效地爲各類業務提供數據支持,是全部人關心的問題。並且如今前端的解決方案是將視圖組件化,各個業務線既能夠是組件的使用者,也能夠是組件的生產者,若是可以將其中通用的內容抽取出來提供給各個業務方反覆使用,必然可以節省寶貴的開發時間和開發人力。那麼問題來了,前端經過組件實現了跨業務的複用,後端接口如何相應地提升開發效率呢?GraphQL,就是應對複雜場景的一種新思路。javascript
官方解釋:css
GraphQL 既是一種用於 API 的查詢語言也是一個知足你數據查詢的運行時。 GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端可以準確地得到它須要的數據,並且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。
下面介紹一下GraphQL的有哪些好處:html
本篇文章中將搭配koa實現一個GraphQL查詢的例子,逐步從簡單kao服務到mongodb的數據插入查詢再到GraphQL的使用,
讓你們快速看到:前端
項目以下圖所示java
一、搭建GraphQL工具查詢界面。node
二、前端用jq發送ajax的使用方式jquery
入門項目咱們都已是預覽過了,下面咱們動手開發吧!!!git
首先創建一個項目文件夾,而後在這個項目文件夾新建一個server.js
(node服務)、config文件夾
、mongodb文件夾
、router文件夾
、controllers文件夾
以及public文件夾
(這個主要放前端靜態數據展現頁面),好啦,項目的結構咱們都已經創建好,下面在server.js
文件夾裏寫上es6
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
github
安裝好上面幾個模塊,
而後運行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/
就會看到以下界面。界面醜,沒什麼設計美化細胞,求輕噴~~~~
操做點擊以後就會想第二張圖同樣了。
全部效果都出來了,本篇文章也就到此結束了。
附上項目地址: https://github.com/naihe138/GraphQL-demo
ps:喜歡的話丟一個小星星(star)給我嘛