写在前面的话
Nest 最新的版本为V9,较之前的V7/8有不小改动,GraphQL 部分之前的V8也是我翻译的,有很多不完善的地方,这次打算重新仔细翻译一遍,会持续更新这块内容,并最后贡献给中文文档仓库,为中文社区贡献一份力量。
利用 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 graphql apollo-server-express
# For Fastify and Apollo
# npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-fastify
# For Fastify and Mercurius
# npm i @nestjs/graphql @nestjs/mercurius graphql mercurius fastify
警告
@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 schema。如果你更喜欢用TypeScript并想避免在不同语言语法之间的上下文切换,那么这种方式会更有效。
在模式优先方式中,真相来源就是GraphQL SDL(Schema Definition Language)文件。SDL是一种在不同平台间共享模式文件的与语言无关的方式。Nest会基于GraphQL schemas自动生成TypeScript定义(不管是用类或者接口),这样就能减少冗余模版代码的编写。
开始使用GraphQL & TypeScript
提示 在接下来的章节中,我们将集成
@nestjs/apollo
包。如果你想用mercurius
包替代,请查阅[此节](to reduce the need to write redundant boilerplate code.)。
安装好依赖包之后,我们就可以导入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
的集成,你应该使用Mercurius
和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,
debug: false,
playground: false,
}),
],
})
export class AppModule {}
在此例中,这些配置项将被转发给ApolloServer
构造器。
GraphQL palyground
palyground是一个图形化、可交互、嵌入浏览器内的GraphQL IDE,和GraphQL服务本身的默认URL是一样的。要使用playground,你需要配置并运行一个基础的GraphQL服务。要现在看,你可以安装并构建这个示例项目。或者,如果你跟着这些代码样例学习,一旦你完成了解析器一章,你就能进入playground了。
到时候,只要将你的程序运行起来,你就可以打开浏览器并导航到http://localhost:3000/graphql
(主机名和端口可能根据你自己的配置有所不同)。你将看到GraphQL playground,如下所示。
注意 @nestjs/mercurius集成方案中不包含内置的GraphQL Playground的集成。你可以使用GraphiQL(设置
graphiql: true
)替代。
多端点
@nestjs/graphql
模块的另一个非常有用的特性是可以同时启动多个端点。这能让你自己决定哪个端点将包含哪些模块。默认情况下,GraphQL
会在整个应用程序中搜索解析器。要限制该扫描仅在部分模块中进行,使用include
属性。
GraphQLModule.forRoot({
include: [CatsModule],
}),
警告 如果你在一个单体应用中使用
apollo-server-fastify
包和多GraphQL多端点,请确保在GraphQLModule
配置中打开disableHealthCheck
设置。
代码优先
在代码优先方式中,你使用装饰器和TypeScript类来生成对应的GraphQL schema。
要使用代码优先方式,首先要在配置项对象中添加autoSchemaFile
属性:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
autoSchemaFile
属性的值就是创建自动生成的schema的路径。另外,schema也可以在内存中即时生成。要启用这个特性,设置autoSchemaFile
属性为true
即可:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
}),
默认情况下,在生成的schema中的类型将按照它们在包含的模块中定义的顺序排序。要按字典顺序对schema进行排序,设置sortSchema
属性为true
即可:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
}),
例子
此处提供了一个完整可用的代码优先示例。
模式优先
要使用模式优先方式,首先要给配置项对象添加一个typePath
属性。typePath
属性指定了GraphQLModule
应该在哪里查找你将要编写的GraphQL SDL schema 定义文件。这些文件将在内存中合成;这允许你把schemas分割成不同的文件并将它们放置在靠近自身解析器的地方。
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
对象中的路径属性指定在哪里保存生成的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 { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';
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-core';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
playground: false,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
],
})
export class AppModule {}
例子
在此提供了一个完整可用的模式优先示例。
访问生成的schema
在某些情况下(比如端到端测试),你可能希望引用生成的schema对象。在端到端测试中,你可以使用graphql
对象运行查询,而无需使用任何HTTP监听器。
使用GraphQLSchemaHost
类,你就可以访问生成的schema(不管是在代码优先或模式优先方式中):
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集成方案
除了使用Apollo,Fastify用户(在此了解更多信息)也可以使用@nestjs/mercurius
驱动程序。
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()
方法接口一个选项对象作为参数。这些选项会被传递到底层驱动程序的实例。在此了解更多可用的设置。