完整的GraphQL标量指南
#node #graphql #codegen #scalars

本文于2023年6月27日星期二发表,Eddy Nguyen @ The Guild Blog

标量类型是要理解的最重要的GraphQL概念之一。知道标量如何工作并使用正确的工具可以帮助我们构建安全,健壮和可扩展的模式。在本文中,我们介绍了各种基本标量主题:

  • 什么是GraphQl标量?
  • 创建自定义GraphQl标量
  • 类型安全的GraphQl标量工具

什么是GraphQL标量?

GraphQL标量类型是一种原始类型,它表示输入或输出字段返回类型中的值。 GraphQl带有一些本地标量:IDIntFloatStringBoolean。但是,可以在模式中声明自定义标量以表示更复杂的数据类型。

标量值强制

GraphQl标量的一个重要概念是强制。当标量用作输入或输出时,就会发生这种情况。胁迫是采用一种或多种类型的传入价值的过程,并将其变成一种传出类型。

让我们以本机ID标量为例:

  • 用作输入时:客户端可以发送stringnumber值,并且该值在到达服务器上的解析器之前将其胁迫到string
  • 当用作输出时:服务器可以返回stringnumber,并且在将其发送给客户端之前将其胁迫到string

这是本机标量输入和输出打字稿类型的完整列表:

标量 客户可以发送 解析器接收 解析器可以返回 客户收到
id string string string string
number string number string
int number(32位签名的整数) number string number(32位签名的整数)
number(32位签名的整数) number(32位签名的整数)
boolean 1如果为true,0如果false
float number(浮点或数字) number string number
number number
boolean 1如果为true,0如果false
字符串 string string string string
boolean “ true”,如果为true,则false”如果false
number string(数字值转换为字符串)
布尔 boolean boolean boolean boolean
number false如果传入值为0,则true如果输入值不是0

自定义GraphQL标量

随着模式的发展,我们可能需要使用自定义验证规则呈现更复杂的数据类型。我们可以创建自定义GraphQl标量来求解此用例。

许多项目中使用了许多流行的自定义标量示例:

  • DateTime:字符串表示确切的时间点
  • BigInt:表示太大而无法由number表示的值。类似于JavaScript的bigint类型
  • EmailAddress:字符串表示有效的电子邮件格式

有3个步骤来创建自定义GraphQl标量:

1.在模式中声明自定义标量

我们可以使用scalar关键字来声明自定义GraphQl标量:

# schema.graphql
scalar CustomScalar

2.创建自定义标量解析器

现在,我们可以使用graphql软件包的GraphQLScalarType为标量创建一个分辨率:

# Install `graphql` package if it's not installed already
yarn add graphql
// resolvers/CustomScalar.ts
import { GraphQLScalarType } from "graphql";

export const CustomScalar = new GraphQLScalarType({
  name: "CustomScalar",
  description: "Custom Scalar description",
  parseValue(inputValue: unknown) {
    // (1) Used for input
  },
  parseLiteral(ast) {
    // (2) Used for input
  },
  serialize(outputValue: unknown) {
    // (3) Used for output
  },
});

创建自定义标量时,有3个主要功能要牢记:

  1. parseValue:当标量用作输入中的变量时,称为此功能。该功能的验证和返回值传递给解析器。以下是graphQl操作的示例,该操作会触发parseValue
query Example($var: CustomScalar!) {
  example(arg: $var) # a variable is used so `parseValue` is called on the server
}
  1. parseLiteral:当标量用作输入中的字面值时,此功能称为。 ast参数包含graphQl类(例如int或string)和输入的值。该功能的验证和返回值传递给解析器。以下是graphQl操作的示例,它将触发parseLiteral
query Example {
  example(arg: "Hello") # a literal string is used so `parseLiteral` is called on the server
}
  1. serialize:在将值发送给客户端之前,在解析器返回的输出上调用此功能。由于大多数GraphQl服务器在HTTP上以JSON的形式发送结果,因此serialize函数的返回值必须将值转换为stringnumberboolean

查看标量值从客户端到服务器的流动流的可视化,然后返回客户端:

Visualisation of how input and output GraphQL Scalar work

  1. 当使用标量用作输入时,分别使用parseValueparseLiteral解析变量或文字值。
  2. 解析值达到接收解析器的第二个参数(通常称为args)。
  3. 如果将标量用作输出,则该值到达接收解析器的第一个参数(通常称为parent)。
  4. 一旦准备返回客户端,标量值就会发送到标量解析器的serialize函数,以将其变成与JSON兼容的值。
  5. 序列化标量值已发送回客户端。

3.将自定义标量添加到解析器地图

最后,我们可以将自定义标量解析器添加到GraphQL Server中的Resololvers映射中。这是如何使用GraphQL Yoga进行的示例:

# Install `graphql-yoga` to create a GraphQL server
yarn add graphql-yoga
import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "http";
import { CustomScalar } from "./resolvers/CustomScalar.ts"; // 1. Import the custom resolver

const yoga = createYoga({
  schema: createSchema({ 
    typeDefs: /* GraphQL typeDefs */, 
    resolvers: {
      CustomScalar // 2. Put the custom scalar resolver into resolvers map
    }
  }),
});
const server = createServer(yoga);
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
});

现在,运行服务器允许我们在项目中使用自定义标量。 ð

GraphQL标量工具用于类型安全

GraphQL生态系统中有很多工具可以支持与本机和自定义标量一起工作:

1. GraphQl代码生成器

GraphQL Code Generator是一个具有广泛插件生态系统的CLI工具

对于标量,GraphQL代码生成器可以正确生成输入和输出类型。最重要的是,我们可以自定义本机和自定义类型的类型以满足我们的需求。


在GraphQL CodeGen插件的最新主要版本中,生成的Scalar类型已更改为更好地支持标量输入和输出类型。此更改是必要的,因为它允许我们从现在开始更具表情的标量。

请继续阅读以查看如何使用代码生成clientsservers的类型。

客户端配置

typescript是生成基本打字稿类型的插件,包括本机和自定义标量的Scalars类型。然后,此类型由typescript-operations等其他客户端插件使用。

首先,安装GraphQl代码生成器CLI和typescript插件:

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript

这是一个示例配置,它为客户端生成本机标量类型和CustomScalar标量

// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema/types.generated.ts': {
      plugins: ['typescript'],
      config: {
        scalars: {
          // Recommended ID scalar type for clients:
          ID: {
            input: 'string | number',
            output: 'string'
          },
          // Setting custom scalar type:
          CustomScalar: {
            input: 'string', // this means our server can take CustomScalar as string
            output: 'number', // this means our server will return CustomScalar as number
          },
        },
      },
    },
  },
};

export default config;

运行yarn graphql-codegen生成以下标量类型:

// src/schema/types.generated.ts
export type Scalars = {
  ID: { input: string | number; output: string };
  String: { input: string; output: string };
  Boolean: { input: boolean; output: boolean };
  Int: { input: number; output: number };
  Float: { input: number; output: number };
  CustomScalar: { input: string; output: number };
};

默认情况下,typescript插件生成了ID输入和输出的string。这种方法允许插件用于客户端和服务器类型生成,而无需额外的配置。

另一方面,使用上面建议的配置来生成更接近GraphQl的本机行为的类型(即客户可以将stringnumber作为ID输入发送)可以使体验更好。许多系统都使用number作为对象的ID类型。如果推荐的配置用于这些情况,我们不必在将其发送到服务器之前手动将ID转换为string


typescripttypescript-operations插件V4开始,所有DepeDent客户端插件(例如Apollo客户端,React请求,URQL等)必须更新以使用新的输入/输出格式。如果您在这些客户端插件中发现问题,请在community repo中创建一个问题。

服务器的CodeGen配置


typescripttypescript-resolvers插件v4.0.0中,默认ID标量输入类型从string更改为string | number。这样做是为了匹配预期的GraphQl客户端类型。

但是,诸如typescript-resolvers之类的服务器插件也取决于typescript插件生成的类型。在设置配置生成服务器类型时,这会引起很多摩擦。

typescripttypescript-resolvers插件v4.0.1中,我们将默认ID标量输入类型恢复为string。阅读pull request有关更多详细信息。

对于GraphQl服务器,我们可以将typescript-resolvers插件与typescript插件相同的scalars配置。这种组合改变了标量类型的使用方式:

  • 标量输入是在 parseValueparseLiteral函数之后收到的胁迫值的类型。
  • 标量输出是 serialize之前返回的类型。

首先,安装GraphQl代码生成器CLI,typescripttypescript-resolvers插件:

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers

这是推荐的配置服务器类型:

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema/types.generated.ts': {
      plugins: ['typescript', 'typescript-resolvers'],
      config: {
        scalars: {
          // Recommended ID scalar type for servers:
          ID: {
            input: 'string',
            output: 'string | number',
          },
          // Setting custom scalar type:
          CustomScalar: {
            input: 'string', // this means our server can take CustomScalar as string
            output: 'number', // this means our server will return CustomScalar as number
          },
        },
      },
    },
  },
};

export default config;

运行yarn graphql-codegen生成以下标量类型:

export type Scalars = {
  ID: { input: string ; output: string | number };
  String: { input: string; output: string };
  Boolean: { input: boolean; output: boolean };
  Int: { input: number; output: number };
  Float: { input: number; output: number };
  CustomScalar: { input: string; output: number };
};

推荐的服务器配置使处理ID标量类型更加方便。如果您具有具有数字ID的对象,则此方法特别有用。不使用推荐的配置,我们将必须创建自定义映射器或手动将数字转换为字符串以满足Typescript Typecheck。


推荐的设置适用于最常见的用例。它不涵盖一些可能使编写解析器尴尬的边缘案例。

例如,Int的输出在技术上是string | number | boolean,但默认为number,因为大多数解析器永远不会返回stringboolean

2. GraphQL标量(graphql-scalars

GraphQL Scalars是一个包含许多常用自定义GraphQl标量的库,例如DateTimeBigInt,等等。

即使创建自定义标量并不难,测试和发布标量在GraphQL服务器之间共享标量也可以迅速分散构建核心业务逻辑的注意力。因此,通常最好使用诸如graphql-scalars的良好维护和记录的库。

这是我们可以从graphql-scalars使用DateTime标量的方法:

  1. 安装graphql-scalars
yarn add graphql-scalars
  1. 在模式中声明DateTime
# schema.graphql
scalar DateTime
  1. DateTime解析器导入到解析器图中:
import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "http";
import { DateTimeResolver } from "graphql-scalars"; // 1. Import scalar resolver

const yoga = createYoga({
  schema: createSchema({ 
    typeDefs: /* GraphQL typeDefs */, 
    resolvers: {
      DateTime: DateTimeResolver // 2. Put resolver to resolvers map
    }
  }),
});
const server = createServer(yoga);
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
});

  1. 使用GraphQL代码生成器创建类型:

graphql-scalars的大多数标量解析器都带有可以在CodeGen Config中使用的推荐服务器类型。我们可以导入一个解析器并很容易使用上述类型:

// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
import { DateTimeResolver } from "graphql-scalars"; // 1. Import scalar resolver

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema/types.generated.ts': {
      plugins: ['typescript', 'typescript-resolvers'],
      config: {
        scalars: {
          ID: {
            input: 'string',
            output: 'string | number',
          },
          DateTime: DateTimeResolver.extensions.codegenScalarType // 2. Use scalar resolver's recommended server type
        },
      },
    },
  },
};

export default config;


对于graphql-scalars的自定义标量的推荐客户类型呢?

目前,没有官方软件包(尚未!)。但是,有一个issue on the GraphQL Scalars repo

这是为graphql-scalars选择正确的客户端类型的一般提示:

  1. 检查serialize函数的返回类型,然后在CodeGen Config.
  2. 中设置标量output类型
  3. 如果您的客户端将返回的标量值转换为另一种类型,则可以在CodeGen配置中设置最终的output类型。

3. GraphQL CodeGen Server预设

GraphQl代码生成器和graphql-scalars库应该足以管理我们的标量实现和类型要求。但是,服务器预设(@eddeee888/gcg-typescript-resolver-files)可以进一步简化标量设置:

  • 默认情况下使用推荐的ID标量服务器配置
  • 检测是否在已安装的graphql-scalars软件包中存在自定义标量,然后自动将标量解析器导入解析器映射,并在CodeGen Config
  • 中设置推荐类型
  • 检测是否在已安装的graphql-scalars软件包中不存在自定义标量(或者如果未安装软件包),然后自动创建自定义标量解析器并将其放入解析器地图
  1. 安装GraphQl代码生成器CLI,服务器预设和((可选))GraphQl标量:
yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files

# (Optional) Install `graphql-scalars` if you intend to use custom scalars from this library
yarn add graphql-scalars
  1. 在CodeGen配置中使用服务器预设:
// codegen.ts
import type { CodegenConfig } from "@graphql-codegen/cli";
import { defineConfig } from "@eddeee888/gcg-typescript-resolver-files";

const config: CodegenConfig = {
  schema: "**/schema.graphql",
  generates: {
    "src/schema": defineConfig(),
  },
};

export default config;

运行yarn graphql-codegen,就是这样!现在,每当将DateTime之类的自定义标量添加到架构中时,实现和类型都会自动来自graphql-scalars! ð


服务器预设不仅可以处理自定义标量。有现有的指南和博客文章,其中包含有关其功能和概念的更多详细信息:

概括

在本文中,我们探讨了GraphQL标量类型的重要性,其工作原理以及如何创建自定义标量解析器以扩展我们的模式。我们还探索了工具,以帮助使用本机和自定义标量(例如GraphQl Code Generator,GraphQL标量和服务器预设)。