基本示例
服务端
使用 Express 并额外安装两个依赖:
npm install express express-graphql graphql --save
其中express用来启动服务器。express-graphql用来构建GraphQL API服务器,响应/graphql的 HTTP 请求。graphql用来创建 schema
var express = require('express');var { graphqlHTTP } = require('express-graphql');var { buildSchema } = require('graphql');// 使用 GraphQL Schema Language 创建一个 schemavar schema = buildSchema(`type Query {hello: String}`);// root 提供所有 API 入口端点相应的解析器函数var root = {hello: () => {return 'Hello world!';},};var app = express();app.use('/graphql', graphqlHTTP({schema: schema,rootValue: root,graphiql: true,}));app.listen(4000);
当服务端设置graphiql: true时,可以使用浏览器来手动执行GraphQL查询。
直接访问http://localhost:4000/graphql即可看到界面,然后用输入GraphQL查询语句,即可看到对应的数据类型:

客户端
浏览器端采用fetch也可以查询,在客户端打开开发者工具,输入以下代码:
async function request() {const res = await fetch('http://localhost:4000/graphql', {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',},body: JSON.stringify({ query: '{ hello }' }),});await res.json().then(data => console.log(data));}request();
就可以看到对应的数据
{data: {hello: 'hello world'}}
基本类型
基本类型有String、Int、Float、Boolean 和 ID,这些类型在buildSchema中可以直接使用。
其中 ID 表示一个不会重复的值,下面的例子中使用uuid来生成。
默认情况下,每个类型都是可以为空的——这表示所有标量类型都可以返回 null。
如果不想为空则可以用一个感叹号表示一个类型不可为空,例如:String!表示非空字符串。
如果是列表类型,使用方括号将对应类型包起来,如 [Int] 就表示一个整数列表。
var { v4: uuidv4 } = require('uuid');var schema = buildSchema(`type Query{name:String,age:Int,isMarried:Boolean,hobbies:[String!],id:ID}`);var rootValue = {name() {return 'this is name';},age() {return 123;},isMarried() {return false;},hobbies() {return ['吃饭', '睡觉'];},id() {return uuidv4();},};
传递参数
服务端 schema这样写:
var schema = buildSchema(`type Query{rollDice(numDice: Int!, numSides: Int): [Int]}`);var rootValue = {rollDice({ numDice, numSides }) {let result = [];for (let i = 0; i < numDice; i++) {result.push(Math.floor(1 + Math.random() * numSides));}return result;},};
需要注意的点是rootValue.rollDice方法只接受一个参数,我们对应schema中的语句,
rollDice(numDice: Int!, numSides: Int): [Int]
需要将查询的参数给解构出来。
rollDice({ numDice, numSides }){}
前端通过GraphQL查询:
{rollDice(numSides:6,numDice:3)}
通过代码查询,使用$来定义查询中的变量,并将变量作为单独映射来传递。
对象类型
现在我们把name、age、hobbies等都封装成一个对象类型,让很多地方可以通过查询 user来获取数据:
var schema = buildSchema(`type User{name:String,age:Int,isMarried:Boolean,hobbies:[String!],id:ID,}type Query{user:User}`);var rootValue = {user() {return {name: rootValue.name,// 可以写函数方法,会自动调用age: rootValue.age,isMarried: rootValue.isMarried,hobbies: rootValue.hobbies(),// 也可以直接返回函数调用后的数据id: rootValue.id(),};},name() {return 'this is name';},age() {return 123;},isMarried() {return false;},hobbies() {return ['吃饭', '睡觉'];},id() {return uuidv4();}};
这时候在前端graphql查询:
{user {idnameisMarriedhobbiesage}}/*{"data": {"user": {"id": "a57fcb5d-1a32-4ac6-a047-818d1af3c54b","name": "this is name","isMarried": false,"hobbies": ["吃饭","睡觉"],"age": 123}}}*/
官方示例中的结合 class 实现是这样的:
比如现在要获取一个骰子,在后端的Query是这样写的:
type RandomDie {numSides: Int!rollOnce: Int!roll(numRolls: Int!): [Int]}type Query{getDie(numSides: Int): RandomDie,}
然后定义一个RandomDie的类
class RandomDie {numSides: number;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;}}
然后在rootValue中返回这个类的实例,也就是对象
var rootValue = {getDie: ({ numSides }) => {return new RandomDie(numSides || 6);},};
此时可以通过client的GraphQL来查询:
{getDie{roll(numRolls:3)numSidesrollOnce}}
当对一个返回对象类型的 API 发出 GraphQL 查询时,可以通过嵌套 GraphQL 字段名来一次性调用对象上的多个方法。
这种方式的好处是将相同类别的查询封装到一个 class 中,适合于面向类思维的编程方式。
变更和输入类型
如果对数据进行修改,比如插入或者更新操作,这时候需要使用Mutation类型而不是Query
比如现在要添加一个信息和查询一个信息,分别对应的schema是这样的:
type Mutation {setMessage(message: String): String}type Query {getMessage: String}
mutation会返回数据库所存的数据
例如,我把信息都存到一个变量里,最后返回出去的rootValue需要这么写:
var fakeDatabase = {};var root = {setMessage: ({message}) => {fakeDatabase.message = message;return message;},getMessage: () => {return fakeDatabase.message;}};
前端根据上面的配置需要使用mutation来发送:
mutation{setMessage(message:"这是前端发送的数据")}/*{"data": {"setMessage": "这是前端发送的数据"}}*/
不过很多时候后端都会接受多个输入,但是输出是一致的。比如当 create 时,会判断前端有没有发送 id,如果没有则会新建一条消息,如果有则会更新与 id 相匹配的数据,最后返回给前端的都是同样的数据类型,这时候就要用输入类型来简化 schema,使用input关键字来定义输入:
input MessageInput {content: Stringauthor: String}
然后定义新增、更新和删除的schema
type Message {id: ID!content: Stringauthor: String}type DeleteStatus{success:Boolean}type Mutation {createMessage(createInput:messageInput):MessageupdateMessage(id:ID!,updateInput: messageInput): MessagedeleteMessage(id:ID!):DeleteStatus}type Query {getMessage(id:ID!): Message}
在 rootValue 中,我们都返回一个 class作为输出
let dataBase: any = {'1': {content: '初始数据库的内容',author: 'qiuyanxi',},};// 如果 Message 拥有复杂字段,我们把它们放在这个对象里面。class Message {id: string;content: string;author: string;constructor(id, { content, author }) {this.id = id;this.content = content;this.author = author;}}var rootValue = {getMessage({ id }) {return new Message(id, dataBase[id]);},createMessage({ createInput }) {let id = uuidv4();dataBase.id = createInput;return new Message(id, createInput);},updateMessage({ id, updateInput }) {dataBase[id] = updateInput;return new Message(id, updateInput);},deleteMessage({ id }) {if (!dataBase[id]) {return { success: false };}delete dataBase[id];return { success: true };},};
最后我们在GraphQL浏览器端使用mutation进行查询
mutation {createMessage(createInput: {author: "qiuyanxi", content: "这是内容"}) {idauthorcontent}updateMessage(id: "1", updateInput: {author: "qiuyanxi", content: "这是另一个内容"}) {authorcontent}deleteMessage(id:"1") {success}}/*{"data": {"createMessage": {"id": "5ab856bf-da39-4afd-8b6a-7c8ebee8c4e3","author": "qiuyanxi","content": "这是内容"},"updateMessage": {"author": "qiuyanxi","content": "这是另一个内容"},"deleteMessage": {"success": true}}}*/
客户端携带参数请求
上面的案例中,客户端发送query请求并携带参数查询信息是这样写的:
async function request() {const id="1"const res = await fetch('http://localhost:4000/graphql', {method: 'POST',headers: {'Content-Type': 'application/json',Accept: 'application/json',},body: JSON.stringify({query: `{getMessage(id:${id}){idcontentauthor}}`,}),});await res.json().then(data => console.log(data));}
更加符合开发要求的写法是带上服务器中写的类型:
body: JSON.stringify({query: `query GetMessage($id:ID!){getMessage(id:$id){idcontentauthor}}`,variables: {id,},}),
通过$id来作为GraphQL 的变量,然后它的类型就是服务端 GraphQL服务端的类型ID!,最后通过variables将声明好的变量给传递进去。
对应createMessage的客户端查询是这么写的
body: JSON.stringify({query: `mutation CreateMessage($createInput:MessageInput){createMessage(createInput:$createInput){idcontentauthor}}`,variables: {createInput: {content: '内容',author: 'qyx',},},}),
客户端开发中,不可能去看服务端的代码,所以一般都会按照GraphQL的客户端 doc 来写对应的类型。

