学习 @nestjs/graphql
依赖
- @nestjs/graphql 是一个单独的npm包
- graphql-tools
- graphql
- apollo-server-express
- apollo-server-fastify
GraphQLModule 与 forRoot()
Nestjs 与 GraphQL 的联系是通过 GraphQLModule 进行连接,GraphQLModule 中的 forRoot 中的参数就是传递给 ApolloServer 的相关的蚕食
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot({}),
],
})
export class AppModule {}
下面我们要思考一个问题,就是我们在写 GraphQL 代码的时候第一原则是什么?
- 统统使用 TypeScript 代码
- 使用 GraphQL 的 原生schema
下面就是关于 GraphQL 代码写建议
代码优先
什么是代码优先呢?就是我们如果习惯了用 TypeScript 写代码,我们可以通过 TypeScript 映射到 GraphQL 的 Schema 文件。代码优先的好处是,我们可以统一使用TypeScript 语言来写代码。在 NestJS 中给我们提供了自动生成器,直接在 GraphQLModule.forRoot({}) 配置项中添加 autoSchemaFile 自动生成 Schema 文件到指定路径。
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
架构 schema 优先
shema 优先的原则:直接写 graphql 文件,我们识别graphql也特别的简单, 配置 typePaths 到指定的文件路径。
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
}),
schema 优先在编写代码时是不够的,我们还需要类型的定义来约束(类型和接口)我们的 TypeScript 代码。NestJS 也已经解决了这个问题,也是能直接生成,生成有两种方法。
- 自动
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
},
}),
- 手动
// 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',
});
GraphQL Resolver
其实 Resolvers 就相当于 Controllers,只是思想不一样,底层不一样。
- Resolver 本身是一个被 Resolver 修饰的类,类的内容就是我们要进行的相关 GraphQL 查询的操作,这些操作包含:
- Query 查询,使用 Query 修饰器修饰
- Mutation 变异,使用 Mutation 修饰器修饰
- Subscription 订阅,使用 Subscription 修饰器修饰
在 Resolver 内部,其实就是根据参数,做响应的逻辑处理。
Resolver 内部类知识一个函数名,在查询时直接使用,和传递对应的参数
import { NotFoundException } from '@nestjs/common';
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'apollo-server-express';
import { NewRecipeInput } from './dto/new-recipe.input';
import { RecipesArgs } from './dto/recipes.args';
import { Recipe } from './models/recipe';
import { RecipesService } from './recipes.service';
const pubSub = new PubSub();
@Resolver(of => Recipe)
export class RecipesResolver {
constructor(private readonly recipesService: RecipesService) {}
@Query(returns => Recipe)
async recipe(@Args('id') id: string): Promise<Recipe> {
const recipe = await this.recipesService.findOneById(id);
if (!recipe) {
throw new NotFoundException(id);
}
return recipe;
}
@Query(returns => [Recipe])
recipes(@Args() recipesArgs: RecipesArgs): Promise<Recipe[]> {
return this.recipesService.findAll(recipesArgs);
}
@Mutation(returns => Recipe)
async addRecipe(
@Args('newRecipeData') newRecipeData: NewRecipeInput,
): Promise<Recipe> {
const recipe = await this.recipesService.create(newRecipeData);
pubSub.publish('recipeAdded', { recipeAdded: recipe });
return recipe;
}
@Mutation(returns => Boolean)
async removeRecipe(@Args('id') id: string) {
return this.recipesService.remove(id);
}
@Subscription(returns => Recipe)
recipeAdded() {
return pubSub.asyncIterator('recipeAdded');
}
}