前言
- 最近学了graphql,感觉真香,这玩意必须要总结下!!!
准备工作
- 首先创建项目,npm init ,然后需要安装下面这些包,这篇不写前台连接,cors也可以不用装。
cnpm i express graphql express-graphql mongoose cors -D
- 这次用的是Mongodb数据库,官网进行安装,这就不用说了。
- 放个graphql官网文档,更多需求去官网看一下。
- 至于不想用express,使用别的js后端,可以使用apollo-server,如果不想用js,参考这里。
建立schema
- 先使用假数据跑通再对接数据库。
const graphql = require('graphql')
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLSchema,
GraphQLList,
GraphQLNonNull
} = graphql;
let categories = [//分类
{ id: '1', name: '吃的' },
{ id: '2', name: '喝的' },
{ id: '3', name: '用的' }
]
let products = [
{ id: '1', name: '浪味仙', category: '1' },
{ id: '2', name: '炸鸡', category: '1' },
{ id: '3', name: '旺旺仙贝', category: '1' },
{ id: '4', name: '汉堡', category: '1' },
{ id: '1', name: '茶颜悦色', category: '2' },
{ id: '2', name: '纸巾', category: '3' }
]
//分类里定义每个字段
const Category = new GraphQLObjectType({
name :'category',
fields:()=>({
id:{type:GraphQLString},
name:{type:GraphQLString},
products:{
type:new GraphQLList(Product),//每个分类下是一个数组,而不是一个对象
resolve(parent){//parent代表上一层
return products.filter(item=>item.category===parent.id)
}
}
})
})
//产品里定义每个字段
const Product = new GraphQLObjectType({
name :'product',
fields:()=>({
id:{type:GraphQLString},
name:{type:GraphQLString},
category:{
type:Category,
resolve(parent){
return categories.find(item=>{
return item.id===parent.category
})
}
}
})
})
//查询接口
const RootQuery= new GraphQLObjectType({
name:'root',
fields:{
getCategory:{
type:Category,
args:{//查询参数
id:{
type:GraphQLString
}
},
resolve(parent,args){
return categories.find(item=>item.id===args.id)
}
},
getCategories:{
type:new GraphQLList(Category),
args:{
},
resolve(parent,args){
return categories
}
},
getProduct:{
type:new GraphQLList(Product),//有可能一个id对多个商品
args:{
id:{
type :GraphQLString
}
},
resolve(parent,args){
return products.filter((item)=>item.id===args.id)
}
},
getProducts:{
type:new GraphQLList(Product),
args:{},
resolve(parent,args){
return products
}
}
}
})
//添加接口
const RootMutation = new GraphQLObjectType({
name:'rootmutation',//这个属性是最后生成接口文档的mutation接口名字
fields:{
addCategory:{//添加类别需要给名字
type:Category,
args:{
name:{type:new GraphQLNonNull(GraphQLString)}
},
resolve(parent,args){
args.id = categories.length+1+''//自增1
categories.push(args)
return args
}
},
addProduct:{//添加商品需要给名字和类别
type:Product,
args:{
name:{
type:new GraphQLNonNull(GraphQLString)
},
category:{
type:new GraphQLNonNull(GraphQLString)
}
},
resolve(parent,args){
args.id=products.length+1+''
products.push(args)
return args
}
},
delProductByName:{
type:new GraphQLList(Product),
args:{
category:{
type:new GraphQLNonNull(GraphQLString)
},
name:{
type:new GraphQLNonNull(GraphQLString)
}
},
resolve(parent,args){
products = products.reduce((prev,next)=>{
return (next.name===args.name&&next.category===args.category)?prev:prev.concat(next)
},[])
return products
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery,
mutation:RootMutation
})
- 我上面做了个假数据分类和产品,其中分类下面有产品,category字段的数字代表对应的分类。
- 然后把分类和产品每个字段进行定义,也就是你想呈现给查询的人什么东西,就定义什么东西。值得注意的就是这里字段我想通过分类来查产品,就给分类下面加个产品字段,然后进行反查,这个反查逻辑写到resolve函数里,return的东西代表要展现给查询人看的东西。另外反查的type,需要结合实际情况来看,比如我这里分类下面去查产品,那么返回的每个分类下必然是很多个Product graphql对象组成的数组,所以这里要转成数组。而通过产品去查分类,必然只有一个分类对象,而不是分类数组,所以类型是给Category。
- 这里定字段是函数形式,查询是直接对象。
express连接
- 然后使用express把graphql连起来。
const express = require('express')
const graphqlHTTP=require('express-graphql')
const schema = require('./schema')
const cors = require('cors')
const app = express()
app.use(cors({
origin:'http://localhost:3000',
method:'GET,PUT,POST,OPTIONS'
}))
app.use('/graphql',graphqlHTTP({
schema,
graphiql:true
}))
app.listen(4000,()=>{
console.log('server start');
})
-
其中路径为
/graphql
就是graphiql的地址了,这个东西可以让我们对后台语句进行各种测试,还自带接口文档,真是太方便了,而自己想要啥数据就能获得啥数据。
-
只要查询语句什么格式,他就能给你返回什么格式,只要id,就写id,绝不多反给你,想嵌套,就写多少层,绝不少。
-
下面看一下查询语法和修改语法:
{
getProduct(id:"1"){
id,name
}
}
- 查询有参数的就在小括号里写上参数冒号参数,如果是graphqlstring类型,必须使用双引号。
- 花括号里就是你想返回的字段了。
{
getCategories{
id,
name,
products{
id,name
}
}
}
- 无参数查询则不需要写小括号,花括号里写想要的字段,如果有嵌套可以继续花括号。
mutation {
delProductByName(name: "可乐3", category: "2") {
id
name
category {
id
name
products {
id
name
}
}
}
}
- 修改数据前面需要加上mutation ,其他跟上面一样。
加入Mongodb数据库
- 可以发现真正需要改的地方就只有resolve,把resolve逻辑改成操作数据库即可完成。
model.js
let mongoose = require('mongoose')
let ObjectId = mongoose.Schema.Types.ObjectId
const Schema = mongoose.Schema
const conn =mongoose.createConnection('mongodb://localhost/graphql', { useNewUrlParser: true, useUnifiedTopology: true })
conn.on('error',(err)=>{
console.log(err);
})
conn.on('open',()=>{
console.log('ok');
})
const CategorySchema = new Schema({
name:String
})
const CategoryModel = conn.model('Category',CategorySchema)
const ProductSchema = new Schema({
name:String,
category:{
type:ObjectId, //产品的分类字段是分类的objid字段
ref:'Category'
}
})
const ProductModel = conn.model('Product',ProductSchema)
module.exports={
CategoryModel,
ProductModel
};
- 连接数据库,建立schema和model,跟graphql的schema一样,也是2个,但是字段有点不一样,这个代表存储的数据,id由mongo自带的objectId完成。建立外键由products的schema做引用category的objid即可。
schema.js
const graphql = require('graphql')
const{ProductModel,CategoryModel }=require('./model')
let mongoose = require('mongoose')
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLSchema,
GraphQLList,
GraphQLNonNull
} = graphql;
let categories = [//分类
{ id: '1', name: '吃的' },
{ id: '2', name: '喝的' },
{ id: '3', name: '用的' }
]
let products = [
{ id: '1', name: '浪味仙', category: '1' },
{ id: '2', name: '炸鸡', category: '1' },
{ id: '3', name: '旺旺仙贝', category: '1' },
{ id: '4', name: '汉堡', category: '1' },
{ id: '1', name: '茶颜悦色', category: '2' },
{ id: '2', name: '纸巾', category: '3' }
]
//分类里定义每个字段
const Category = new GraphQLObjectType({
name :'category',
fields:()=>({
id:{type:GraphQLString},
name:{type:GraphQLString},
products:{
type:new GraphQLList(Product),//每个分类下是一个数组,而不是一个对象
resolve(parent){//parent代表上一层
//return products.filter(item=>item.category===parent.id)
return ProductModel.find({category:parent.id})
}
}
})
})
//产品里定义每个字段
const Product = new GraphQLObjectType({
name :'product',
fields:()=>({
id:{type:GraphQLString},
name:{type:GraphQLString},
category:{
type:Category,
resolve(parent){
// return categories.find(item=>{
// return item.id===parent.category
// })
return CategoryModel.findById(parent.category)
}
}
})
})
//查询接口
const RootQuery= new GraphQLObjectType({
name:'root',
fields:{
getCategory:{
type:Category,
args:{//查询参数
id:{
type:GraphQLString
}
},
resolve(parent,args){
// return categories.find(item=>item.id===args.id)
return CategoryModel.findById(args.id)
}
},
getCategories:{
type:new GraphQLList(Category),
args:{
},
resolve(parent,args){
// return categories
return CategoryModel.find()
}
},
getProduct:{
type:new GraphQLList(Product),
args:{
id:{
type :GraphQLString
}
},
resolve(parent,args){
//return products.filter((item)=>(item.id===args.id))
return ProductModel.findById(args.id)
}
},
getProducts:{
type:new GraphQLList(Product),
args:{},
resolve(parent,args){
// return products
return ProductModel.find()
}
}
}
})
//添加接口
const RootMutation = new GraphQLObjectType({
name:'rootmutation',
fields:{
addCategory:{//添加类别需要给名字
type:Category,
args:{
name:{type:new GraphQLNonNull(GraphQLString)}
},
resolve(parent,args){
// args.id = categories.length+1+''//自增1
// categories.push(args)
// return args
return CategoryModel.create(args)
}
},
addProduct:{//添加商品需要给名字和类别
type:Product,
args:{
name:{
type:new GraphQLNonNull(GraphQLString)
},
category:{
type:new GraphQLNonNull(GraphQLString)
}
},
resolve(parent,args){
// args.id=products.length+1+''
// products.push(args)
// return args
return ProductModel.create(args)
}
},
delProductByName:{
type:new GraphQLList(Product),
args:{
category:{
type:new GraphQLNonNull(GraphQLString)
},
name:{
type:new GraphQLNonNull(GraphQLString)
}
},
async resolve(parent,args){
// products = products.reduce((prev,next)=>{
// return (next.name===args.name&&next.category===args.category)?prev:prev.concat(next)
// },[])
// return products
let res=await ProductModel.deleteMany(args)
return ProductModel.find()
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery,
mutation:RootMutation
})
- 可以发现,mongoose,Mongodb与express接近完全分离,靠graphql来驱动,前面写的server.js完全不变。让代码看上去更加清爽。