• 试一试 GraphQL


    GraphQL 简介

    一种用于 API 的查询语言。

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

    对比 restful api:

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

    安装

    使用 JavaScript 语言,express node.js 框架开始。

    npm install express express-graphql graphql
    

    创建 server.js 并 使用命令 node server.js 运行 demo。

    const express = require('express');
    const app = express();
    const { graphqlHTTP } = require('express-graphql');
    const { buildSchema } = require('graphql');
    
    const schema = buildSchema(`
      type Query {
        hello: String
      }
    `);
    
    const root = { hello: () => 'Hello world!' };
    
    app.use('/graphql', graphqlHTTP({
      schema: schema,
      rootValue: root,
      graphiql: true,
    }));
    app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));
    

    参数类型

    • 基本类型: String, Int, Float, Boolean, ID
    • [类型]代表数组,例如:[Int]代表整型数组。

    参数传递

    • 和 JavaScript 传递参数一样,小括号内定义形参,但是注意 : 参数需要定义类型。
    • ! 叹号代表参数不能为空。
    type Query {
        rollDice (numDice: Int!, numSide: Int): [Int]
    }
    

    自定义参数类型

    GraphQL 允许用户自定义参数类型,通常用来描述要获取的资源的属性。

    type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary: (city: String): Int
    }
    
    type Query {
        account (name: string): Account
    }
    

    server.js

    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      type Query {
        hello: String
        getClassMates(classNo: Int!): [String]
        account (username: String): Account
      }
    `)
    
    const root = {
      hello: () => 'Hello world!',
    
      getClassMates: ({ classNo }) => {
        const obj = {
          31: ['张三', '李四', '王五'],
          61: ['张大三', '李大四', '王大五'],
        }
        return obj[classNo]
      },
    
      account: ({ username }) => {
        const name = username
        const sex = 'nan'
        const age = 10
        const department = '开发部'
        const salary = ({ city }) => {
          if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
            return 10000
          }
          return 3000
        }
        return {
          name,
          sex,
          age,
          department,
          salary,
        }
      },
    }
    

    在客户端访问GraphQL接口

    在 server.js 公开文件夹 供用户访问静态资源。

    app.use(express.static('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>Document</title>
    </head>
    <body>
    
        <button onclick="getData()">获取数据</button>
    
        <script>
    
            function getData() {
                const query = `
                    query Account ($username: String!) {
                        account(username: $username) {
                            name
                            age
                            sex
                            salary(city: "北京")
                        }
                    }`
    
                const variables = { username: '李大四' }
    
                fetch('/graphql', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json'
                    },
                    body: JSON.stringify({
                        query,
                        variables
                    })
                })
                    .then(res => res.json)
                    .then(json => {
                        console.log(json)
                    })
            }
        </script>
    </body>
    </html>
    
    

    后端的 username 对应 variales 的值中的 username, 和 query 中的 $username

    使用 Mutations 修改数据

    不能单独使用 Mutation, 需结合 Query

    
    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      input AccountInput {
        name: String
        age: Int 
        sex: String
        department: String
        salary: Int
      }
    
      type Query {
        accounts: [Account]
      }
    
      type Mutation {
          createAccount(input: AccountInput): Account
          updateAccount(id: ID!, input: AccountInput): Account
      }
    `)
    
    // 模拟数据库
    const fakeDb = {}
    
    const root = {
      createAccount({ input }) {
        // 模拟数据库保存
        fakeDb[input.name] = input
        // 返回保存结果
        return fakeDb[input.name]
      },
    
      updateAccount({ id, input }) {
        // 模拟更新数据库
        const updatedAccount = Object.assign({}, fakeDb[id], input)
        fakeDb[id] = updatedAccount
        return updatedAccount
      },
    
      accounts() {
        let arr = []
        for (const key in fakeDb) {
          arr.push(fakeDb[key])
        }
        return arr
      },
    }
    

    试着创建一个 account。

    通过 accounts,查询到刚刚创建的 account。

    试着修改数据,我们将 age 18 改为 20,并将该 account 更改后的信息返回。

    这里将 name 作为记录的主键了。

    Constructing Types

    上文通过字符串的形式构建 schema,还可以通过构造函数来构建。

    以前:

    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      type Query {
        account (username: String): Account
      }
    `)
    

    现在

    const AccountType = new graphql.GraphQLObjectType({
      name: 'Account',
      fields: {
        name: {type: graphql.GraphQLString},
        age: {type: graphiql.GraphQLInt},
        sex: {type: raphql.GraphQLString},
        department: {type: graphql.GraphQLString},
      }
    })
    
    const queryType = new graphiql.GraphQLObjectType({
      name: 'Query',
      fields: {
        account: {
          type: AccountType,
          args: {
            username: {type: graphiql.GraphQLString}
          },
          resolve(_, {username}){
            const name = username
            const sex = 'nan' 
            const age = 18
            const department = '开发部'
            return {
              name,
              age,
              sex,
              department
            }
          }
        }
      }
    })
    
    const schema = new graphiql.GraphQLSchema({query: queryType})
    

    代码量提升,编辑器提示,可维护性提升,报错信息更精准。

    结合 MySql CURD

    接下来需要稍作更改,拼接几个 SQL 语句, 操作数据库。

    创建数据库 test , 表 account,并添加几个字段如下:

    npm i mysql -S
    
    const mysql = require('mysql')
    
    const pool = mysql.createPool({
      connectionLimit: 10,
      host: 'localhost',
      user: 'root',
      password: '123456',
      database: 'test',
    })
    
    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      input AccountInput {
        name: String
        age: Int 
        sex: String
        department: String
        salary: Int
      }
    
      type Query {
        accounts: [Account]
      }
    
      type Mutation {
          createAccount(input: AccountInput): Account
          updateAccount(id: ID!, input: AccountInput): Account
          deleteAccount(id: ID!): Boolean
      }
    `)
    
    const rootValue = {
      createAccount({ input }) {
        const data = {
          name: input.name,
          age: input.age,
          sex: input.sex,
          department: input.department,
        }
    
        // query 是异步操作, 使用  promise
        return new Promise((resolve, reject) => {
          pool.query('insert into account set ?', data, err => {
            if (err) {
              console.log('出错了' + err.message)
              return
            }
            resolve(data)
          })
        })
      },
    
      deleteAccount({ id }) {
        return new Promise((resolve, reject) => {
          pool.query('delete from account where id = ?', id, err => {
            if (err) {
              console.log('出错了' + err)
              reject(false)
              return
            }
            resolve(true)
          })
        })
      },
    
      updateAccount({ id, input }) {
        const data = input
        return new Promise((resolve, reject) => {
          pool.query('update account set ? where id = ?', [data, id], err => {
            if (err) {
              console.log('出错了' + err.message)
              return
            }
            resolve(data)
          })
        })
      },
    
      accounts() {
        return new Promise((resolve, reject) => {
          pool.query('select name, age, sex, department from account', (err, res) => {
            if (err) {
              console.log('出错了' + err)
              return
            }
            let arr = []
            for (let i = 0; i < res.length; i++) {
              arr.push({
                name: res[i].name,
                sex: res[i].sex,
                age: res[i].age,
                department: res[i].department,
              })
            }
            resolve(arr)
          })
        })
      },
    }
    
    

    代码片段

    为方便操作,我将完整代码片段放在最后,供你试一试。

    fakeDb
    const express = require('express')
    const app = express()
    const { graphqlHTTP } = require('express-graphql')
    const { buildSchema } = require('graphql')
    
    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      input AccountInput {
        name: String
        age: Int 
        sex: String
        department: String
        salary: Int
      }
    
      type Query {
        hello: String
        getClassMates(classNo: Int!): [String]
        account (username: String): Account
        accounts: [Account]
      }
    
      type Mutation {
          createAccount(input: AccountInput): Account
          updateAccount(id: ID!, input: AccountInput): Account
      }
    `)
    
    const fakeDb = {}
    
    const rootValue = {
      hello: () => 'Hello world!',
    
      getClassMates: ({ classNo }) => {
        const obj = {
          31: ['张三', '李四', '王五'],
          61: ['张大三', '李大四', '王大五'],
        }
        return obj[classNo]
      },
    
      account: ({ username }) => {
        const name = username
        const sex = 'nan'
        const age = 10
        const department = '开发部'
        const salary = ({ city }) => {
          if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
            return 10000
          }
          return 3000
        }
        return {
          name,
          sex,
          age,
          department,
          salary,
        }
      },
    
      createAccount({ input }) {
        // 模拟数据库保存
        fakeDb[input.name] = input
        // 返回保存结果
        return fakeDb[input.name]
      },
    
      updateAccount({ id, input }) {
        // 模拟更新数据库
        const updatedAccount = Object.assign({}, fakeDb[id], input)
        fakeDb[id] = updatedAccount
        return updatedAccount
      },
    
      accounts() {
        let arr = []
        for (const key in fakeDb) {
          arr.push(fakeDb[key])
        }
        return arr
      },
    }
    
    // 公开文件夹  供用户访问静态资源
    app.use(express.static('public'))
    
    // const middleware = (req, res, next) => {
    //   // console.log(req.headers.cookie)
    //   if (req.url.indexOf('/graphql') !== -1) {
    //     res.send(
    //       JSON.stringify({
    //         error: '您没有权访问这个接口',
    //       })
    //     )
    //     return
    //   }
    //   next()
    // }
    
    // app.use(middleware)
    
    app.use(
      '/graphql',
      graphqlHTTP({
        schema,
        rootValue,
        graphiql: true,
      })
    )
    
    app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'))
    
    
    MySQL
    const express = require('express')
    const app = express()
    const { graphqlHTTP } = require('express-graphql')
    const { buildSchema } = require('graphql')
    const mysql = require('mysql')
    const { resolve } = require('path')
    
    const pool = mysql.createPool({
      connectionLimit: 10,
      host: 'localhost',
      user: 'root',
      password: '123456',
      database: 'test',
    })
    
    const schema = buildSchema(`
      type Account {
        name: String
        age: Int
        sex: String
        department: String
        salary(city: String): Int
      }
    
      input AccountInput {
        name: String
        age: Int 
        sex: String
        department: String
        salary: Int
      }
    
      type Query {
        hello: String
        getClassMates(classNo: Int!): [String]
        account (username: String): Account
        accounts: [Account]
      }
    
      type Mutation {
          createAccount(input: AccountInput): Account
          updateAccount(id: ID!, input: AccountInput): Account
          deleteAccount(id: ID!): Boolean
      }
    `)
    
    const rootValue = {
      hello: () => 'Hello world!',
    
      getClassMates: ({ classNo }) => {
        const obj = {
          31: ['张三', '李四', '王五'],
          61: ['张大三', '李大四', '王大五'],
        }
        return obj[classNo]
      },
    
      account: ({ username }) => {
        const name = username
        const sex = 'nan'
        const age = 10
        const department = '开发部'
        const salary = ({ city }) => {
          if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
            return 10000
          }
          return 3000
        }
        return {
          name,
          sex,
          age,
          department,
          salary,
        }
      },
    
      accounts() {
        return new Promise((resolve, reject) => {
          pool.query('select name, age, sex, department from account', (err, res) => {
            if (err) {
              console.log('出错了' + err)
              return
            }
            let arr = []
            for (let i = 0; i < res.length; i++) {
              arr.push({
                name: res[i].name,
                sex: res[i].sex,
                age: res[i].age,
                department: res[i].department,
              })
            }
            resolve(arr)
          })
        })
      },
    
      createAccount({ input }) {
        const data = {
          name: input.name,
          age: input.age,
          sex: input.sex,
          department: input.department,
        }
        return new Promise((resolve, reject) => {
          pool.query('insert into account set ?', data, err => {
            if (err) {
              console.log('出错了' + err.message)
              return
            }
            resolve(data)
          })
        })
      },
    
      updateAccount({ id, input }) {
        const data = input
        return new Promise((resolve, reject) => {
          pool.query('update account set ? where id = ?', [data, id], err => {
            if (err) {
              console.log('出错了' + err.message)
              return
            }
            resolve(data)
          })
        })
      },
    
      deleteAccount({ id }) {
        return new Promise((resolve, reject) => {
          pool.query('delete from account where id = ?', id, err => {
            if (err) {
              console.log('出错了' + err)
              reject(false)
              return
            }
            resolve(true)
          })
        })
      },
    }
    
    // 公开文件夹  供用户访问静态资源
    app.use(express.static('public'))
    
    app.use(
      '/graphql',
      graphqlHTTP({
        schema,
        rootValue,
        graphiql: true,
      })
    )
    
    app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'))
    
  • 相关阅读:
    mybatis-plus解析
    ybatis中查询出多个以key,value的属性记录,封装成一个map返回的方法
    mybatis-plus分页记坑
    ComponentScan注解的使用
    fastJson序列化
    SpringBoot-RestTemplate测试Controller
    configparser模块
    python 将乱码转为汉字
    1.x 版本Django对应rest_framework版本
    docker容器内执行linux的dmidecode命令
  • 原文地址:https://www.cnblogs.com/guangzan/p/13345945.html
Copyright © 2020-2023  润新知