Skip to content

GraphQL API

graphql是一种数据查询语言, graphQL api和restful api具有相同的功能.

GraphQL的API通常是一个URL对应多种查询, 不同的查询是通过请求体来区分的.

环境准备:

  • 安装node.js,安装好之后通过npm命令在Terminal安装express模块、graphql模块和express-graphql模块。(eg:npminstallexpress)
  • 最好安装visualStudioCode编写javascript代码
  • 可选:安装好postman
  • 可选:python安装好了requests库(pip3installrequests)
  • graphql模块和express-graphql模块:

现在express-graphql模块(latestversion==0.12.0)已经不再更新也不再有⼈维护了,最新的expressgraphql模块需要依赖graphql模块(版本为:14.7.0或15.3.0),所以你需要预先安装graphql模块。

graphql模块当前最新版本是16.8.1,是不满⾜依赖条件的,所以安装的时候直接npminstallgraphql之后执⾏npminstallexpress-graphql会报错。

解决办法:

安装graphql模块的时候指定graphql模块的版本为可⽀持express-graphql的版本:npm install graphql@15.3.0

安装graphql模块之后再安装express-graphql模块:npm install express-graphql

  • postman:

    postman是⼀个API平台,它可以帮我们存储以及管理我们的API集合。通过使⽤postman,我们可以轻松地发送请求,检查响应以及调试我们的API。

1 HelloWorld Demo

demo refers to: https://graphql.org/graphql-js/running-an-express-graphql-server/

var express = require('express');

这段代码导入Express框架. Express是一个流行的Node.js Web框架, Node.js是一个JavaScript运行环境, 允许开发者在服务器端运行JavaScript.

通过导入Express, 开发者可以使用其功能和函数来创建Web应用程序和API. "require"函数是Node.js中的内置函数, 它允许开发者从外部文件或包中导入模版, 当前我们已经使用过npm工具本地安装express模块, 所以这里可以直接导入. 在这种情况下, 该代码导入Express模块并将其分配给变量"express", 以便在整个应用程序中使用.

var { buildSchema } = require('graphql');

在这行代码中, require('graphql')语句将graphql模块导入到您的应用程序中, 并使用解构方式将graphql模块中的buildSchema函数分配给变量{ buildSchema}. 然后, 您可以在代码中使用buildSchema函数来创建GraphQL Schema对象.

在GraphQL中, schema定义了客户端可以查询的数据类型以及它们之间的关系. 使用buildSchema函数可以创建schema对象. 这个schema对象可以被传递给GraphQL的执行器, 以便它可以执行来自客户端的查询, 并且返回所需的数据.

Schema(模式)指的是数据结构的定义或组织方式,描述了数据如何存储、组织以及数据之间的关系

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

虽然express-graphql可以与express一起使用, 但是它是一个独立的模块. 因此, 安装express模块并不会自动安装express-graphql模块.

如果您想使用express-graphql模块来构建GraphQL API, 您需要在项目中单独安装它. 您可以在终端或命令提示符窗口中使用npm (Node.js包管理器)来安装express-graphql模块. 命令是npm install express-graphql.

// Construct a schema, using GraphQL schema language.
// The parameter of buildSchema function is a String type.
var schema = buildSchema(`
    type Query{
        hello: String!
        }
    `);

这段schema定义了所有可能的类型和操作, 并且充当客户端和服务器之间的契约. 注意, buildSchema方法的参数这里使用的是反引号.

在这个schema中, 我们定义了一个类型: Query.

Query类型是GraphQL schema中的预定义类型. 其中定义了一些查询操作(方法), 客户端可以i使用这些操作从服务器中检索数据.

在Query类型中, 我们定义了一个字段: hello. hello字段是一个字符串类型, 它的值为String!, 表示这是一个非空的字符串类型.

在实际应用中, 我们需要提供一个名为hello的函数, 用于处理查询操作.

// The root provide a resolver function for each API endpoint
// 定义了一个根解析器对象root
var root = {
    hello: () => {
        return 'Hello World!';
    }
};

在GraphQL中, root对象是一个用于处理客户端请求的实际对象(根对象), 它包含了所有的处理函数.

hello方法是箭头函数的表示方式, 返回一个字符串Hello World, 这个方法用于处理hello查询操作. 在GraphQL中, 查询操作时客户端用于服务器检索数据的一种方式.

在实际应用中, 这些方法通常会从数据库或其他数据源中检索数据, 并将其转换为GraphQL中定义的类型.

var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

首先, 我们创建了一个Express应用程序.

然后, 我们使用app.use方法将GraphQL中间件添加到应用程序中. 在这里, 我们使用了graphqlHTTP函数创建了一个中间件, 并将其挂载到/graphql路径上. graphqlHTTP函数接收三个参数: schema, rootValue和graphql. Schema表示GraphQL schema, rootValue表示用于处理查询操作的根对象, graphql表示是否允许调试. Since we configured graphqlHTTP with graphiql: true, you can use the GraphQL tool to manually issue GraphQL queries. If you navigate in a web browser to http://localhost:3000/grpahql, you should see an interface that lets you enter queries.

最后, 我们调用了app.listen方法来启动应用程序并监听3000端口. 这意味着应用程序将在3000端口上监听来自客户端的请求. 客户端可以通过向/graphql路径发送请求, 使用定义好的GraphQL schema 进行查询操作, 并从根对象中获取数据.

node D:\GraphqlDemo\helloWorld.js

以上是在命令中使用Node.js运行上述js文件, 当服务器启动后, 我们可以使用浏览器或其他HTTP客户端向服务器发送请求, 查询hello, 并获取相应的数据.

  code node helloWorld.js 
node:internal/modules/cjs/loader:1143
  throw err;
  ^

Error: Cannot find module 'express'
Require stack:
- /Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1140:15)
    at Module._load (node:internal/modules/cjs/loader:981:27)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:177:18)
    at Object.<anonymous> (/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js:1:15)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Module._load (node:internal/modules/cjs/loader:1019:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js' ]
}

Node.js v18.20.5
  code npm install express   

added 69 packages in 3m

14 packages are looking for funding
  run `npm fund` for details
  code node helloWorld.js 
node:internal/modules/cjs/loader:1143
  throw err;
  ^

Error: Cannot find module 'graphql'
Require stack:
- /Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1140:15)
    at Module._load (node:internal/modules/cjs/loader:981:27)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:177:18)
    at Object.<anonymous> (/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js:5:23)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Module._load (node:internal/modules/cjs/loader:1019:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js' ]
}

Node.js v18.20.5
  code npm install graphql@15.3.0       

added 1 package, and audited 71 packages in 532ms

14 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
  code node helloWorld.js        
node:internal/modules/cjs/loader:1143
  throw err;
  ^

Error: Cannot find module 'express-graphql'
Require stack:
- /Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1140:15)
    at Module._load (node:internal/modules/cjs/loader:981:27)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:177:18)
    at Object.<anonymous> (/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js:8:23)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Module._load (node:internal/modules/cjs/loader:1019:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/eve/Desktop/CS/Tools/GraphQL/code/helloWorld.js' ]
}

Node.js v18.20.5
  code npm install express-graphql
npm warn deprecated express-graphql@0.12.0: This package is no longer maintained. We recommend using `graphql-http` instead. Please consult the migration document https://github.com/graphql/graphql-http#migrating-express-grpahql.

added 5 packages, and audited 76 packages in 641ms

14 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Screenshot 2025-03-17 at 16.20.07

Screenshot 2025-03-17 at 16.20.57

npm install express

npm install graphql@15.3.0

npm install express-graphql

Screenshot 2025-03-17 at 16.22.39

Screenshot 2025-03-17 at 16.24.19

Screenshot 2025-03-17 at 16.30.31

默认带上query 你写和不写都没有关系

Screenshot 2025-03-17 at 16.29.14

2 graphQL数据类型

2.1 基本数据类型

  • 基本数据类型: String, Int, Float, Boolean, ID(ID 类型的本质是字符串类型, 但是如果是ID类型就表示数据不能重复, 但是GraphQL本身并没有内置的机制来强制确保ID类型的数据不重复, 需要协同数据库共同控制), 这几个类型都可以在schema声明的时候直接使用.
  • [类型] 代表数组, 例如[Int]代表整数数组

Screenshot 2025-03-17 at 16.57.25

Screenshot 2025-03-17 at 16.57.34

var express = require('express'); // 这行代码引入了express模块
// var是用来声明变量的关键字 express是一个函数,
// 通过require方法引入了express模块,并将其赋值给变量express
// require是node.js中的一个函数,用于引入模块
var { buildSchema } = require('graphql');
// 引入graphql模块中的buildSchema函数,用于构建GraphQL模式
// { }是ES6中的解构赋值语法,用于从模块中引入特定的函数或对象
var { graphqlHTTP } = require('express-graphql');
// 引入express-graphql模块中的graphqlHTTP函数,用于处理GraphQL请求
// express-graphql是一个中间件,用于将GraphQL与Express结合起来
// 引入了express-graphql模块中的graphqlHTTP函数,用于处理GraphQL请求
// graphqlHTTP是一个中间件函数,用于处理GraphQL请求

// // Construct a schema, using GraphQL schema language.
// // The parameter of buildSchema function is a String type.
// var schema = buildSchema(`
//     type Query{
//         hello: String!
//         }
//     `);

// // The root provide a resolver function for each API endpoint
// // 定义了一个根解析器对象root
// var root = {
//     hello: () => {
//         return 'Hello World!';
//     }
// };
// Construct a schema, using GraphQL schema language.
// The parameter of buildSchema function is a String type.
var schema = buildSchema(`
    type Query{
        hello: Int!
        }
    `);
// 为什么要!?
// 在GraphQL中,感叹号(!)表示该字段是非空的(non-nullable),

// The root provide a resolver function for each API endpoint
// 定义了一个根解析器对象root
var root = {
    hello: () => {
        return 1;
    }
};



var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

2.2 数据非空判断

// Construct a schema, using GraphQL schema language.
// The parameter of buildSchema function is a String type.
var schema = buildSchema(`
    type Query{
        hello: [String!]! 
        }
    `);
// [String!]! 表示数组类型,数组中的元素是非空的,并且整个数组本身也是非空的。

// The root provide a resolver function for each API endpoint
// 定义了一个根解析器对象root
var root = {
    hello: () => {
        return null; // 数组不能为空, 表示如果返回null,则会报错
    }
};
// 数组不能为空, 表示如果返回null,则会报错
// 数组中的每一个元素不能为空, 则返回['hello', null] 数据会报错.

Screenshot 2025-03-17 at 17.03.02

Screenshot 2025-03-17 at 17.03.10

2.3 自定义数据类型

GraphQL中除了几种基本的数据类型, 有两种预定义的数据类型, 一种是Query, 另一种是Mutation.

如果用户想要自定义其他数据类型, 则可以在buildSchema方法中通过type定义其他类型, 例如我们想定义一个User类型和Post(博客)类型.

var express = require('express'); // 这行代码引入了express模块
// var是用来声明变量的关键字 express是一个函数,
// 通过require方法引入了express模块,并将其赋值给变量express
// require是node.js中的一个函数,用于引入模块
var { buildSchema } = require('graphql');
// 引入graphql模块中的buildSchema函数,用于构建GraphQL模式
// { }是ES6中的解构赋值语法,用于从模块中引入特定的函数或对象
var { graphqlHTTP } = require('express-graphql');
var schema = buildSchema(
    `
    type User{
        id: ID!
        name: String!
        posts: [ID!]
    }

    type Post{
        id: ID!
        title: String!
        content: String!
        author: ID!
    }

    type Query{
        getUser: User!
        getPost: Post!
    }
    `
);

var root = {
    getUser() {
        return {
            id: '001',
            name: 'Alice',
            postw: ['11', '12']
            // 实际要返回数据库里的值
        }
    },
    getPost() {
        var id = '11'
        var title = 'What is API'
        var content = 'API is an interface ...'
        var author = '001'
        return {
            id,
            title,
            content,
            author
        }
    }
};



var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

Screenshot 2025-03-17 at 18.43.44

Screenshot 2025-03-17 at 18.44.59

在getUser和getPost方法的具体实现中, 都需要返回对应的自定义数据类型, 我们的返回值是字典类型, 可以返回比期望的数据类型更多的键值对, 因为graphQL只会匹配和期望的数据类型中相同的字段. 但是不能建议更少, 不然会容易报错. 这样在查询的时候对应被遗漏的字段的值为null, 如果恰好被遗漏的字段的值不能为null, 又恰好去查询了这个字段的值, 那么查询就会报错.

var express = require('express'); // 这行代码引入了express模块
// var是用来声明变量的关键字 express是一个函数,
// 通过require方法引入了express模块,并将其赋值给变量express
// require是node.js中的一个函数,用于引入模块
var { buildSchema } = require('graphql');
// 引入graphql模块中的buildSchema函数,用于构建GraphQL模式
// { }是ES6中的解构赋值语法,用于从模块中引入特定的函数或对象
var { graphqlHTTP } = require('express-graphql');
var schema = buildSchema(
    `
    type User{
        id: ID!
        name: String!
        posts: [ID!]
    }

    type Post{
        id: ID!
        title: String!
        content: String!
        author: ID!
    }

    type Query{
        getUser: User!
        getPost: Post!
    }
    `
);

var root = {
    getUser() {
        return {
            id: '001',
            // name: 'Alice',
            postw: ['11', '12'],
            // 实际要返回数据库里的值
            hh: 'hh' // 多了没事儿
            // 少了不行
        }
    },
    getPost() {
        var id = '11'
        var title = 'What is API'
        var content = 'API is an interface ...'
        var author = '001'
        return {
            id,
            title,
            content,
            author
        }
    }
};



var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

Screenshot 2025-03-17 at 19.21.51

由于getUser方法和getPost方法的返回类型都是自定义类型, 在调用的时候我们必须使用{}来指定我们希望服务器端返回给我们哪几个字段的值, 一下方法是错的

query{# this is mean root bracket
  getUser
}

在graphql中如果这样写, 它会默认指定一个id, 自动变成

Screenshot 2025-03-17 at 19.25.23

但是postman中会报错:

Screenshot 2025-03-17 at 19.25.59

使用python中的request调用以上API:

Screenshot 2025-03-17 at 19.27.49

Screenshot 2025-03-17 at 19.31.21

curl --location --request GET '127.0.0.1:3000/graphql' \
--header 'Content-Type: application/json' \
--data '{"query":"{# this is mean root bracket\n  getUser{\n    id\n  }\n}\n","variables":{}}'

curl:

curl是常用的命令行工具, 用来请求Web服务器. 它的名字就是客户端(client)的URL工具的意思

Window如果已经安装了git环境, 那么git环境自带curl, 可以直接在命令行使用curl命令. 如果没有安装git, 需要的话可以根据其他教程安装好curl环境. Window中, 需要设置Line continuation character

Set a character used to mark the continuation of a statement on the next line (generally, \ for OSX/Linux, ^ for Windows cmd and ` for Powershell)

Quote Type

String denoting the quote type to use (single or double) for URL (Use double quotes when running curl in cmd.exe and single quotes for the rest)

MacOS自带curl环境, 可以直接在Terminal使用curl命令

Screenshot 2025-03-17 at 19.33.08

graphQL.py:

import requests
json_data = {
    "query": "{\n getUser{\n id\n name\n posts\n }\n}",
    "variables": {}
}

r = requests.get(url="http://127.0.0.1:3000/graphql", json=json_data)
print(r.status_code)
print(r.json())

Screenshot 2025-03-17 at 19.57.06

  code python3 -m pip install requests


[notice] A new release of pip is available: 24.3.1 -> 25.0.1
[notice] To update, run: python3.13 -m pip install --upgrade pip
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:
    
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
    
    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with
    
    brew install pipx
    
    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.
    
    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.
    
    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
  code 
  code python3 -m venv myenv

  code source ./myenv/bin/activate

(myenv)   code pip install requests

Collecting requests
  Downloading requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)
Collecting charset-normalizer<4,>=2 (from requests)
  Downloading charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl.metadata (35 kB)
Collecting idna<4,>=2.5 (from requests)
  Downloading idna-3.10-py3-none-any.whl.metadata (10 kB)
Collecting urllib3<3,>=1.21.1 (from requests)
  Downloading urllib3-2.3.0-py3-none-any.whl.metadata (6.5 kB)
Collecting certifi>=2017.4.17 (from requests)
  Downloading certifi-2025.1.31-py3-none-any.whl.metadata (2.5 kB)
Downloading requests-2.32.3-py3-none-any.whl (64 kB)
Downloading certifi-2025.1.31-py3-none-any.whl (166 kB)
Downloading charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl (195 kB)
Downloading idna-3.10-py3-none-any.whl (70 kB)
Downloading urllib3-2.3.0-py3-none-any.whl (128 kB)
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2025.1.31 charset-normalizer-3.4.1 idna-3.10 requests-2.32.3 urllib3-2.3.0

[notice] A new release of pip is available: 24.3.1 -> 25.0.1
[notice] To update, run: pip install --upgrade pip
(myenv)   code python graphQL.py

500
{'errors': [{'message': 'Cannot return null for non-nullable field User.name.', 'locations': [{'line': 4, 'column': 2}], 'path': ['getUser', 'name']}], 'data': None}
(myenv)   code 

Solution:

使用了Homebrew安装的Python环境(macOS),它默认设置了保护,防止你直接使用pip安装库到系统级环境中。

你应该使用虚拟环境来管理你的Python项目。使用虚拟环境既安全,也更容易管理依赖。

进入你的项目文件夹:

python3 -m venv myenv

激活虚拟环境:

source ./myenv/bin/activate

终端前会显示类似 (myenv) 的提示,表示虚拟环境已激活。

在虚拟环境中运行:

pip install requests

现在,requests会被安装到当前虚拟环境,而不会影响系统环境。

确保虚拟环境激活后,再运行:

python graphQL.py

用完后可输入以下命令退出:

deactivate

PS. 尽量避免使用 --break-system-packages 或者禁用环境管理警告,因为这样做可能破坏Homebrew或系统的Python环境。

养成每个项目使用独立虚拟环境的习惯,能避免项目间依赖版本冲突的问题。

2.4 站在graphQL角度重新考虑数据的组成

在之前我们对User类型和Post类型的定义都是站在数据库的角度, 其实和restful API的角度是一样的.

restful用不同的url来区分资源, graphql用类型区分资源.

现在我们的需求是获取id为1的用户名称以及其发布的所有博客标题.

站在查询的角度重新思考一下User类型定义, 以下会更符合graphQL API的角度.

type User{
  id: ID!
  name: String!
  posts: [Post!]
}

3 graphQL中的参数传递

3.1 参数为基本数据类型

和js传递参数一样, 对象解构语法中小括号内定义行参, 但是参数需要在schema中定义类型.

graphQL请求参数, 参数的值如果是String, 必须使用双引号, 使用单引号会报错.

var express = require('express'); // 这行代码引入了express模块
// var是用来声明变量的关键字 express是一个函数,
// 通过require方法引入了express模块,并将其赋值给变量express
// require是node.js中的一个函数,用于引入模块
var { buildSchema } = require('graphql');
// 引入graphql模块中的buildSchema函数,用于构建GraphQL模式
// { }是ES6中的解构赋值语法,用于从模块中引入特定的函数或对象
var { graphqlHTTP } = require('express-graphql');
var schema = buildSchema(
    `
    type User{
        id: ID!
        name: String!
        posts: [ID!]
    }
    type Post{
        id: ID!
        title: String!
        content: String!
        author: ID!
    }
    type Query{
        getUser(id: ID!, name : String!) : User!
        getPost(id: ID!): Post!
    }
    `
);



var root = {
    getUser({ id, name }) {
        var User1 = { id: '001', name: 'Alice', posts: ['11', '12'] }
        var User2 = { id: '002', name: 'Bob', posts: ['21', '22'] }
        if (User1.id == id && User1.name == name) {
            return User1
        }
        else {
            return User2
        }
    },
    getPost({ id }) {
        var Post1 = { id: '11', title: 'What is API', content: 'API is an interface ...', author: '001' }
        var Post2 = { id: '21', title: 'What is GraphQL', content: 'GraphQL is a query language ...', author: '002' }
        if (Post1.id == id) {
            return Post1
        }
        else {
            return Post2
        }
    }
};

var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

Screenshot 2025-03-18 at 20.53.03

ID 也是个String

Screenshot 2025-03-18 at 20.57.20

3.2 参数为自定义数据

Mutation详解章节会具体使用.

4 why schema comes in

The GraphQL query language is basically about selecting fields on objects.

If you've seen a GraphQL query before, you know that the GraphQL query language is basically about selecting fields on objects. So, for example, in the following query:

{
    hero {
        name,
        appearsIn
    }
}

//response
{
    "data":{
        "hero":{
            "name": "R2-D2",
            "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
            ]

        }
    }
}

We start with a special "root" obejct

We select the hero field on that

For the object returned by hero, we select the name and appearsIn fields

Because the shape of a GraphQL query closely matches the result, you can predict what the query will return without knowing that much about the server. But it's useful to have an exact description of the data we can ask for - what fields can we select? What kinds of objects might they return? What fields are available on those sub-objects? That's where the schema comes in.

Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema.

More message, please refer to : https://graphql.org/learn/schema/

5 graphQL请求设置变量

5.1 postman中为graphQL请求设置变量

Screenshot 2025-03-19 at 08.17.02

Screenshot 2025-03-19 at 08.19.45

Screenshot 2025-03-19 at 08.57.03

import requests

# json_data = {
#     "query": "{\n getUser{\n id\n name\n posts\n }\n}",
#     "variables": {}
# }

a=11
json_data = {
    "query": "query($id: ID!){\n getPost(id:$id){\n id\n title\n }\n}",
    "variables": {"id": a}
}

r = requests.get(url="http://127.0.0.1:3000/graphql", json=json_data)
print(r.status_code)
print(r.json())

Screenshot 2025-03-19 at 09.08.27

5.2 bruno中为graphQL请求设置变量

Screenshot 2025-03-19 at 09.17.54

6 query 和 mutation

在GraphQL中, Query和Mutation是两种特殊的根操作类型, 都是预定义好的, 用于定义可执行的查询和变更操作.

Query类型:

Query类型用于定义可以执行的读取操作. 它表示你可以从服务器获取数据的能力. 在前面我们一直是查询操作, 所以我们所有查询操作的定义都写在Query type下面:

type Query{
    getUser(id: ID!, name: String!): User!
    getPost(id: ID!): Post!
}

Mutation类型:

Mutation类型用于定义可以执行的写入或修改操作. 它表示你可以向服务器发送请求以更改数据的能力. 通常, Mutation类型中的字段对应用于可以对服务器上的数据进行修改的操作.

7 mutation详解

7.1 使用mutation修改数据

假设目前我们需要创建一条Account数据. 我们可以和之前一样使用type自定义Account数据类型:

type Account{
    id: ID!
    name: String
    age: Int
    salary(city : String): Int
}

但是使用type定义出来的数据类型只能作为查询类型, 却不能作为请求的输入类型. 所以我们还需要使用input定义输入类型AccountInput作为请求的输入参数:

input AccountInput{
  name: String
  age: Int
  city: String
}

接下来我们定义mutation类型以及其createAccount操作, createAccount的输入参数是AccountInput类型:

type Mutation{
    createAccount(input: AccountInput): Account
}

注意, 由于如果只有Mutation类型而没有Query类型的话, graphQL不支持, 所以我们再添加一个Query类型:

type Query{
  getAccount(id: ID!): Account
}

完整代码:

var express = require('express'); // 这行代码引入了express模块
// var是用来声明变量的关键字 express是一个函数,
// 通过require方法引入了express模块,并将其赋值给变量express
// require是node.js中的一个函数,用于引入模块
var { buildSchema } = require('graphql');
// 引入graphql模块中的buildSchema函数,用于构建GraphQL模式
// { }是ES6中的解构赋值语法,用于从模块中引入特定的函数或对象
var { graphqlHTTP } = require('express-graphql');
var schema = buildSchema(
    `
    type Account{
        id: ID!
        name: String!
        age: Int
        salary(city : String): Int
    }
    input AccountInput{
        name: String
        age: Int
        city : String
    }

    type Mutation{
        createAccount(input: AccountInput): Account    
    }

    type Query{
        getAccount(id: ID!): Account
    }
    `
);



var root = {
    createAccount({ input }) {
        var name = input.name
        var age = input.age
        if (input.city == 'ShenZhen') {
            var salary = 2000
        } else {
            var salary = 8000
        }
        return { id: "001", name, age, salary }
    },
    getAccount({ id }) {
        if (id == '001') {
            var name = 'xx'
            var age = 18
        }
        var salary = ({ city }) => {
            if (city == 'Shenzhen') {
                return 2000;
            } else {
                return 1000;
            }
        }
        return { id, name, age, salary }
    }
};

var app = express(); // 创建一个Express应用程序
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(3000);

Screenshot 2025-03-19 at 17.54.51

Screenshot 2025-03-19 at 17.56.16

mutaion 必须post

Screenshot 2025-03-19 at 18.02.55

8 补充知识

8.1 GraphQL操作类型

在上面的示例中, 我们经常省略query操作类型, 对于操作类型, 除非是query操作类型, 其他的Mutation操作类型和Subscription操作类型都不能省略, 需要明确表示出来.

GraphQL的Subscription(订阅):

https://www.apollographql.com/docs/react/data/subscriptions

Subscription是一种用于实时数据推送的功能. 它允许客户端与服务器建立长连接, 并通过订阅特定的事件或数据源来接收实时更新.

Subscription在GraphQL中用于订阅特定事件或数据的更改, 并在事件发生时将更新推送给客户端. 这种实时性的数据推送对于需要实时更新的应用程序非常有用, 如聊天应用, 实时数据监控, 实时通知等.

使用Subscription, 客户端可以定义一个订阅操作, 并指定所需的事件或数据源. 当这些事件或数据源发生更改时, 服务器会向订阅的客户端发送相应的更新信息. 这样, 客户端就可以接收到实时更新, 并及时更新应用程序的用户界面或执行其他逻辑.

下面是一个示例, 展示了使用GraphQL Subscription的基本结构:

subscription{
  newMessage{
    id
    content
    timestamp
    sender{
      id
      name
    }
  }
}

在上面的示例中, 我们定义了一个名为“newMessage”的订阅操作, 用于订阅新消息的事件. 当有新消息时, 服务器会将消息的相关信息推送给订阅的客户端.

GraphQL的Subscription 功能使用WebSocket或其他实时通信协议来实现长连接, 并通过这种方式实现实时数据推送. 它提供了一种灵活且高效的方式来处理实时数据需求, 并可以与GraphQL的查询和变更操作结合使用, 提供全面的数据管理解决方案.

8.2 GraphQL操作名称

前面我们提到, query操作类型是可以省略的, 但是在实际的生产过程中, 为了具体表示操作的意义, 一般不会省略操作类型.

在GraphQL中, 还有可选字段叫做操作名称(Opeartion Name), 操作名称是可选的, 它用于标识一个查询.

在前面的例子中我们都没有使用过操作名称:

query{
    getUser{
        id
    }
}

现在我们为我们的操作添加上操作名称(操作名称可以随便写, 但是最好有某种意义. 添加了操作名称并不会对实际的查询有什么影响, 但当出现问题时, 您在网络日志或GraphQL服务器日志中看到错误, 通过名称识别代码库中的查询比尝试破译内容更容易)

query Myname{
    getUser{
        id
    }
}

但需要注意, 为了更好理解, 操作名称一般都需要实际意义, 不能乱取, 比如上面的例子, 将操作名称定为“GetUser” 会更好些.

在之前的变量练习中, 我们没有使用操作名称, 直接在操作类型后面添加了变量的定义:

query($id: ID!){
    getPost(id:$id){
        id
        title
    }
}

实际上, 我们也可以在操作名称之后添加变量的定义.

query GetPost($id: ID!){
  getPost(id:$id){
        id
        title
    }
}

8.3 graphQL传递参数必须是json格式吗

我们之前使用的requests库的调用代码里面, 参数的传递类型一直是json

其实使用params json data形式传递参数是没有什么区别的

8.4 使用其他模块开发graphql API

Express GraphQL (not recommended)

graphql-http

Apollo Server

...

https://graphql.org/learn/serving-over-http/

https://graphql.org/community/tools-and-libraries/