graphQL.md

graphql 与 restful

restful

属性状态转移,本质就是用定义uri,通过api接口来获取资源。通用系统架构,不受语言限制

  • restful一个接口只返回一个资源,graphql一次可以获取多个资源
  • restful用不同的url来区分资源,graphql用类型区分资源

express+graphql

最基本的写法
const express = require('express');
const {buildSchema} = require('graphql');
const {graphqlHTTP} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  type Query {
    hello: String
  }
`)
// 定义查询对应的解析器
const root = {
  hello: () => {
    return "hello world";
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});
自定义类型

自定义类型就是先在Query中对要定义的类型进行说明,然后再自己在外面对这个类型进行定义

const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  type Account {         //
    name: String
    age: Int
    gender: String
    department: String
  }

  type Query {
    hello: String
    account: Account    // 
  }
`)
// 定义查询对应的解析器
const root = {
  hello: () => {
    return "hello world";
  },
  account: () => {
    return {
      name: "Doris",
      age: 18,
      gender: "female",
      department: "xi'an"
    }
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

通过刚才两个例子可以看出graphql的运行模式,首先分为两个部分,一部分为schema区,一部分为root区:

  • schema

    • 定义查询query的语句及类型(包括自己的自定义类型)
  • root

    • 定义查询对应的resolver,也就是查询对应的解析器

比如说上面代码中,在 schema 中定义了一个hello的查询,在 root 中就定义这个 hello 的方法对应的实现,也就是 hello 具体要做什么业务

⚠️ root 中 return 的返回值的类型应该和 schema 中的定义类型保持一致

参数类型

基本参数类型,可以在 schema 中直接使用

  • String
  • Int
  • Boolean
  • Float
  • ID

[类型]代表数组类型,如: [int] 代表 Int类型的数组

参数传递

基本参数类型的参数传递
type Query{
	getName(Id: ID!, Age: Int): [Int]
}
  • 和 js 传参一样,小括号内定义形参,参数需要定义类型
  • ! 代表桉树不能为空

⚠️ getName(Id: ID!, Age: Int): [Int] 这句最后的[Int]的意思是getName这个查询的返回值是 Int 类型的数组

下面来看一下例子:

其中先在schema中定义一个getClassMates()的查询语句,加上形参,注意返回来的数据为string类型的数组。然后在root 中定义getClassMates()这个查询的解析器,其中 getClassMates({classNo}) 中的 {classNo} 其实是从arg中进行的结构赋值,当然也可以写成arg.classNo

const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  type Query{
    getClassMates(classNo: Int!): [String]
  }
`)
// 定义查询对应的解析器
const root = {
  getClassMates({ classNo }){
    const obj = {
      61 : ['doris', 'doris1', 'doris2'],
      62 : ['Doris', 'Doris1', 'Doris2']
    }
    return obj[classNo];
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});
复杂参数类型的参数传递

比如说之前例子中的Account类型

下面的例子中有一个salary,在schema中这个salary是可以传一个city的参数的,这样在对应的root里面,salary就可以被当作一个function来处理,可以在里面进行条件判断等等

const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  type Account {
    name: String
    age: Int
    gender: String
    department: String
    salary(city: String): Int
  }
  type Query{
    getClassMates(classNo: Int!): [String]
    account(userName: String): Account
  }
`)
// 定义查询对应的解析器
const root = {
  getClassMates({ classNo }){
    const obj = {
      61 : ['doris', 'doris1', 'doris2'],
      62 : ['Doris', 'Doris1', 'Doris2']
    }
    return obj[classNo];
  },
  account({userName}){
    const name = userName;
    const age = 18;
    const gender = 'female';
    const department = "xi'an"
    const salary = ({city}) => {
      if(city === '北京' || city === '上海' || city === '广州' || city === '深圳'){
        return 10000;
      }
      return 8000;
    }
    return {
      name,
      age,
      gender,
      department,
      salary
    }
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

GraphQL Clients

如何在客户端访问graphql接口

  • 先在之前的js文件中添加一句可以在访问外部静态文件的代码
// 公开文件夹,供用户访问静态资源
app.use(express, static('public'));
  • 新建public文件夹,并在其中新建index.html文件
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <button onclick="getData()">获取数据</button>
</body>
<script>
  function getData() {
    // 这里的query是一个字符串类型,
    // 这个字符串规定了这个接口的名字,参数,参数类型等
    // 实际上它是来自服务器端的定义
    // 这里从Account的参数中拿到$userName后将它给到account的userName
    const query = `
      query Account($userName: String){
        account(userName: $userName)
      }
    `
    // 定义variables进行传值
    const variables = {userName: "doris"};

    // 通过fetch向外发数据
    fetch('/graphql', {
      method: "POST",
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({
        query: query,
        variables: variables
      })
    }).then(res => res.json).then(json => {
      console.log(data);
    })
  }
</script>
</html>

然后运行 js 文件,运行完毕之后在localhost:9000/index.html进行对应的操作

但是这时候在这个index页面中点击获取数据按钮的时候会报错👇

是因为我们在查询的时候并没有告诉它我们需要什么样的字段,所以我们应该在

const query = `
      query Account($userName: String){
        account(userName: $userName)
      }
    `

这个代码中加入需要的字段

const query = `
      query Account($userName: String){
        account(userName: $userName){
			name
			age
			gender
			department
      		salary(city: "上海")
		}
      }
    `

这个时候就可以看到结果了

当然,salary中的city值也可以通过参数传递进来

const query = `
  query Account($userName: String, $city: String){
    account(userName: $userName){
      name
      age
      gender
      department
      salary(city: $city)
    }
  }
`
// 定义variables进行传值
const variables = {userName: "doris", city: "西安"};

Mutations

定义修改mutation其实和定义查询Query是类似的

  • Query
const schema = buildSchema(`
  type Account {         //
    name: String
    age: Int
    gender: String
    department: String
  }

  type Query {
    hello: String
    account: Account    // 
  }
`)
  • Mutation
const schema = buildSchema(`
  input AccountInput {         //
    name: String
    age: Int
    gender: String
    department: String
    salary: Int
  }
  type Account {
    name: String
    age: Int
    gender: String
    department: String
    salary: Int
  }
  type Mutation {
    createAccount(input: AccountInput): Account
  }
`)

type Mutation中:

  • 定义createAccount方法的形参(input: AccountInput),其中形参input的类型为AccountInput,这个AccountInput类型的定义和之前在query中定义类型的方法是类似的,不同的是,这个类型在定义的时候用的是input,也就是input AccountInput{},而不是和查询中定义一样用的是query
  • 定义createAccount方法的返回类型Account,因为基本来说返回值最后都是通过Query得到的,所以这里的Account的定义还是通过query的方式来进行,也就是前面应该是type
const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  input AccountInput {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Account {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Mutation {
    createAccount(input: AccountInput): Account
    updateAccount(id: ID, input: AccountInput): Account
  }
`)
// 定义一个假的数据库
const fakeDb = {};

// 定义查询对应的解析器
const root = {
  // 这里的input相当于拿到的所有字段
  createAccount({ input }){
    // 相当于数据库的保存
    fakeDb[input.name] = input;
    // 返回保存的结果
    return fakeDb[input.name];
  },

  updateAccount({ id, input }){
    // 相当于数据库的更新
    // 从fakeDb中拿到需要的fakeDb[id],拿到之后将input新的属性覆盖到它身上
    const updatedAccount = Object.assign({}, fakeDb[id], input);
    // 将更新保存到数据库
    fakeDb[id] = updatedAccount;
    // 返回更新的结果
    return updatedAccount;
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

node mutationtest_1.js之后,发现在http://localhost:9000/graphql发现error👇

这是为什么呢???

⚠️ 这是因为graphql中必须要有一个query,也就是说你不能只往里面写,你必须要与一个读的操作

所以就需要添加一个关于查询的Query

// 添加到schema
type Query {
  accounts: [Account]
}


// 添加到root
accounts(){
  return fakeDb;
}

然后运行

存入之后进行query时,发现还在报错👇

这个错的原因是因为返回的值不是一个数组,所以这块需要将root下面的acounts下面return fakeDb修改一下,因为可以在代码中看到这块的fakeDb是一个对象,所以下面需要改一下

const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  input AccountInput {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Account {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Mutation {
    createAccount(input: AccountInput): Account
    updateAccount(id: ID, input: AccountInput): Account
  }
  type Query {
    accounts: [Account]
  }
`)
// 定义一个假的数据库
const fakeDb = {};

// 定义查询对应的解析器
const root = {

  accounts(){            // 这里做了修改
    var arr = [];
    for(const i in fakeDb){
      arr.push(fakeDb[i]);
    }
    return arr;
  },
  
  // 这里的input相当于拿到的所有字段
  createAccount({ input }){
    // 相当于数据库的保存
    fakeDb[input.name] = input;
    // 返回保存的结果
    return fakeDb[input.name];
  },

  updateAccount({ id, input }){
    // 相当于数据库的更新
    // 从fakeDb中拿到需要的fakeDb[id],拿到之后将input新的属性覆盖到它身上
    const updatedAccount = Object.assign({}, fakeDb[id], input);
    // 将更新保存到数据库
    fakeDb[id] = updatedAccount;
    // 返回更新的结果
    return updatedAccount;
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

因为重启之后,之前的数据就消失了,所以需要重新存一次数据

下面来看一下怎么 updateAccount,这里的id暂时先用name来代替

那修改了之后到底有没有存进数据库还是得通过query来看

所以是成功存进去了

认证和中间件

控制具有权限的人去访问数据接口,需要借助于express的中间件来实现

中间件的本质实际上是一个function,它在接口执行之前首先拦截请求,然后决定是往下走还是返回错误消息

const express = require('express');
const {
  buildSchema
} = require('graphql');
const {
  graphqlHTTP
} = require('express-graphql');
// 定义schema,查询和类型
const schema = buildSchema(`
  input AccountInput {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Account {
    name: String
    age: Int
    gender: String
    department: String
  }
  type Mutation {
    createAccount(input: AccountInput): Account
    updateAccount(id: ID, input: AccountInput): Account
  }
  type Query {
    accounts: [Account]
  }
`)
// 定义一个假的数据库
const fakeDb = {};

// 定义查询对应的解析器
const root = {
  accounts(){
    var arr = [];
    for(const i in fakeDb){
      arr.push(fakeDb[i]);
    }
    return arr;
  },
  // 这里的input相当于拿到的所有字段
  createAccount({ input }){
    // 相当于数据库的保存
    fakeDb[input.name] = input;
    // 返回保存的结果
    return fakeDb[input.name];
  },

  updateAccount({ id, input }){
    // 相当于数据库的更新
    // 从fakeDb中拿到需要的fakeDb[id],拿到之后将input新的属性覆盖到它身上
    const updatedAccount = Object.assign({}, fakeDb[id], input);
    // 将更新保存到数据库
    fakeDb[id] = updatedAccount;
    // 返回更新的结果
    return updatedAccount;
  }
}

const app = express();

// 中间件
const middleware = (req, res, next) => {
  // 判断访问地址中包含/graphql这个字样并且cookie中没有auth的时候
  if(req.url.indexOf('/graphql') !== -1 && req.headers.cookie.indexOf('auth') === -1){
    res.send(JSON.stringify({
      error: "当前没有权限访问该接口"
    }));
    return;
  }
  next();
};

// 中间件的注册
app.use(middleware);

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

middleware中先检测访问的地址是不是/graphql,如果是这个地址,接着看一下cookie中是否包含auth这个字样,如果没有包含则抛出错误提示,否则就next()正常往下走

上面没有权限访问的原因是因为cookie中没有auth这个字段,可以在application中加一个之后刷新访问就可以

上面是通过一个小例子来说明中间件对权限验证,在真正的项目中是需要跟服务器端进行配合的:服务器端生成一个token,将token交给前端,前端在访问接口时将token带回来,拿到token之后对它进行一个检查

构造类型

之前的schema是通过buildSchema👇

const schema = buildSchema(`
// 定义类型
  type Account {
    name: String
    age: Int
    gender: String
    department: String
  }

// 定义Query
  type Query {
    accounts: [Account]
  }
`)

现在要说的是将类型的定义变成构造函数的形式进行,之前一步可以完成的现在需要三步,先定义type,然后定义Query,最后将Query转成schema👇

  • 使用GeaphQLObjectType定义type(类型)
var AccountType = new graphql.GraphQLObjectType({
  name: 'Account',
  fields: {
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
    gender: { type: graphql.GraphQLString },
    department: { type: graphql.GraphQLString }
  }
});
  • 使用GeaphQLObjectType定义query(查询)
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    account: {
      type: AccountType,
      args: {
        username: {
          type: graphql.GraphQLString
        },
      },
      resolver: function (_, { username }) {
        const name = username;
        const age = 18;
        const gender = 'female';
        const department = 'xian';
        return {
          name,
          age,
          gender,
          department
        }
      }
    }
  }
});
  • 创建schema
var schema = new graphql.GraphQLSchema({ query: queryType });

const express = require('express');
const { graphql } = require('graphql');
const { buildSchema } = require('graphql');
const { graphqlHTTP } = require('express-graphql');

var AccountType = new graphql.GraphQLObjectType({
  name: 'Account',
  fields: {
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
    gender: { type: graphql.GraphQLString },
    department: { type: graphql.GraphQLString }
  }
});

var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    account: {
      type: AccountType,
      args: {
        username: {
          type: graphql.GraphQLString
        },
      },
      resolver: function (_, { username }) {
        const name = username;
        const age = 18;
        const gender = 'female';
        const department = 'xian';
        return {
          name,
          age,
          gender,
          department
        }
      }
    }
  }
});


var schema = new graphql.GraphQLSchema({ query: queryType });





// 定义一个假的数据库
const fakeDb = {};

// 定义查询对应的解析器
const root = {
  accounts() {
    var arr = [];
    for (const i in fakeDb) {
      arr.push(fakeDb[i]);
    }
    return arr;
  },
  // 这里的input相当于拿到的所有字段
  createAccount({ input }) {
    // 相当于数据库的保存
    fakeDb[input.name] = input;
    // 返回保存的结果
    return fakeDb[input.name];
  },

  updateAccount({ id, input }) {
    // 相当于数据库的更新
    // 从fakeDb中拿到需要的fakeDb[id],拿到之后将input新的属性覆盖到它身上
    const updatedAccount = Object.assign({}, fakeDb[id], input);
    // 将更新保存到数据库
    fakeDb[id] = updatedAccount;
    // 返回更新的结果
    return updatedAccount;
  }
}

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(9000, () => {
  console.log("http://localhost:9000/graphql")
});

这样的变化带来的好处是比较好维护,哪块出错了之后就可以精确到出错的那个type

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值