尝试使用官方教程学习 GraphQL

2023-12-31 15:43:05 浏览数 (2)

  • Apollo Client
  • Relay

官方教程(JavaScript)


入门指南

试用的存储库在此处

准备软件包

代码语言:bash复制
npm init -y && npm i ts-node graphql

在 GraphQL 中获取数据需要定义查询类型(Query type)的模式以及实际处理数据的被称为 Resolver 的函数的实现。需要注意的是,在 Query 类型中定义了用于获取数据的 API。除了 Query 类型,还有用于添加、修改、删除数据的 Mutation 类型,以及用于订阅事件的 Subscription 类型。

以下是一个返回 Hello World 的 Query 类型 API 的实现示例。

代码语言:ts复制
import { graphql, buildSchema } from 'graphql';

// 在 Query 类型中定义模式
// 定义返回字符串的名为 hello 的 API
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// 定义处理查询的 Resolver
const rootValue = {
  hello: () => {
    return 'Hello world!';
  },
};

const main = async () => {
  const query = '{ hello }';

  // 执行查询
  const response = await graphql({
    schema,
    source: query,
    rootValue,
  });

  console.log(response);
};

main();

执行

代码语言:sh复制
npx ts-node server

{ data: Object: null prototype { hello: 'Hello world!' } }

Running an Express GraphQL Server

可以使用 Express 将 GraphQL 服务器挂载到 HTTP 终端点上

安装包

代码语言:bash复制
npm init && npm i ts-node graphql express-graphql express @types/express

使用 Express 将 GraphQL 服务器映射到 HTTP 终端点的实现示例

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const root = {
  hello: () => {
    return 'hello world';
  },
};

const app = express();
app.use(
  '/graphql', // 将 GraphQL 服务器映射到 /graphql
  graphqlHTTP({
    schema,
    rootValue: root,
    graphiql: true, // 启用 GraphQL 的 Web 客户端 GraphiQL。
  })
);

app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');

运行graphql服务器

代码语言:sh复制
npx ts-node server

在浏览器中访问以下 URL,即可访问 GraphiQL,通过 Web UI 执行 GraphQL 查询。

https://localhost:4000/graphql

在屏幕的左侧输入以下查询并执行,将会返回结果。

代码语言:graphql复制
{
    hello
}

GraphQL Clients

虽然存在 GraphQL 客户端库,但也可以通过简单的 HTTP POST 请求轻松发出查询。在 REST API 中,根据用途使用 GET/DELETE/POST/PUT 等不同的请求方法,但在 GraphQL 中,所有查询都使用 POST。

对于使用 Express GraphQL Server 创建的 GraphQL 服务器,可以通过执行以下 curl 命令返回 JSON 格式的数据。

代码语言:sh复制
curl -X POST -H "Content-Type: Application/json" -d '{"Query": "{hello}"}' http://localhost:4000/graphql

此外,GraphQL 也允许向 API 端点传递参数。

通过在查询中指定以 $ 为前缀的关键字,并在变量中传递具有相应关键字属性的对象,可以自动转义值并发出查询。

代码语言:ts复制
const dice = 3;
const sides = 7;
      
// 为了传递 dice 和 sides 作为变量,
// 通过指定 $dice 和 $sides 来创建查询。
const query = `query RollDice($dice: Int!, $sides: Int) {
    rollDice(numDice: $dice, numSides: $sides)
}`;

const result = await fetch("/graphql2", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify({
    query,
    variables: { dice, sides },// 传递包含 dice 和 sides 的对象
  }),
});

Basic Types

在 GraphQL 中的基本类型及其对应的 JavaScript 类型如下:

  • String:字符串型 → string
  • Int:整数型 → number
  • Float:浮点数型 → number
  • Boolean:布尔型 → boolean
  • ID:唯一标识符 → string
    • GraphQL 客户端似乎会根据此ID执行缓存等操作

所有基本类型都是可空的。

如果不允许为 Null,则在末尾添加 !(例如,String!)。

对于列表类型,使用 [] 括起来(例如,String)。

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

const schema = buildSchema(`
  type Query {
    quoteOfTheDay: String
    random: Float!
    rollThreeDice: [Int]
  }
`);

const 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));
  },
};

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

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Passing Arguments

在 GraphQL 中,也可以向端点传递参数。

参数需要指定名称和类型。

代码语言:graphql复制
type Query {
  rollDice(numDice: Int!, numSides: Int): [Int]
}

由于 numDice 使用 ! 保证非空,因此可以省略服务器的验证。

在带有参数的 API 中,参数将作为对象传递给解析器的第一个参数。

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

const schema = buildSchema(`
type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
}
`);

// RollDice 的参数
interface RollDiceArgs {
  numDice: number;
  numSides: number;
}

const root = {
  //解析器的第一个参数作为对象传递了参数。
  rollDice: ({ numDice, numSides }: RollDiceArgs) => {
    const output: number[] = [];

    for (let i = 0; i < numDice; i  ) {
      output.push(1   Math.floor(Math.random() * (numSides || 6)));
    }

    return output;
  },
};

const app = express();
app.use(express.static('public'));
app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    rootValue: root,
    graphiql: true,
  })
);

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Object Type

在 GraphQL 的模式中,可以定义具有自定义行为的对象。

代码语言:ts复制
// 定义具有自定义行为的 RandomDie
type RandomDie {
  roll(numRolls: Int!): [Int]
}

type Query {
  // 返回 RandomDie 的 API
  getDie(numSides: Int): RandomDie
}

getDie 和 RandomDie 的实现示例

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

// 由于可以访问 RandomDie 的公共成员,
// 因此也要为 numSides 和 rollOnce 定义模式
const schema = buildSchema(`
type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int!]!
}

type Query {
    getDie(numSides: Int): RandomDie
}
`);

class RandomDie {
  constructor(private _numSides: number) {}

  get numSides() {
    return this._numSides;
  }

  rollOnce() {
    return 1   Math.floor(Math.random() * this._numSides);
  }

  // 参数以对象形式传递,因此使用解构赋值接收它们
  roll({ numRolls }: { numRolls: number }) {
    const output: number[] = [];

    for (let i = 0; i < numRolls; i  ) {
      output.push(this.rollOnce());
    }

    return output;
  }
}

interface GetDieArg {
  numSides: number;
}

const root = {
  getDie: ({ numSides }: GetDieArg) => {
    return new RandomDie(numSides || 6);
  },
};

const app = express();
app.use(express.static('public'));
app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    rootValue: root,
    graphiql: true,
  })
);

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

对于通过 getDie 获取的 RandomDie,执行每个方法的结果将返回。

代码语言:graphql复制
{
    getDie(numSides: 6) {
        rollOnce
        roll(numRolls: 3)
        numSides
    }
}

代码语言:json复制
{
  "data": {
    "getDie": {
      "rollOnce": 5,
      "roll": [
        5,
        4,
        5
      ],
      "numSides": 6
    }
  }
}

Mutations and Input Types

在执行诸如数据插入或数据修改等的 API 时,应将其定义为 Mutation 而不是 Query。

更新和获取消息的 API 模式

代码语言:graphql复制
type Mutation {
  setMessage(message: String): String
}

type Query {
  getMessage(message: String): String
}

更新和获取消息的 API 实现示例

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

const schema = buildSchema(`
type Mutation {
    setMessage(message:String!): String!
}

type Query {
  getMessage: String!
}
`);

const inmemoryDB = {
  message: 'default',
};

const root = {
  setMessage: ({ message }: { message: string }) => {
    inmemoryDB.message = message;

    return inmemoryDB.message;
  },
  getMessage: () => {
    return inmemoryDB.message;
  },
};

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

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

获取初始消息

代码语言:tsx复制
{
	getMessage
}

代码语言:json复制
{
	"getMessage": "default"
}

更新消息

代码语言:tsx复制
mutation {
	setMessage(message: "hello")
}

代码语言:json复制
{
  "setMessage": "hello"
}

获取新消息

代码语言:graphql复制
{
	getMessage
}

代码语言:json复制
{
	"getMessage": "hello"
}

此外,当多个 API 使用相同的输入参数等情况时,可以使用 input 关键字将它们汇总为输入类型。

代码语言:graphql复制
input MessageInput {
  content: String
  author: String
  # message: Message -> 对象类型不允许
}

type Message {
  id: ID!
  content: String
  author: String
}

type Query {
  getMessage(id: ID!): Message
}

type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}

输入类型可以作为字段具有基本类型、列表类型和输入类型。

不允许具有对象类型。

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';
import crypto from 'crypto';

const schema = buildSchema(`
  input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

interface MessageInput {
  content: string;
  author: string;
}

class Message {
  private _content: string;
  private _author: string;
  constructor(private _id: string, { content, author }: MessageInput) {
    this._content = content;
    this._author = author;
  }

  get id() {
    return this._id;
  }

  get content() {
    return this._content;
  }
  get author() {
    return this._author;
  }
}

type MessageInfo = {
  content: string;
  author: string;
};

const inmemoryDB: {
  [key: string]: MessageInfo;
} = {};

const root = {
  getMessage: ({ id }: { id: string }) => {
    if (!inmemoryDB[id]) {
      throw new Error('no message exists with id '   id);
    }

    return new Message(id, inmemoryDB[id]);
  },
  createMessage: ({ input }: { input: MessageInfo }) => {
    const id = crypto.randomUUID();

    inmemoryDB[id] = input;
    return new Message(id, input);
  },
  updateMessage: ({ id, input }: { id: string; input: MessageInput }) => {
    if (!inmemoryDB[id]) {
      throw new Error('no message exists with id '   id);
    }

    inmemoryDB[id] = input;
    return new Message(id, input);
  },
};

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

app.listen(4001, () => {
  console.log('Running a GraphQL API server2 at localhost:4001/graphql');
});

Authentication and Express Middleware

结合 express-graphql,可以轻松地使用 Express 中间件。

此外,在解析器中,可以通过第二个参数访问请求(request)。。

代码语言:ts复制
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

var schema = buildSchema(`
  type Query {
    ip: String
  }
`);

const loggingMiddleware = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) => {
  console.log('ip:', req.ip);
  next();
};

const root = {
  ip: (_args: any, context: express.Request) => {
    // 由于没有指定 graphqlHTTP 的 context 选项
    // express.Request 的值将传递给第二个参数
    return context.ip;
  },
};

const app = express();
app.use(loggingMiddleware);
app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    rootValue: root,
    graphiql: true,
    // 传递给解析器的第二个参数。如果不指定,则传递 request 对象
    // context: { hoge: 'context' }, 
  })
);

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

简要尝试了一下教程。

0 人点赞