前言
参考文章: https://chenyitian.gitbooks.io/graphql/content/introduction.html
参考网站: https://www.howtographql.com/basics/0-introduction/
GraphQL基础
1. 什么是GraphQL
GraphQL是一种用于API的查询语言,同时也是一种运行在服务端的执行引擎。它使用基于类型系统的模式定义和验证查询结构(类型系统由你的数据定义)。GraphQL不依赖任何特定数据库或存储引擎,而是通过现有代码和数据来支持查询
为什么使用GraphQL
- 灵活的数据获取: 客户端可以请求确切的数据结构,避免数据过多或不足的问题
 - 单一端点: 所有查询通过一个端点完成,简化了客户端与服务端的交互
 - 强类型系统: 定义
API时使用强类型,可以捕获开发错误,增强工具支持(如自动生成文档和代码) - 实时更新: 通过订阅(
subscriptions),支持实时数据更新,客户端在数据变化时能自动接收更新 
与REST的对比
与REST的对比 (优点)
- 避免多次请求: 
REST中可能需要多次请求不同资源才能获取所需数据,而GraphQL能通过单一查询获取所有相关数据 - 精确的数据获取: 客户端明确指定需要的字段,避免多余数据传输,适用于带宽有限或网络不稳定的环境
 
与REST的对比 (缺点)
- 查询解析复杂: 由于
GraphQL查询的灵活性,解析和执行查询可能比REST更复杂,尤其在大规模数据集或复杂业务逻辑中 - 缓存困难: 与
REST固定URL不同,GraphQL查询不是静态的,因此标准HTTP缓存机制难以直接应用,需要额外设计缓存策略 - 学习曲线: 😅
 
2. 字段Fields
在GraphQL中,字段 (fields) 是定义在类型 (type) 上的属性,表示类型包含的数据内容。字段有以下特点
- 每个字段都有名称和返回类型,类型可以是标量(如
String、Int)、对象、枚举、列表等 - 字段可以接受参数,用于传递附加信息或过滤数据
 
字段的基本定义
type User {   id: ID!   name: String!   age: Int }
  type Query {   user(id: ID!): User }
  | 
User类型
- 包含
id、name和age三个字段 - 每个字段定义了
User类型的数据结构 
Query类型
- 定义了一个查询字段
user,可以根据id获取一个User 
什么是Schema
Schema是GraphQL的核心概念,用于描述API的结构,包括- 类型定义
Type: 定义API中的类型,例如标量类型 (String、Int)、对象类型 (User)、枚举类型等 - 根类型
Root Types: 包括Query、Mutation和Subscription,分别定义了查询、变更和订阅操作 
解析器Resolver
- 字段通常需要解析器来告诉
GraphQL如何获取字段的值。当客户端请求某个字段时,GraphQL会调用对应的解析器来获取数据 
const resolvers = {   Query: {     user: (parent, args, context, info) => {       const userId = args.id;        return getUserById(userId);      },   }, };
  | 
完整查询示例
query {   user(id: "1") {     id     name     age   } }
  | 
- 定义
Schema: 描述数据类型和操作 - 编写
Resolver: 实现具体的数据获取逻辑 - 客户端查询: 通过
Query请求数据,GraphQL调用解析器返回结果 
Mutation示例
- 下面是一个使用变更操作 (
Mutation) 创建用户的示例 
type User {   id: ID!   name: String!   age: Int }
  type Mutation {   createUser(name: String!, age: Int): User }
  | 
const resolvers = {   Mutation: {     createUser: (parent, args, context, info) => {       const newUser = {         id: generateUniqueId(),         name: args.name,         age: args.age,       };       saveUserToDatabase(newUser);        return newUser;     },   }, };
  | 
mutation {   createUser(name: "Alice", age: 30) {     id     name     age   } }
  | 
3. 参数Arguments
在GraphQL中,参数 (Arguments) 用于向字段或操作(如查询Query和变更Mutation)传递输入数据。参数允许客户端在请求中提供额外信息,用于筛选、过滤或指定要操作的数据
参数的定义
- 参数可以定义在
GraphQL schema中的字段上。参数可以是- 必需参数: 使用
!符号表示,必须提供 - 可选参数: 没有
!符号的参数,可以省略 
 
type User {   id: ID!   name: String!   age: Int }
  type Query {   user(id: ID!): User   users(age: Int): [User] }
  | 
user字段有一个必需的id参数,用于指定要查询的用户users字段有一个可选的age参数,用于筛选特定年龄的用户- 方括号
[ ]表示返回值是一个列表类型 
解析器中的参数
- 在解析器 (
Resolver) 中,参数通过args对象传递,可以使用这些参数来筛选或操作数据 
const resolvers = {   Query: {     user: (parent, args, context, info) => {              return getUserById(args.id);     },     users: (parent, args, context, info) => {              if (args.age) {         return getUsersByAge(args.age);       }       return getAllUsers();     },   }, };
  | 
args.id和args.age是客户端请求中传递的参数- 在
user查询中,通过id获取单个用户 - 在
users查询中,根据age筛选用户列表,如果未提供age参数,则返回所有用户 
客户端查询
query {   user(id: "1") {     id     name     age   }      users(age: 30) {     id     name     age   } }
  | 
- 第一部分请求
id为"1"的用户 - 第二部分请求所有
age为30的用户 
变更中的参数
参数在变更操作(Mutation)中用于提供操作所需的输入数据,例如创建一个新用户
type Mutation {   createUser(name: String!, age: Int): User }
  | 
const resolvers = {   Mutation: {     createUser: (parent, args, context, info) => {       const newUser = {         id: generateUniqueId(),         name: args.name,         age: args.age,       };       saveUserToDatabase(newUser);        return newUser;     },   }, };
  | 
mutation {   createUser(name: "Alice", age: 30) {     id     name     age   } }
  | 
- 客户端传递参数
name和age,服务器根据参数创建用户并返回新增用户的数据 
GraphQL核心功能
4. 别名Aliases
在GraphQL中,别名 (Aliases) 是一种用于重命名查询结果中字段名称的功能。它允许客户端
- 避免命名冲突: 当查询结果包含多个同名字段时,使用别名区分不同的字段调用
 - 自定义响应格式: 通过别名修改返回数据中的字段名称,使其更符合客户端的需求
 
避免命名冲突
- 当需要多次查询同一个字段的不同实例时,别名可以为每个字段调用提供不同的名称。例如
 
query {   user1: user(id: "1") {     id     name     age   }   user2: user(id: "2") {     id     name     age   } }
  | 
user1和user2是别名,用于区分user字段的两次调用, 这避免了因字段名称重复而导致的命名冲突
定义响应格式
- 别名可以改变返回数据中的字段名称,使其更符合业务需求或提高可读性
 
{   "data": {     "user1": {       "id": "1",       "name": "Alice",       "age": 30     },     "user2": {       "id": "2",       "name": "Bob",       "age": 25     }   } }
  | 
user1和user2成为响应数据中的字段名称,分别包含两个不同用户的信息
5. 片段Fragments
片段 (Fragments) 是GraphQL中的一种机制,用于在多个查询或操作中重用一组字段选择。通过定义片段,可以减少字段的重复定义,提高查询的可读性和可维护性
片段的语法
fragment FragmentName on TypeName {   field1   field2   ... }
  | 
FragmentName: 片段的名称,用于在查询中引用片段TypeName: 片段适用的类型,指定字段所属的对象类型field1, field2, ...: 片段中包含的字段集合
片段的使用示例
假设我们有一个User类型,包含id、name和age字段,以下示例展示了如何定义和使用片段
fragment UserFields on User {   id   name   age }
  | 
query {   user1: user(id: "1") {     ...UserFields   }   user2: user(id: "2") {     ...UserFields   } }
  | 
{   "data": {     "user1": {       "id": "1",       "name": "Alice",       "age": 30     },     "user2": {       "id": "2",       "name": "Bob",       "age": 25     }   } }
  | 
嵌套片段
片段可以嵌套使用,在一个片段中引用另一个片段,从而灵活地组织字段
fragment AddressFields on Address {   street   city   country }
  fragment UserFields on User {   id   name   age   address {     ...AddressFields   } }
  | 
query {   user(id: "1") {     ...UserFields   } }
  | 
{   "data": {     "user": {       "id": "1",       "name": "Alice",       "age": 30,       "address": {         "street": "123 Main St",         "city": "New York",         "country": "USA"       }     }   } }
  | 
内联片段
内联片段 (Inline Fragments) 是GraphQL中的一种机制,用于在查询中根据对象的实际类型动态选择字段。它们主要用于处理接口 (Interface) 或联合类型 (Union Type),当返回的数据类型不确定时,可以根据具体类型选择不同的字段
... on TypeName {   field1   field2 }
  | 
Schema示例: 假设有一个Person接口,两个实现类型Employee和Customer
interface Person {   id: ID!   name: String! }
  type Employee implements Person {   id: ID!   name: String!   salary: Int }
  type Customer implements Person {   id: ID!   name: String!   purchaseHistory: [String] }
  type Query {   persons: [Person] }
  | 
- 查询示例: 根据
Person的具体类型选择不同的字段 
query {   persons {     id     name     ... on Employee {       salary     }     ... on Customer {       purchaseHistory     }   } }
  | 
{   "data": {     "persons": [       {         "id": "1",         "name": "Alice",         "salary": 50000       },       {         "id": "2",         "name": "Bob",         "purchaseHistory": ["item1", "item2"]       }     ]   } }
  | 
6. 操作名称
在GraphQL中,操作名称 (Operation Name) 是一个可选的标识,用于明确标注特定的查询 (Query)、变更 (Mutation) 或订阅 (Subscription) 操作的名称。它有助于提高可读性、调试效率,以及在某些工具中实现特定功能
操作名称特点
- 操作名称的位置
- 紧跟在操作类型(如
query、mutation或subscription)之后 
 - 命名规则
- 名称只能包含字母、数字和下划线
(_) - 名称不能以数字开头
 
 - 用途
- 提供查询的上下文,明确查询意图
 - 在调试、日志记录和工具(如
Apollo Client)中,用于识别特定的操作 - 当一个请求中包含多个操作时,必须使用操作名称
 
 
操作名称示例
- 查询示例: 
GetUser是操作名称,用于标识此查询操作 
query GetUser {   user(id: "1") {     id     name     age   } }
  | 
- 变更示例: 
CreateUser是操作名称,表明此变更的目的为创建用户 
mutation CreateUser {   createUser(name: "Alice", age: 30) {     id     name     age   } }
  | 
- 多操作示例: 当一个请求中包含多个操作时,必须使用操作名称进行区分
 
query GetUser {   user(id: "1") {     id     name     age   } }
  mutation UpdateUser {   updateUser(id: "1", name: "Alice") {     id     name     age   } }
  | 
- 匿名操作: 如果操作名称被省略,该操作称为匿名操作, 但在一个请求中不能包含多个匿名操作,因为没有操作名称用于区分
 
query {   user(id: "1") {     id     name     age   } }
  | 
7. 操作类型
在GraphQL中,操作类型 (Operation Type) 是指客户端可以在API请求中执行的三种操作: 查询 (Query)、变更 (Mutation) 和 订阅 (Subscription)。每种操作类型对应不同的用途,分别用于读取数据、修改数据和监听实时数据更新
操作类型介绍
- 查询 
Query- 用途: 用于读取和获取数据
 - 特点: 不会对服务器数据产生任何修改,仅返回请求的数据 (类似于
REST API的GET请求) 
 
query {   user(id: "1") {     id     name     age   } }
  <!-- 查询 user 数据,根据参数 id: "1" 获取特定用户的信息 --> <!-- 返回的字段包括 id、name 和 age -->
  | 
- 变更 
Mutation- 用途: 用于修改服务器上的数据,包括创建、更新或删除操作
 - 特点: 与查询不同,变更操作会改变服务器的数据状态 (类似于
REST API的POST、PUT、PATCH和 DELETE请求) 
 
mutation {   createUser(name: "Alice", age: 30) {     id     name   } }
  <!-- 调用 createUser 变更操作,创建一个新用户 --> <!-- 参数 name 和 age 提供新用户的基本信息 --> <!-- 返回结果包括新用户的 id 和 name -->
  | 
- 订阅 
Subscription- 用途: 用于监听服务器上的事件,并在事件发生时实时接收更新
 - 特点: 用于实时数据更新场景,如实时聊天、股票行情等。订阅操作会保持一个持久的连接(通常使用
WebSocket),当事件发生时,服务器会主动推送更新给客户端 
 
subscription {   userAdded {     id     name     age   } }
  <!-- 订阅 userAdded 事件,当有新用户被添加时,服务器会推送更新数据 --> <!-- 返回结果包含新用户的 id、name 和 age -->
  | 
三种操作类型的比较
| 操作类型 | 用途 | 是否修改数据 | 使用场景 | 
|---|
| 查询 | 读取和获取数据 | 否 | 获取用户、产品列表、详情页数据 | 
| 变更 | 修改服务器数据 | 是 | 创建、更新、删除资源 | 
| 订阅 | 监听事件并实时接收数据更新 | 否 | 实时聊天、通知、数据流 | 
8. 查询Query
查询 (Query) 是GraphQL中的一种操作类型,用于从服务器获取数据。它与传统REST API的GET请求类似,但在灵活性和强大性方面超越了后者
查询的定义
- 在
GraphQL中,查询类型通常在schema中定义为Query类型。Query类型描述了所有可执行的查询操作 
type Query {   user(id: ID!): User   users: [User] }
  type User {   id: ID!   name: String!   age: Int }
  | 
查询解析器Resolver
- 解析器负责实现字段的具体逻辑,定义如何获取数据。以下是查询解析器的示例
 
const users = [   { id: '1', name: 'Alice', age: 30 },   { id: '2', name: 'Bob', age: 25 }, ];
  const resolvers = {   Query: {     user: (parent, args, context, info) => {       return users.find(user => user.id === args.id);     },     users: () => users,   }, };
   | 
user: 根据传入的id参数,从users列表中查找并返回对应用户users: 直接返回所有用户
查询的特点
- 精确的数据获取: 客户端可以指定所需字段,避免传输多余数据
 
query {   user(id: "1") {     name     age   } }
  | 
{   "data": {     "user": {       "name": "Alice",       "age": 30     }   } }
  | 
query {   user(id: "1") {     id     name   }   users {     id     name   } }
  | 
{   "data": {     "user": {       "id": "1",       "name": "Alice"     },     "users": [       {         "id": "1",         "name": "Alice"       },       {         "id": "2",         "name": "Bob"       }     ]   } }
  | 
9. 变更Mutations
变更 (Mutations) 是GraphQL中的一种操作类型,专门用于对服务器端的数据进行修改操作。与查询 (Query) 仅用于读取数据不同,变更支持创建、更新或删除数据。每个变更操作都可以定义所需的输入参数和返回结果的结构
变更的定义
- 在
GraphQL的schema中,变更类型通常定义在Mutation类型中,用来描述所有支持的数据修改操作 
type Mutation {   createUser(name: String!, age: Int!): User   updateUser(id: ID!, name: String, age: Int): User   deleteUser(id: ID!): Boolean }
  type User {   id: ID!   name: String!   age: Int }
  | 
createUser: 接收name和age参数,返回新创建的User对象updateUser: 接收id(必需)、name和age参数,用于更新用户信息,返回更新后的UserdeleteUser: 接收id参数,用于删除指定用户,返回布尔值表示操作是否成功
变更的解析器
- 解析器定义了如何执行变更操作,通常与数据库或其他数据存储交互
 
const { v4: uuidv4 } = require('uuid');
  const users = [   { id: '1', name: 'Alice', age: 30 },   { id: '2', name: 'Bob', age: 25 }, ];
  const resolvers = {   Mutation: {     createUser: (parent, args, context, info) => {       const newUser = {         id: uuidv4(),          name: args.name,         age: args.age,       };       users.push(newUser);        return newUser;     },     updateUser: (parent, args, context, info) => {       const user = users.find(user => user.id === args.id);       if (!user) {         throw new Error("User not found");       }       if (args.name !== undefined) {         user.name = args.name;       }       if (args.age !== undefined) {         user.age = args.age;       }       return user;     },     deleteUser: (parent, args, context, info) => {       const userIndex = users.findIndex(user => user.id === args.id);       if (userIndex === -1) {         throw new Error("User not found");       }       users.splice(userIndex, 1);        return true;     },   }, };
  | 
变更的使用
客户端可以通过以下查询发送变更请求
mutation {   createUser(name: "Alice", age: 30) {     id     name     age   } }
  | 
{   "data": {     "createUser": {       "id": "c81e728d-59f2-4d16-9f02-fcad84756789",       "name": "Alice",       "age": 30     }   } }
  | 
mutation {   updateUser(id: "1", name: "Alice Updated") {     id     name     age   } }
  | 
{   "data": {     "updateUser": {       "id": "1",       "name": "Alice Updated",       "age": 30     }   } }
  | 
mutation {   deleteUser(id: "2") }
  | 
{   "data": {     "deleteUser": true   } }
  | 
10. 订阅Subscription
订阅 (Subscription) 是GraphQL中的一种操作类型,允许客户端订阅服务器上的事件,并在事件发生时实时接收更新数据。这种机制特别适合实时更新场景,如聊天消息、通知、股票价格等
订阅的工作原理
- 建立订阅连接: 客户端发送订阅请求到服务器,指定要订阅的事件或数据
 - 保持连接: 服务器与客户端之间通过
WebSocket或其他双向通信协议维持持久连接 - 事件触发: 当服务器端的事件发生变化(如新增或更新数据)时,触发对应的订阅逻辑
 - 数据推送: 服务器将更新的数据通过连接推送到订阅的客户端
 
订阅示例
type User {   id: ID!   name: String!   age: Int }
  type Subscription {   userAdded: User }
  type Mutation {   createUser(name: String!, age: Int!): User }
  | 
const { PubSub } = require('graphql-subscriptions'); const pubsub = new PubSub();
  const resolvers = {   Subscription: {     userAdded: {              subscribe: () => pubsub.asyncIterator(['USER_ADDED']),     },   },   Mutation: {     createUser: (parent, args, context, info) => {       const newUser = {         id: generateUniqueId(),         name: args.name,         age: args.age,       };
               saveUserToDatabase(newUser);
               pubsub.publish('USER_ADDED', { userAdded: newUser });
        return newUser;     },   }, };
  | 
subscription {   userAdded {     id     name     age   } }
  | 
- 当有新用户通过
createUser变更操作添加时,服务器会触发userAdded事件,客户端将接收到用户信息 
Apollo Client订阅
- 配置
Apollo Client: 创建一个支持查询、变更和订阅的客户端 
import { ApolloClient, InMemoryCache, split } from '@apollo/client'; import { WebSocketLink } from '@apollo/client/link/ws'; import { HttpLink } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities';
 
  const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
 
  const wsLink = new WebSocketLink({   uri: 'ws://localhost:4000/graphql',   options: {     reconnect: true,   }, });
 
  const splitLink = split(   ({ query }) => {     const definition = getMainDefinition(query);     return (       definition.kind === 'OperationDefinition' &&       definition.operation === 'subscription'     );   },   wsLink,   httpLink, );
 
  const client = new ApolloClient({   link: splitLink,   cache: new InMemoryCache(), });
  | 
- 订阅操作: 使用
Apollo Client的useSubscription Hook实现订阅 
import { gql, useSubscription } from '@apollo/client';
  const USER_ADDED_SUBSCRIPTION = gql`   subscription OnUserAdded {     userAdded {       id       name       age     }   } `;
  function UserList() {   const { data, loading, error } = useSubscription(USER_ADDED_SUBSCRIPTION);
    if (loading) return <p>Loading...</p>;   if (error) return <p>Error: {error.message}</p>;
    return (     <div>       <h2>New User Added:</h2>       <p>ID: {data.userAdded.id}</p>       <p>Name: {data.userAdded.name}</p>       <p>Age: {data.userAdded.age}</p>     </div>   ); }
  export default UserList;
  | 
- 总而言之,
GraphQL的Subscription依赖于WebSocket来实现。WebSocket提供了双向持久连接的能力,使得服务器能够在事件发生时立即向客户端推送数据,而Subscription则定义了这些事件和数据的结构。因此,Subscription不是替代WebSocket,而是利用WebSocket来实现特定的功能和需求 
GraphQL进阶
11. 变量 Variables
在GraphQL中,变量 (Variables) 是一种机制,用于动态传递参数到查询 (Query)、变更 (Mutation) 或订阅 (Subscription) 中。通过使用变量,可以使查询更加灵活、可重用,并且避免将用户输入直接嵌入到查询字符串中,从而提升安全性和可读性
变量的定义与使用
- 查询中的变量: 变量需要在查询语句中定义,并在执行查询时传入实际的值
 
query GetUser($id: ID!) {   user(id: $id) {     id     name     age   } }
  <!-- $id 是一个变量,表示用户的 ID --> <!-- 变量的类型为 ID!,! 表示这是一个必需字段 -->
  | 
- 变量在
React中的使用: 使用Apollo Client的useQuery Hook动态传递变量值 
import { gql, useQuery } from '@apollo/client';
 
  const GET_USER = gql`   query GetUser($id: ID!) {     user(id: $id) {       id       name       age     }   } `;
  function User({ userId }) {   const { loading, error, data } = useQuery(GET_USER, {     variables: { id: userId },    });
    if (loading) return <p>Loading...</p>;   if (error) return <p>Error: {error.message}</p>;
    return (     <div>       <h2>User Details</h2>       <p>ID: {data.user.id}</p>       <p>Name: {data.user.name}</p>       <p>Age: {data.user.age}</p>     </div>   ); }
  export default User;
 
 
 
  | 
- 变更中的变量: 在变更操作中,变量同样可以动态传递参数
 
mutation CreateUser($name: String!, $age: Int!) {   createUser(name: $name, age: $age) {     id     name     age   } }
  | 
import { gql, useMutation } from '@apollo/client';
  const CREATE_USER = gql`   mutation CreateUser($name: String!, $age: Int!) {     createUser(name: $name, age: $age) {       id       name       age     }   } `;
  function CreateUserForm() {   const [createUser, { data, loading, error }] = useMutation(CREATE_USER);
    const handleSubmit = async (e) => {     e.preventDefault();     const name = e.target.name.value;     const age = parseInt(e.target.age.value, 10);
      await createUser({ variables: { name, age } });   };
    if (loading) return <p>Submitting...</p>;   if (error) return <p>Error: {error.message}</p>;
    return (     <form onSubmit={handleSubmit}>       <input name="name" placeholder="Name" required />       <input name="age" placeholder="Age" type="number" required />       <button type="submit">Create User</button>       {data && <p>Created User: {data.createUser.name}</p>}     </form>   ); }
  export default CreateUserForm;
  | 
12. 指令 Directives
指令 (Directives) 是GraphQL中的一种强大功能,用于动态影响查询、变更 (Mutation) 或订阅 (Subscription) 的执行行为。指令可以应用于字段、片段等,帮助客户端或服务器灵活调整查询结果的生成逻辑。例如,可以根据条件决定是否包含某字段或动态修改返回结果
内置指令
GraphQL规范中定义了两个常用的内置指令: @include和@skip
@include: 条件包含- 用于根据条件动态包含字段或片段
 - 接受一个名为
if的参数,该参数的值为布尔类型 
query GetUser($withAge: Boolean!) {   user(id: "1") {     id     name     age @include(if: $withAge)   } }
  <!-- 如果变量 $withAge 为 true,则返回结果中包含 age 字段 --> <!-- 如果 $withAge 为 false,则排除 age 字段 -->
  | 
@skip: 条件跳过- 用于根据条件跳过字段或片段
 - 同样接受一个名为
if的布尔参数 
query GetUser($withoutAge: Boolean!) {   user(id: "1") {     id     name     age @skip(if: $withoutAge)   } }
  <!-- 如果变量 $withoutAge 为 true,则跳过 age 字段 --> <!-- 如果 $withoutAge 为 false,则包含 age 字段 -->
  | 
自定义指令
除了内置指令,GraphQL还支持自定义指令。这些指令可以用于更复杂的场景,比如数据验证、字段格式化等。自定义指令需要在schema中声明,并提供对应的实现逻辑
directive @upper on FIELD_DEFINITION
  type Query {   greeting: String @upper }
  <!-- @upper 是一个自定义指令,应用在字段定义上 --> <!-- on FIELD_DEFINITION 指定了该指令适用于字段定义 -->
   | 
- 自定义指令的实现: 使用
graphql-tools创建一个@upper指令,将字符串字段的返回值转换为大写 
const { SchemaDirectiveVisitor } = require('graphql-tools'); const { defaultFieldResolver } = require('graphql');
  class UpperCaseDirective extends SchemaDirectiveVisitor {   visitFieldDefinition(field) {     const { resolve = defaultFieldResolver } = field;
      field.resolve = async function (...args) {       const result = await resolve.apply(this, args);       if (typeof result === 'string') {         return result.toUpperCase();       }       return result;     };   } }
  module.exports = UpperCaseDirective;
  | 
- 整合指令到
Schema: 自定义指令@upper应用于greeting字段,返回的值将自动转换为大写 
const { gql } = require('apollo-server'); const UpperCaseDirective = require('./UpperCaseDirective');
  const typeDefs = gql`   directive @upper on FIELD_DEFINITION
    type Query {     greeting: String @upper   } `;
  const resolvers = {   Query: {     greeting: () => "hello world",   }, };
  const { makeExecutableSchema } = require('graphql-tools');
  const schema = makeExecutableSchema({   typeDefs,   resolvers,   schemaDirectives: {     upper: UpperCaseDirective,   }, });
  module.exports = schema;
  | 
{   "data": {     "greeting": "HELLO WORLD"   } }
  | 
附录
参考文章: https://chenyitian.gitbooks.io/graphql/content/introduction.html
参考网站: https://www.howtographql.com/basics/0-introduction/