Skip to content

结合 TypeScript 与 GraphQL 的强大力量

GraphQL 是一种强大的 API 查询语言,也是一个用现有数据执行这些查询的运行时。它是一种优雅的方式,能够解决 REST API 中常见的许多问题。关于背景知识,建议阅读这篇 GraphQL 与 REST 的对比文章。GraphQL 结合 TypeScript 有助于你在 GraphQL 查询中获得更好的类型安全性,实现端到端的类型化。

在本章中,我们假设你已具备 GraphQL 的基本知识,重点介绍如何使用内置的 @nestjs/graphql 模块。GraphQLModule 可以配置使用 Apollo 服务器(使用 @nestjs/apollo 驱动)和 Mercurius(使用 @nestjs/mercurius)。我们为这些成熟的 GraphQL 包提供了官方集成,以便在 Nest 中简单地使用 GraphQL(更多集成请参见这里)。

你也可以构建自己的专用驱动(详情请参阅这里)。

安装

首先安装所需的包:

bash
# 用于 Express 和 Apollo(默认)
$ npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/express5 graphql

# 用于 Fastify 和 Apollo
# npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/fastify graphql

# 用于 Fastify 和 Mercurius
# npm i @nestjs/graphql @nestjs/mercurius graphql mercurius

警告

@nestjs/graphql@>=9@nestjs/apollo^10 包兼容 Apollo v3(更多详情请查看 Apollo Server 3 迁移指南),而 @nestjs/graphql@^8 仅支持 Apollo v2(例如 apollo-server-express@2.x.x 包)。

概览

Nest 提供了两种构建 GraphQL 应用的方式:代码优先(code first)模式优先(schema first) 方法。你应该选择最适合自己的方式。本 GraphQL 章节中的大多数内容分为两个主要部分:一部分适用于采用代码优先方式的开发者,另一部分适用于采用模式优先方式的开发者。

代码优先方式中,你使用装饰器和 TypeScript 类来生成对应的 GraphQL schema。如果你偏好只使用 TypeScript,不想在不同语言语法之间切换,这种方式非常有用。

模式优先方式中,真实数据源是 GraphQL SDL(Schema Definition Language)文件。SDL 是一种与语言无关的方式,可以在不同平台之间共享 schema 文件。Nest 会根据 GraphQL schema 自动生成 TypeScript 定义(使用类或接口),从而减少编写冗余样板代码的需要。

GraphQL 与 TypeScript 入门

提示

在接下来的章节中,我们将集成 @nestjs/apollo 包。如果你想使用 mercurius 包,请导航到这个章节

安装包之后,我们可以导入 GraphQLModule 并使用 forRoot() 静态方法进行配置。

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
    }),
  ],
})
export class AppModule {}

提示

对于 mercurius 集成,你应该使用 MercuriusDriverMercuriusDriverConfig。两者都从 @nestjs/mercurius 包中导出。

forRoot() 方法接受一个选项对象作为参数。这些选项会传递给底层的驱动实例(在此了解更多可用设置:ApolloMercurius)。例如,如果你想禁用 playground 并关闭 debug 模式(针对 Apollo),可以传递以下选项:

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      playground: false,
    }),
  ],
})
export class AppModule {}

在这种情况下,这些选项将被转发给 ApolloServer 构造函数。

GraphQL Playground

Playground 是一个图形化的、交互式的、基于浏览器的 GraphQL IDE,默认情况下与 GraphQL 服务器本身使用相同的 URL。要访问 playground,你需要配置并运行一个基本的 GraphQL 服务器。要立即查看效果,你可以安装并构建此处的示例。或者,如果你一直跟着这些代码示例学习,完成 Resolvers 章节中的步骤后,你就可以访问 playground 了。

准备就绪后,在应用在后台运行的情况下,打开浏览器并导航到 http://localhost:3000/graphql(主机和端口可能因你的配置而异)。你将看到如下所示的 GraphQL playground。

提示

@nestjs/mercurius 集成不附带内置的 GraphQL Playground 集成。你可以使用 GraphiQL(设置 graphiql: true)。

警告

更新(04/14/2025):默认的 Apollo playground 已被弃用,将在下一个主要版本中移除。你可以使用 GraphiQL,只需在 GraphQLModule 配置中设置 graphiql: true,如下所示:

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  graphiql: true,
}),

如果你的应用使用了 subscriptions,请确保使用 graphql-ws,因为 GraphiQL 不支持 subscriptions-transport-ws

代码优先

代码优先方式中,你使用装饰器和 TypeScript 类来生成对应的 GraphQL schema。

要使用代码优先方式,首先在选项对象中添加 autoSchemaFile 属性:

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),

autoSchemaFile 属性值是自动生成的 schema 文件的创建路径。你也可以选择在内存中即时生成 schema。要启用此功能,将 autoSchemaFile 属性设置为 true

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: true,
}),

默认情况下,生成的 schema 中的类型按照它们在包含的模块中定义的顺序排列。要按字典序排序 schema,请将 sortSchema 属性设置为 true

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
  sortSchema: true,
}),

示例

一个完整的代码优先示例可在这里获取。

模式优先

要使用模式优先方式,首先在选项对象中添加 typePaths 属性。typePaths 属性指示 GraphQLModule 应在哪里查找你编写的 GraphQL SDL schema 定义文件。这些文件会在内存中合并;这允许你将 schema 拆分到多个文件中,并将它们放在对应的 resolver 附近。

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  typePaths: ['./**/*.graphql'],
}),

通常你还需要与 GraphQL SDL 类型对应的 TypeScript 定义(类和接口)。手动创建对应的 TypeScript 定义既冗余又繁琐,这让我们失去了单一真实数据源——在 SDL 中做的每个更改都要求我们同步调整 TypeScript 定义。为了解决这个问题,@nestjs/graphql 包可以从抽象语法树(AST自动生成 TypeScript 定义。要启用此功能,在配置 GraphQLModule 时添加 definitions 选项属性。

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  typePaths: ['./**/*.graphql'],
  definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
  },
}),

definitions 对象的 path 属性指示生成的 TypeScript 输出文件的保存位置。默认情况下,所有生成的 TypeScript 类型都创建为接口。要改为生成类,请将 outputAs 属性指定为 'class'

typescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  typePaths: ['./**/*.graphql'],
  definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
    outputAs: 'class',
  },
}),

上述方式会在应用每次启动时动态生成 TypeScript 定义。或者,构建一个简单的脚本来按需生成可能更为合适。例如,假设我们创建以下脚本 generate-typings.ts

typescript
import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'node:path';

const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'],
  path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class',
});

现在你可以按需运行此脚本:

bash
$ ts-node generate-typings

提示

你可以预先编译脚本(例如使用 tsc),然后使用 node 来执行它。

要启用脚本的监视模式(每当任何 .graphql 文件更改时自动生成类型定义),请将 watch 选项传递给 generate() 方法。

typescript
definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'],
  path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class',
  watch: true,
});

要为每个对象类型自动生成额外的 __typename 字段,请启用 emitTypenameField 选项:

typescript
definitionsFactory.generate({
  // ...
  emitTypenameField: true,
});

要将 resolver(queries、mutations、subscriptions)生成为不带参数的普通字段,请启用 skipResolverArgs 选项:

typescript
definitionsFactory.generate({
  // ...
  skipResolverArgs: true,
});

要将枚举生成为 TypeScript 联合类型而不是常规的 TypeScript 枚举,请将 enumsAsTypes 选项设置为 true

typescript
definitionsFactory.generate({
  // ...
  enumsAsTypes: true,
});

Apollo Sandbox

要使用 Apollo Sandbox 代替 graphql-playground 作为本地开发的 GraphQL IDE,请使用以下配置:

typescript
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      playground: false,
      plugins: [ApolloServerPluginLandingPageLocalDefault()],
    }),
  ],
})
export class AppModule {}

示例

一个完整的模式优先示例可在这里获取。

访问生成的 Schema

在某些情况下(例如端到端测试),你可能希望获取生成的 schema 对象的引用。在端到端测试中,你可以使用 graphql 对象运行查询,而无需使用任何 HTTP 监听器。

你可以使用 GraphQLSchemaHost 类来访问生成的 schema(代码优先或模式优先方式均适用):

typescript
const { schema } = app.get(GraphQLSchemaHost);

提示

你必须在应用初始化之后(即 onModuleInit 钩子被 app.listen()app.init() 方法触发之后)才能调用 GraphQLSchemaHost#schema getter。

异步配置

当你需要异步传递模块选项而不是静态传递时,请使用 forRootAsync() 方法。与大多数动态模块一样,Nest 提供了几种处理异步配置的技术。

一种技术是使用工厂函数:

typescript
 GraphQLModule.forRootAsync<ApolloDriverConfig>({
  driver: ApolloDriver,
  useFactory: () => ({
    typePaths: ['./**/*.graphql'],
  }),
}),

像其他工厂提供者一样,我们的工厂函数可以是异步的,并且可以通过 inject 注入依赖。

typescript
GraphQLModule.forRootAsync<ApolloDriverConfig>({
  driver: ApolloDriver,
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    typePaths: configService.get<string>('GRAPHQL_TYPE_PATHS'),
  }),
  inject: [ConfigService],
}),

或者,你可以使用类而不是工厂来配置 GraphQLModule,如下所示:

typescript
GraphQLModule.forRootAsync<ApolloDriverConfig>({
  driver: ApolloDriver,
  useClass: GqlConfigService,
}),

上面的构造会在 GraphQLModule 内部实例化 GqlConfigService,使用它来创建选项对象。注意,在此示例中,GqlConfigService 必须实现 GqlOptionsFactory 接口,如下所示。GraphQLModule 将调用所提供类的实例化对象上的 createGqlOptions() 方法。

typescript
@Injectable()
class GqlConfigService implements GqlOptionsFactory {
  createGqlOptions(): ApolloDriverConfig {
    return {
      typePaths: ['./**/*.graphql'],
    };
  }
}

如果你想重用现有的选项提供者,而不是在 GraphQLModule 内部创建私有副本,请使用 useExisting 语法。

typescript
GraphQLModule.forRootAsync<ApolloDriverConfig>({
  imports: [ConfigModule],
  useExisting: ConfigService,
}),

Mercurius 集成

Fastify 用户(了解更多请阅读这里)可以使用 @nestjs/mercurius 驱动来替代 Apollo。

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { MercuriusDriver, MercuriusDriverConfig } from '@nestjs/mercurius';

@Module({
  imports: [
    GraphQLModule.forRoot<MercuriusDriverConfig>({
      driver: MercuriusDriver,
      graphiql: true,
    }),
  ],
})
export class AppModule {}

提示

应用运行后,打开浏览器并导航到 http://localhost:3000/graphiql。你应该能看到 GraphQL IDE

forRoot() 方法接受一个选项对象作为参数。这些选项会传递给底层的驱动实例。在这里了解更多可用设置。

多端点

@nestjs/graphql 模块的另一个有用功能是能够同时提供多个端点服务。这让你可以决定哪些模块应包含在哪个端点中。默认情况下,GraphQL 在整个应用中搜索 resolver。要将此扫描限制为仅部分模块,请使用 include 属性。

typescript
GraphQLModule.forRoot({
  include: [CatsModule],
}),

警告

如果你在单个应用中使用 @apollo/server@as-integrations/fastify 包并配置多个 GraphQL 端点,请确保在 GraphQLModule 配置中启用 disableHealthCheck 设置。

第三方集成

示例

一个可用的示例在这里

基于 NestJS 官方文档翻译