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)给我嘛