# GraphQL
## 驾驭 TypeScript 和 GraphQL 的强大功能
[<span style="color:red">GraphQL</span>](https://graphql.org/) 是一种用于 API 的强大查询语言,是使用现有数据来完成这些查询的运行时。这是一种优雅的方法,可以解决我们在典型REST APIs 中遇到的许多问题 。为了解背景,我们建议你阅读一下 GraphQL 和 REST 之间的[<span style="color:red">比较</span>](https://dev-blog.apollodata.com/graphql-vs-rest-5d425123e34b) 。GraphQL 与 [<span style="color:red">TypeScript</span>](https://www.typescriptlang.org/) 相结合,能帮你在 GraphQL 查询中开发出更好的类型安全性,从而为你提供端到端的输入。
在本章中, 我们假设你对 GraphQL 已经有了基本的了解,我们将不解释什么是 GraphQL, 而是重点介绍如何使用内置的 `@nestjs/graphql` 模块。`GraphQLModule` 仅仅是 [<span style="color:red">Apollo</span>](https://www.apollographql.com) Server 的包装器。我们没有造轮子, 而是提供一个现成的模块, 这让 GraphQL 和 Nest 有了比较简洁的融合方式。
### 安装
首先,我们需要安装以下依赖包:
~~~bash
# 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,则安装 `apollo-server-fastify`,替代安装 `apollo-server-express`。
### 概述
Nest 提供了两种构建 GraphQL 应用程序的方式,**模式优先**和**代码优先**。你可以选择一个适合自己的最佳方案。在 GraphQL 一章中的大部分章节将被分为两个主要部分:一部分采用**代码优先**,另一部分采用**模式优先**。
在**代码优先**的方式中,你将仅使用装饰器和 TypeScript 类来生成相应的 GraphQL schema。如果您更喜欢使用 TypeScript 来工作并想要避免语言语法之间的上下文切换,那这种方式会更有效。
**模式优先**的方式,本质是 GraphQL SDL(模式定义语言)。它以一种与语言无关的方式,基本允许您在不同平台之间共享模式文件。此外,Nest 将根据GraphQL 模式(通过类或接口)自动生成 TypeScript 定义,以减少冗余。
### 入门
依赖包安装完成后,我们就可以加载 `GraphQLModule` 并通过 `forRoot()` 静态方法来配置它。
> app.module.ts
~~~typescript
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 实例(请在[此处](https://www.apollographql.com/docs/apollo-server/v2/api/apollo-server.html#constructor-options-lt-ApolloServer-gt)阅读有关可用设置的更多信息)。例如,如果要禁用`playground`并关闭`debug`模式,只需传递以下选项:
```typescript
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
debug: false,
playground: false,
}),
],
})
export class AppModule {}
```
如上所述,所有这些设置都将传递给`ApolloServer`构造函数。
### GraphQL playground
`Playground` 是一个图形化的,交互式的浏览器内 GraphQL IDE,默认情况下可与 GraphQL 服务器本身 URL 相同。为了进入 playground,你需要进行基础的 GraphQL 服务配置并且运行它。现在看,你可以用这里的[示例代码](https://github.com/nestjs/nest/tree/master/sample/23-graphql-code-first)进行安装和构建。或者,如果你遵循这些代码示例,一旦你完成了[解析器章节]()中的步骤,你就可以进入 `playground` 了。
当您的应用程序在后台运行时,打开 Web 浏览器并访问: `http://localhost:3000/graphql` (主机和端口可能因您的配置而异)。你将看到 `GraphQL playground`,如下所示。
![](https://docs.nestjs.com/assets/playground.png)
>**笔记**:`@nestjs/mercurius` 集成不附带内置的 `GraphQL Playground` 集成。 相反,您可以使用 GraphiQL(设置 `graphiql: true`)。
### 多个端点
该模块的另一个有用功能是能够同时为多个端点提供服务。多亏了这一点,您可以决定哪个模块应该包含在哪个端点中。默认情况下,`GraphQL` 在整个应用程序中搜索解析器。要仅限制模块的子集,可以使用该 `include` 属性。
```typescript
GraphQLModule.forRoot({
include: [CatsModule],
}),
```
> 如果你在单个应用中使用具有多个 GraphQL 端点的 `apollo-server-fastify` 包,请确保在 GraphQLModule 配置中启用 `disableHealthcheck` 设置。
<!-- tabs:start -->
#### ** 模式优先 **
使用模式优先的方式,首先要在配置对象中添加 `typePaths`属性。该 `typePaths` 属性指示 `GraphQLModule` 应该查找 GraphQL SDL schema 文件的位置。所有这些文件最终将合并到内存中,这意味着您可以将模式拆分为多个文件并将它们放在靠近解析器的位置。
```typescript
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
}),
```
你通常也需要有对应于 GraphQL SDL 类型的 TypeScripts 定义(类和接口)。手动创建相应的 TypeScript 定义是多余且乏味的。它让我们没有单一的事实来源-- SDL 中所做的每一次更改都迫使它们也调整 TypeScript 定义。为解决这个问题,该 `@nestjs/graphql` 包可以使用抽象语法树([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree))**自动生成** TypeScript 定义。要启用这个功能,只需在配置 `GraphQLModule`时 添加 `definitions` 属性即可。
```typescript
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
},
}),
```
`defintions` 对象中的 path 属性,指示在哪里保存生成的 TypeScript 输出文件。默认情况下,所有生成的 TypeScript 被转换为接口类型。若要转换为类,则要将 `outputAs` 属性指定为 `class`。
```typescript
GraphQLModule.forRoot({
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 '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,
});
```
要将解析器(查询、变更、订阅)生成不带参数的普通字段,需开启 `skipResolverArgs` 选项。
```typescript
definitionsFactory.generate({
// ...,
skipResolverArgs: true,
});
```
[这里](https://github.com/nestjs/nest/tree/master/sample/12-graphql-apollo) 提供完整的例子。
#### ** 代码优先 **
要使用模式优先方法,首先将 typePaths 属性添加到选项对象。 typePaths 属性指示 GraphQLModule 应在何处查找您将要编写的 GraphQL SDL 模式定义文件。 这些文件将在内存中合并; 这允许您将架构拆分为多个文件并将它们定位在它们的解析器附近。
在代码优先方式中,您将只使用装饰器和 TypeScript 类来生成相应的 `GraphQL schema`。
使用代码优先方式,首先要在配置对象里添加 `autoSchemaFile` 这个属性:
```typescript
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
```
这里 `autoSchemaFile` 属性值是您自动生成的 schema 文件将被创建的路径。或者,schema 也可以被实时创建在内存里。要开启它,需要设置 `autoSchemaFile` 属性为 `true`:
```typescript
GraphQLModule.forRoot({
autoSchemaFile: true,
}),
```
默认情况下,生成的 schema 中的类型将按照它们在包含的模块中定义的顺序。要按照字典顺序对 schema 进行排序,需设置 `sortSchema` 属性为 `true`。
```typescript
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
}),
```
上述方法在应用程序每次启动时动态生成 TypeScript 定义。或者,最好构建一个简单的脚本来按需生成这些脚本。例如,假设我们创建以下脚本`generate-typings.ts`:
~~~typescript
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',
});
~~~
现在您可以按需运行此脚本:
~~~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,
});
~~~
要将解析器(查询、突变、订阅)生成为不带参数的普通字段,请启用该`skipResolverArgs`选项。
~~~typescript
definitionsFactory.generate({
// ...,
skipResolverArgs: true,
});
~~~
[这里](https://github.com/nestjs/nest/tree/master/sample/23-graphql-code-first)提供代码优先的完整例子。
### Apollo Sandbox (阿波罗沙盒)[#](#apollo-sandbox)
要使用[Apollo Sandbox](https://www.apollographql.com/blog/announcement/platform/apollo-sandbox-an-open-graphql-ide-for-local-development/)而不是`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-core';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
playground: false,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
],
})
export class AppModule {}
~~~
#### 示例[#](https://docs.nestjs.com/graphql/quick-start#example-1)
[此处](https://github.com/nestjs/nest/tree/master/sample/12-graphql-schema-first)提供了一个完整工作的模式第一个示例。
<!-- tabs:end -->
### 访问生成的 schema
在某些情况下(例如端到端的测试),你可能希望引用生成的 schema 对象。在端到端的测试中,你可以使用 `graphql` 对象运行查询,而无需使用任何 HTTP 监听器。
你可以使用 `GraphQLSchemaHost` 类,访问生成的 schema(无论是代码优先还是模式优先方式)。
```typescript
const { schema } = app.get(GraphQLSchemaHost);
```
> 你必须在应用初始化之后(在 `onModuleInit` 钩子被 `app.listen()` 或 `app.init()` 方法触发之后)才能调用 `GraphQLSchemaHost` 的 getter 方法。
### 异步配置
当你需要异步而不是静态地传递模块选项时,请使用 `forRootAsync()` 方法。与大多数动态模块一样,Nest 提供了多种技术来处理异步配置。
第一种技术是使用工厂函数:
~~~typescript
GraphQLModule.forRootAsync<ApolloDriverAsyncConfig>({
driver: ApolloDriver,
useFactory: () => ({
typePaths: ['./**/*.graphql'],
}),
}),
~~~
像其他工厂提供者一样,我们的工厂函数可以是[异步](https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory)的, 并且能够通过 `inject` 注入依赖关系。
~~~typescript
GraphQLModule.forRootAsync<ApolloDriverAsyncConfig>({
driver: ApolloDriver,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
typePaths: configService.getString('GRAPHQL_TYPE_PATHS'),
}),
inject: [ConfigService],
}),
~~~
另外,你也可以在配置 `GraphQLModule` 时用类替代工厂,如下所示:
~~~typescript
GraphQLModule.forRootAsync<ApolloDriverAsyncConfig>({
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({
imports: [ConfigModule],
useExisting: ConfigService,
}),
```
### 整合Mercurius
除了使用 Apollo,Fastify 用户([在此处](https://docs.nestjs.com/techniques/performance)阅读更多内容)也可以使用`@nestjs/mercurius`驱动程序。
~~~typescript
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`](https://github.com/graphql/graphiql)。
该`forRoot()`方法将选项对象作为参数。这些选项被传递到底层驱动程序实例。[在此处](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options)阅读有关可用设置的更多信息。
- 介绍
- 概述
- 第一步
- 控制器
- 提供者
- 模块
- 中间件
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 自定义装饰器
- 基础知识
- 自定义提供者
- 异步提供者
- 动态模块
- 注入作用域
- 循环依赖
- 模块参考
- 懒加载模块
- 应用上下文
- 生命周期事件
- 跨平台
- 测试
- 技术
- 数据库
- Mongo
- 配置
- 验证
- 缓存
- 序列化
- 版本控制
- 定时任务
- 队列
- 日志
- Cookies
- 事件
- 压缩
- 文件上传
- 流式处理文件
- HTTP模块
- Session(会话)
- MVC
- 性能(Fastify)
- 服务器端事件发送
- 安全
- 认证(Authentication)
- 授权(Authorization)
- 加密和散列
- Helmet
- CORS(跨域请求)
- CSRF保护
- 限速
- GraphQL
- 快速开始
- 解析器(resolvers)
- 变更(Mutations)
- 订阅(Subscriptions)
- 标量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 联合类型
- 枚举(Enums)
- 字段中间件
- 映射类型
- 插件
- 复杂性
- 扩展
- CLI插件
- 生成SDL
- 其他功能
- 联合服务
- 迁移指南
- Websocket
- 网关
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 适配器
- 微服务
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定义传输器
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 独立应用
- Cli
- 概述
- 工作空间
- 库
- 用法
- 脚本
- Openapi
- 介绍
- 类型和参数
- 操作
- 安全
- 映射类型
- 装饰器
- CLI插件
- 其他特性
- 迁移指南
- 秘籍
- CRUD 生成器
- 热重载
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模块
- Swagger
- 健康检查
- CQRS
- 文档
- Prisma
- 静态服务
- Nest Commander
- 问答
- Serverless
- HTTP 适配器
- 全局路由前缀
- 混合应用
- HTTPS 和多服务器
- 请求生命周期
- 常见错误
- 实例
- 迁移指南
- 发现
- 谁在使用Nest?