• GraphQL


    GraphQL介绍

    当下接口开发的方式

    • RESTful
    • GraphQL

    RESTful的问题

    • 接口粒度比较细,很多场景需要调用多次请求才能完成一个功能
    • 接口扩展、维护成本比较高
    • 接口响应的数据格式无法预知(JSON 已经成为主流格式)

    GraphQL 介绍

    官网介绍:GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具

    传统的 API 调用一般获取到的是后端组装好的一个完整对象,而前端可能只需要用其中的某些字段,大部分数据的查询和传输工作都浪费了。GraphQL 提供一种全新数据查询方式,可以只获取需要的数据,使 API 调用更灵活、高效和低成本。

    • GraphQL 是 Facebook 开发的一种用于 API 的数据查询语言并于 2015 年开源
    • GraphQL 既是一种用于 API 的查询语言,也是一个满足你数据查询的运行时。
    • GraphQL 是一种接口开发标准,支持常见的服务端开发语言。例如:Java、PHP、Python、Node.js 等
    • 官方网站:https://graphql.org/
    • 中文文档:https://graphql.cn/
    • GitHub:https://github.com/graphql
    • 未来技术趋势,可能替代 RESTful,但是近几年还不太行
      • 因为 RESTful 沉淀了很多年
      • 一个新项目可以全部使用 GraphQL
      • GraphQL 和 RESTful 结合使用

    为什么选择 GraphQL

    在某些情况下,GraphQL 绝对是构建服务器的最佳选择,例如:

    • 如果您有多个客户端,因为它们只是用他们选择的语言编写自己的查询(GraphQL支持所有这些);
    • 如果您在不同的平台上工作:Web,移动,应用程序等;
    • 如果您的API是高度可定制的。

    使用

    • 服务端通过定义的数据类型规定了可以提供的各种形式的数据
    • 类型的字段要有对应的 resolver 提供对应的解析
    • 客户端可以根据服务端定义的数据类型选择性查询需要的字段信息

    GraphQL.js

    GraphQL.js 是一个 GraphQL 的参考实现。

    为了处理 GraphQL 查询,我们需要定义一个 Query 类型的 schema。我们还需要一个 API 根节点,为每个 API 端点提供一个名为 resolver 的函数。对于只返回 Hello world! 的 API,我们可以将此代码放在名为 server.js 的文件中:

    const { graphql, buildSchema } = require('graphql')
    
    // 1. 使用 GraphQL schema 语法构建一个 schema
    const schema = buildSchema(`
      type Query {
        hello: String
      }
    `)
    
    // 2. 根节点为每个 API 入口端点提供一个 resolver 函数
    const root = {
      hello: () => {
        return 'Hello world!'
      }
    }
    
    // 3. 运行 GraphQL query '{ hello }' ,输出响应
    graphql(schema, '{ hello }', root).then(response => {
      console.log(response) // 输出结果:{ data: { hello: 'Hello world!' } }
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Express GraphQL

    在实际应用中,你可能不会在命令行工具里执行 GraphQL,而是会想从一个 API 服务器运行 GraphQL 查询。比如 Node.js Express。

    安装依赖

    npm install express express-graphql graphql
    
    • 1

    示例

    const express = require('express')
    const { graphqlHTTP } = require('express-graphql')
    const { buildSchema } = require('graphql')
    
    // 使用 GraphQL Schema Language 创建一个 schema
    const schema = buildSchema(`
      type Query {
        hello: String
      }
    `)
    
    // root 提供所有 API 入口端点相应的解析器函数
    const root = {
      hello: () => {
        return 'Hello world!'
      }
    }
    
    const app = express()
    
    app.use(
      '/graphql',
      graphqlHTTP({
        schema: schema,
        rootValue: root,
        graphiql: true
      })
    )
    
    app.listen(4000, () => {
      console.log('Running a GraphQL API server at http://localhost:4000/graphql')
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    GraphQL 客户端

    在有了 express-graphql 的情况下,你可以向 GraphQL 服务器上的入口端点发送一个 HTTP POST 请求,其中将 GraphQL 查询作为 JSON 载荷的 query 字段,就能调用 GraphQL 服务器。

    JavaScript 请求查询示例如下:

    fetch('/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify({query: "{ hello }"})
    })
      .then(r => r.json())
      .then(data => console.log('data returned:', data));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    传递参数:

    var dice = 3;
    var sides = 6;
    var query = `query RollDice($dice: Int!, $sides: Int) {
      rollDice(numDice: $dice, numSides: $sides)
    }`;
    
    fetch('/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify({
        query,
        variables: { dice, sides },
      })
    })
      .then(r => r.json())
      .then(data => console.log('data returned:', data));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    GraphQL 浏览器测试工具

    • 开启方式
    • 基本使用
      • 编写
      • 验证
      • 测试
    • 键盘快捷键
      • 格式化查询:Shift-Ctrl-P
      • 合并查询:Shift-Ctrl-M
      • 执行查询:Ctrl-Enter
      • 自动完成:Ctrl-Space
    • 查询文档

    GraphQL 模式和类型

    每一个 GraphQL 服务都会定义一套类型,用以描述你可能从那个服务查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。
    GraphQL 定义了自己的类型语言,称之为 “GraphQL schema language” —— 它和 GraphQL 的查询语言很相似,让我们能够和 GraphQL schema 之间可以无语言差异地沟通。

    Query 类型

    • Query 类型是客户端默认的查询类型
    • Query 类型必须存在
    • Query 是唯一的,不能重复定义

    标量类型

    所谓的标量类型也就是基本类型。

    GraphQL schema language 支持的标量类型有

    • Int:有符号 32 位整数。
    • Float:有符号双精度浮点值。
    • String:UTF‐8 字符序列。
    • Boolean:true 或者 false。
    • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读性。

    类型的作用:

    约束数据格式,防止出现不合理数据
    如果数据可以合理的转换为对应的数据类型则不会报错,例如字符串 “123” 可以被合理的转换为数字 123
    这些类型都直接映射 JavaScript,所以你可以直接返回原本包含这些类型的原生 JavaScript 对象。下面是一个展示如何使用这些基本类型的示例:

    var express = require('express');
    var graphqlHTTP = require('express-graphql');
    var { buildSchema } = require('graphql');
    
    // 使用 GraphQL schema language 构建一个 schema
    var schema = buildSchema(`
      type Query {
        quoteOfTheDay: String
        random: Float!
        rollThreeDice: [Int]
      }
    `);
    
    // root 将会提供每个 API 入口端点的解析函数
    var root = {
      quoteOfTheDay: () => {
        return Math.random() < 0.5 ? 'Take it easy' : 'Salvation lies within';
      },
      random: () => {
        return Math.random();
      },
      rollThreeDice: () => {
        return [1, 2, 3].map(_ => 1 + Math.floor(Math.random() * 6));
      },
    };
    
    var app = express();
    app.use('/graphql', graphqlHTTP({
      schema: schema,
      rootValue: root,
      graphiql: true,
    }));
    app.listen(4000);
    console.log('Running a GraphQL API server at localhost:4000/graphql');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    对象类型

    var express = require('express');
    var graphqlHTTP = require('express-graphql');
    var { buildSchema } = require('graphql');
    
    // 用 GraphQL schema language 构造一个 schema
    var schema = buildSchema(`
      type RandomDie {
        numSides: Int!
        rollOnce: Int!
        roll(numRolls: Int!): [Int]
      }
    
      type Query {
        getDie(numSides: Int): RandomDie
      }
    `);
    
    // 该类继承 RandomDie GraphQL 类型
    class RandomDie {
      constructor(numSides) {
        this.numSides = numSides;
      }
    
      rollOnce() {
        return 1 + Math.floor(Math.random() * this.numSides);
      }
    
      roll({numRolls}) {
        var output = [];
        for (var i = 0; i < numRolls; i++) {
          output.push(this.rollOnce());
        }
        return output;
      }
    }
    
    // root 规定了顶层的 API 入口端点
    var root = {
      getDie: ({numSides}) => {
        return new RandomDie(numSides || 6);
      }
    }
    
    var app = express();
    app.use('/graphql', graphqlHTTP({
      schema: schema,
      rootValue: root,
      graphiql: true,
    }));
    app.listen(4000);
    console.log('Running a GraphQL API server at localhost:4000/graphql');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    小结:
    • 花括号中是对象的字段信息
    • 属性名称是自定义的
    • 属性名后面的类型为 GraphQL 内置的标量类型
    • GraphQL 使用 # 注释

    查询语法示例:

    {
      getDie(numSides: 6) {
        rollOnce
        roll(numRolls: 3)
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    列表类型

    # 这表示数组本身可以为空,但是其不能有任何空值成员。
    myField: [String!]
    
    # 不可为空的字符串数组
    myField: [String]!
    
    # 数组本身不能为空,其中的数据也不能为空
    myField: [String!]!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    非空类型

    • 默认情况下,每个类型都是可以为空的,意味着所有的标量类型都可以返回 null
    • 使用感叹号可以标记一个类型不可为空,如 String! 表示非空字符串
    • 如果是列表类型,使用方括号将对应类型包起来,如 [Int] 就表示一个整数列表。

    枚举类型

    也称作枚举(enum),枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。这让你能够:

    1. 验证这个类型的任何参数是可选值的的某一个
    2. 与类型系统沟通,一个字段总是一个有限值集合的其中一个值。

    下面是一个用 GraphQL schema 语言表示的 enum 定义:

    enum Episode {
      NEWHOPE
      EMPIRE
      JEDI
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这表示无论我们在 schema 的哪处使用了 Episode,都可以肯定它返回的是 NEWHOPE、EMPIRE 和 JEDI 之一。

    注意,各种语言实现的 GraphQL 服务会有其独特的枚举处理方式。对于将枚举作为一等公民的语言,它的实现就可以利用这个特性;而对于像 JavaScript 这样没有枚举支持的语言,这些枚举值可能就被内部映射成整数值。当然,这些细节都不会泄漏到客户端,客户端会根据字符串名称来操作枚举值。

    传递参数

    • 基本用法
    • 非空参数
    • 查询多个参数
    • 参数的默认值
    • 客户端请求带有参数的查询
    var express = require('express');
    var graphqlHTTP = require('express-graphql');
    var { buildSchema } = require('graphql');
    
    // 使用 GraphQL schema language 构造一个 schema
    var schema = buildSchema(`
      type Query {
        rollDice(numDice: Int!, numSides: Int): [Int]
      }
    `);
    
    // root 为每个端点入口 API 提供一个解析器
    var root = {
      rollDice: ({numDice, numSides}) => {
        var output = [];
        for (var i = 0; i < numDice; i++) {
          output.push(1 + Math.floor(Math.random() * (numSides || 6)));
        }
        return output;
      }
    };
    
    var app = express();
    app.use('/graphql', graphqlHTTP({
      schema: schema,
      rootValue: root,
      graphiql: true,
    }));
    app.listen(4000);
    console.log('Running a GraphQL API server at localhost:4000/graphql');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    客户端请求示例:

    var dice = 3;
    var sides = 6;
    var query = `query RollDice($dice: Int!, $sides: Int) {
      rollDice(numDice: $dice, numSides: $sides)
    }`;
    
    fetch('/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify({
        query,
        variables: { dice, sides },
      })
    })
      .then(r => r.json())
      .then(data => console.log('data returned:', data));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Mutation 类型

    Input 类型
    客户端操作

    获取所有文章列表:

    axios({
      method: 'POST', // GraphQL 的请求方法必须是 POST
      url: 'http://localhost:4000/graphql',
      data: {
        query: `
          query getArticles {
            articles {
              title
            }
          }
    		`
      }
    }).then(res => {
      console.log(res.data)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    获取单个文章:

    axios({
      method: 'POST', // GraphQL 的请求方法必须是 POST
      url: 'http://localhost:4000/graphql',
      data: {
        query: `
          query getArticles($id: ID!) {
    	      article(id: $id) {
    	      id
    	      title
    	      }
    	    }
    		`,
        variables: {
          id: 2
        }
      }
    }).then(res => {
      console.log(res.data)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    添加文章:

    axios({
      method: 'POST', // GraphQL 的请求方法必须是 POST
      url: 'http://localhost:4000/graphql',
      data: {
        query: `
          mutation createArteicle($article: CreateArticleInput) {
            createArticle(article: $article) {
              id
              title
              body
            }
          }
        `,
        variables: {
          article: {
            title: 'aaa',
            body: 'bbb'
          }
        }
      }
    }).then(res => {
      console.log(res.data)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    更新文章:

    axios({
      method: 'POST', // GraphQL 的请求方法必须是 POST
      url: 'http://localhost:4000/graphql',
      data: {
        query: `
          mutation updateArteicle($id: ID!, $article: UpdateArticleInput) {
            updateArticle(id: $id, article: $article) {
              id
              title
              body
            }
          }
        `,
        variables: {
          id: 2,
          article: {
            title: 'aaa',
            body: 'bbb'
          }
        }
      }
    }).then(res => {
      console.log(res.data)
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    删除文章:

    axios({
      method: 'POST', // GraphQL 的请求方法必须是 POST
      url: 'http://localhost:4000/graphql',
      data: {
        query: `
          mutation deleteArteicle($id: ID!) {
            deleteArticle(id: $id) {
              success
            }
          }
        `,
        variables: {
          id: 2
        }
      }
    }).then(res => {
      console.log(res.data)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    VMware虚拟机安装运行MacOS系统
    漫谈!如何简单明了通过分解和增量更改将单体迁移到微服务
    react 安装教程
    idea中提示:error has occurred, please check your installation and try again
    MSQL系列(五) Mysql实战-索引最左侧匹配原则分析及实战
    Day26:内部类的详解
    设计模式-原则篇-01.开闭原则
    按位运算符、逻辑运算符
    JavaScript算法43- 分类求和并作差(leetCode:100103easy)周赛
    基于C语言的推选优秀班委投票系统
  • 原文地址:https://blog.csdn.net/qq_42308316/article/details/128158908