利用 TypeScript 和 GraphQL 的强大功能
GraphQL 是一种强大的 API 查询语言,也是使用现有数据执行这些查询的运行时。它是一种优雅的方法,可以解决 REST API 中常见的许多问题。作为背景介绍,我们建议阅读 GraphQL 和 REST 之间的 比较。GraphQL 与 TypeScript 相结合,可帮助您通过 GraphQL 查询实现更好的类型安全性,从而为您提供端到端的类型输入。
在本章中,我们假设您对 GraphQL 有基本的了解,并重点介绍如何使用内置的 @nestjs/graphql
模块。 GraphQLModule
可以配置为使用 Apollo 服务器(使用 @nestjs/apollo
驱动程序)和 Mercurius(使用 @nestjs/mercurius
)。我们为这些经过验证的 GraphQL 软件包提供官方集成,以提供一种将 GraphQL 与 Nest 结合使用的简单方法(查看 此处 的更多集成)。
您还可以构建自己的专用驱动程序(阅读更多相关信息,请 此处)。
安装
首先安装所需的软件包:
# For Express and Apollo (default)
$ npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql
# For Fastify and Apollo
# npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/fastify graphql
# For Fastify and 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 应用程序的方法,代码优先 和 架构优先 方法。您应该选择最适合您的方法。本 GraphQL 部分中的大多数章节分为两个主要部分:一个是如果您采用代码优先,则应遵循的部分;另一个是如果您采用模式优先,则应使用的部分。
在代码优先方法中,您使用装饰器和 TypeScript 类来生成相应的 GraphQL 模式。如果您更喜欢专门使用 TypeScript 并避免在语言语法之间切换上下文,则此方法很有用。
在模式优先方法中,事实来源是 GraphQL SDL(模式定义语言)文件。SDL 是一种与语言无关的方式,用于在不同平台之间共享模式文件。Nest 会根据 GraphQL 模式自动生成您的 TypeScript 定义(使用类或接口),以减少编写冗余样板代码的需要。
开始使用 GraphQL 和 TypeScript
在接下来的章节中,我们将集成 @nestjs/apollo
包。如果您想改用mercurius
包,请导航至此部分。
安装包后,我们可以导入GraphQLModule
并使用forRoot()
静态方法对其进行配置。
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
集成,您应该改用 MercuriusDriver
和 MercuriusDriverConfig
。两者都是从 @nestjs/mercurius
包导出的。
forRoot()
方法将选项对象作为参数。这些选项将传递到底层驱动程序实例(在此处阅读有关可用设置的更多信息:Apollo 和 Mercurius)。例如,如果您想禁用 playground
并关闭 debug
模式(对于 Apollo),请传递以下选项:
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 游乐场
游乐场是一个图形化、交互式、浏览器内 GraphQL IDE,默认情况下在与 GraphQL 服务器本身相同的 URL 上可用。要访问游乐场,您需要配置并运行一个基本的 GraphQL 服务器。要立即查看它,您可以安装并构建 此处的工作示例。或者,如果您按照这些代码示例进行操作,一旦您完成了 解析器章节 中的步骤,您就可以访问游乐场。
有了这些,并且您的应用程序在后台运行,您就可以打开 Web 浏览器并导航到 http://localhost:3000/graphql
(主机和端口可能因您的配置而异)。然后您将看到 GraphQL 游乐场,如下所示。
![img](/images/playground.png" alt=")
@nestjs/mercurius
集成不随内置的 GraphQL Playground 集成一起提供。相反,您可以使用 GraphiQL(设置 graphiql: true
)。
多个端点
@nestjs/graphql
模块的另一个有用功能是能够同时为多个端点提供服务。这让您可以决定哪些模块应该包含在哪个端点中。默认情况下,GraphQL
会在整个应用程序中搜索解析器。要将此扫描限制为模块的子集,请使用 include
属性。
GraphQLModule.forRoot({
include: [CatsModule],
})
如果您在单个应用程序中将 @apollo/server
与 @as-integrations/fastify
包一起使用,并且有多个 GraphQL 端点,请确保在 GraphQLModule
配置中启用 disableHealthCheck
设置。
代码优先
在 代码优先 方法中,您使用装饰器和 TypeScript 类来生成相应的 GraphQL 模式。
要使用代码优先方法,首先将 autoSchemaFile
属性添加到选项对象:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
})
autoSchemaFile
属性值是自动生成的架构的创建路径。或者,可以在内存中即时生成架构。要启用此功能,请将 autoSchemaFile
属性设置为 true
:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
})
默认情况下,生成的架构中的类型将按照它们在包含的模块中定义的顺序排列。要按字典顺序对架构进行排序,请将 sortSchema
属性设置为 true
:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
})
示例
此处 提供了完整可用的代码优先示例。
模式优先
要使用模式优先方法,首先向选项对象添加 typePaths
属性。typePaths
属性指示 GraphQLModule
应在何处查找您要编写的 GraphQL SDL 模式定义文件。这些文件将在内存中合并;这允许您将模式拆分为多个文件并将它们放置在解析器附近。
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/*.graphql'],
})
通常,您还需要具有与 GraphQL SDL 类型相对应的 TypeScript 定义(类和接口)。手动创建相应的 TypeScript 定义是多余且繁琐的。它使我们没有单一的可信来源——在 SDL 中所做的每个更改都迫使我们调整 TypeScript 定义。为了解决这个问题,@nestjs/graphql
包可以从抽象语法树 (AST) 自动生成 TypeScript 定义。要启用此功能,请在配置 GraphQLModule
时添加 definitions
选项属性。
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
},
})
definitions
对象的 path 属性指示将生成的 TypeScript 输出保存在何处。默认情况下,所有生成的 TypeScript 类型都创建为接口。要生成类,请将 outputAs
属性指定为 'class'
的值。
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
},
})
上述方法在每次应用程序启动时动态生成 TypeScript 定义。或者,最好构建一个简单的脚本来按需生成这些定义。例如,假设我们创建以下脚本作为generate-typings.ts
:
import { join } from 'node:path'
import { GraphQLDefinitionsFactory } from '@nestjs/graphql'
const definitionsFactory = new GraphQLDefinitionsFactory()
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
})
现在您可以按需运行此脚本:
$ ts-node generate-typings
您可以预先编译脚本(例如,使用tsc
)并使用node
执行它。
要为脚本启用监视模式(每当任何.graphql
文件更改时自动生成类型),请将watch
选项传递给generate()
方法。
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
watch: true,
})
要为每个对象类型自动生成附加的 __typename
字段,请启用 emitTypenameField
选项。
definitionsFactory.generate({
// ...,
emitTypenameField: true,
})
要将解析器(查询、突变、订阅)生成为不带参数的普通字段,请启用 skipResolverArgs
选项。
definitionsFactory.generate({
// ...,
skipResolverArgs: true,
})
Apollo Sandbox
要使用 Apollo Sandbox 而不是 graphql-playground
作为用于本地开发的 GraphQL IDE,请使用以下配置:
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 {}
示例
此处 提供了一个完全可用的架构优先示例。
访问生成的架构
在某些情况下(例如端到端测试),您可能希望获取对生成的架构对象的引用。在端到端测试中,您可以使用 graphql
对象运行查询,而无需使用任何 HTTP 侦听器。
您可以使用 GraphQLSchemaHost
类访问生成的架构(采用代码优先或架构优先方法):
const { schema } = app.get(GraphQLSchemaHost)
您必须在应用程序初始化后(在 onModuleInit
钩子被 app.listen()
或 app.init()
方法触发后)调用 GraphQLSchemaHost#schema
getter。
异步配置
当您需要异步传递模块选项而不是静态传递时,请使用 forRootAsync()
方法。与大多数动态模块一样,Nest 提供了几种处理异步配置的技术。
一种技术是使用工厂函数:
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useFactory: () => ({
typePaths: ['./**/*.graphql'],
}),
})
与其他工厂提供程序一样,我们的工厂函数可以是 异步,并且可以通过 inject
注入依赖项。
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
typePaths: configService.get<string>('GRAPHQL_TYPE_PATHS'),
}),
inject: [ConfigService],
})
或者,您可以使用类而不是工厂来配置 GraphQLModule
,如下所示:
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: GqlConfigService,
})
上面的构造在 GraphQLModule
中实例化 GqlConfigService
,并使用它来创建选项对象。请注意,在此示例中,GqlConfigService
必须实现 GqlOptionsFactory
接口,如下所示。GraphQLModule
将在所提供类的实例化对象上调用 createGqlOptions()
方法。
@Injectable()
class GqlConfigService implements GqlOptionsFactory {
createGqlOptions(): ApolloDriverConfig {
return {
typePaths: ['./**/*.graphql'],
}
}
}
如果您想重用现有的选项提供程序,而不是在 GraphQLModule
中创建私有副本,请使用 useExisting
语法。
GraphQLModule.forRootAsync<ApolloDriverConfig>({
imports: [ConfigModule],
useExisting: ConfigService,
})
Mercurius 集成
Fastify 用户(阅读更多 此处)可以使用 @nestjs/mercurius
驱动程序,而不是使用 Apollo。
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()
方法将选项对象作为参数。这些选项将传递到底层驱动程序实例。有关可用设置的更多信息,请参阅 此处。
第三方集成
示例
一个工作示例可在此处找到 此处。