如何使用 Nest 构建 GraphQL API
欢迎在推特上关注我,我很乐意接受您对话题或改进方面的建议。/克里斯
构建 GraphQL API 的方法有很多。以 JavaScript 为例,你可能正在使用原生
grapql库、库,或者Apollographql-express提供的库。其实还有一种非常棒的方法,那就是 Nest.js,它与 GraphQL 完美集成。
本文我们将:
- 快速讲解GraphQL 基础知识。我们将解释足够多的内容,让您理解主要结构。
- 创建第一个 Nest.js + GraphQL 项目,看看完整的 CRUD 操作是什么样的。
- 最佳实践:让我们看看如何才能充分发挥 Nest 的强大功能。
GraphQL基础知识
我在以下文章中解释了GraphQL的基础知识:
如果要完整介绍 GraphQL,这篇文章会变得非常长,所以我们不妨简单地说明 GraphQL API 由 schema 和 resolver 函数组成。
Hello GraphQL在 Nest.js 中创建你的第一个作品
好了,现在我们对GraphQL的工作原理有了基本的了解。接下来,我们将进行以下操作:
- 搭建Nest 项目脚手架
- 将项目配置为使用 GraphQL
- 编写我们的模式和解析器
搭建一个 Nest.js 项目
要搭建一个新项目,只需输入以下命令:
nest new hello-world
您可以将其替换hello-world为您的项目名称。这将为您提供下一步所需的必要文件,即添加 GraphQL。
连接 GraphQL
现在,要在我们刚刚创建的项目中使用 GraphQL,我们需要执行以下操作:
- 安装所需的依赖项
- 配置
GraphQLModule
好的,要安装依赖项,我们需要输入:
npm i --save @nestjs/graphql apollo-server-express graphql
以上将为我们提供 Nest 所需的 GraphQL 绑定@nestjs/graphql和用于创建 GraphQL 服务器的 Apollo 库apollo-server-express。
接下来,我们需要配置一个GraphQLModule从库中获取的名为 `<schema>` 的组件@nestjs/graphql。有很多方法可以设置它,但此时我们只需告诉它模式文件的位置即可。因此,我们将修改app.module.ts为如下所示:
// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { AppResolver } from './app.resolver';
// import { join } from 'path';
@Module({
imports: [
GraphQLModule.forRoot({
debug: false,
playground: true,
typePaths: ['./**/*.graphql']
}),
],
providers: [ AppResolver ]
})
export class AppModule { }
让我们仔细看看这个GraphQLModule.forRoot()调用。现在,我们看到这里将 `is_query_name` 设置playground为 `true`。这将为我们提供一种图形化的方式来表达查询,稍后会详细介绍。我们还看到我们设置了一个名为 `is_query_name` 的属性typePaths,并为其赋予一个如下所示的数组['./**/*.graphql']。这是一个模式匹配,用于查找所有以 `.` 结尾的文件.graphql。这种构造的原因在于,我们可以将模式定义分散到多个文件中。
编写我们的模式和解析器
下一步是创建一个符合上述模式的文件,因此我们创建一个名为 `.txt` 的文件app.graphql,并为其添加以下内容:
// app.graphql
type Cat {
id: Int
name: String
age: Int
}
type Query {
getCats: [Cat]
cat(id: ID!): Cat
}
现在我们已经做好了充分的准备,但是解析器函数呢?好,让我们回到app.module.ts代码片段,放大查看其中一行providers: [ AppResolver ]。这里我们正在配置AppResolver将作为解析器类的函数。让我们仔细看看AppResolver:
// app.resolver.ts
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { ParseIntPipe } from '@nestjs/common';
@Resolver('Cat')
export class AppResolver {
cats = [{
id: 1,
name: 'Mjau',
age: 17
}]
@Query()
getCats() {
console.log('getCats');
return this.cats;
}
@Query('cat')
async findOneById(
@Args('id', ParseIntPipe)
id: number,
): Promise<any> {
return this.cats.find(c => c.id === id);
}
}
如您所见,我们创建了一个类AppResolver,它还附带了一些有趣的装饰器。让我们来解释一下:
@Resolver该装饰器告诉 GraphQL 此类应该知道如何解析与类型相关的任何内容Cat。Query()这表示被 `this` 装饰的方法会按名称匹配Query模式中定义的某些内容。我们可以看到,我们有这个方法,getCats()但如果我们不打算进行名称匹配,就需要向它传递一个参数,说明它匹配的是哪个部分。正如你所看到的,我们Query在方法上使用了 `@MyName` 装饰器,这意味着它会解析对 `MyName` 的任何查询。findOneById()Query('cat')cat@Args这个装饰器用作辅助装饰器,用于挖掘任何输入参数。
试驾一下
首先,让我们确保所有必要的库都已安装,请先输入:
npm install
这将安装所有必需的依赖项。安装完成后,我们就可以开始了。
接下来输入以下内容,以便我们测试我们的 API:
npm start
它应该看起来像这样:
下一步是打开我们的浏览器地址栏http://localhost:3000/graphql。您应该会看到以下内容:
如上图所示,我们定义了两个不同的查询,分别名为 `query`oneCat和allCats`query`,您可以在每个查询中看到其定义。在名为 `query` 的查询中,oneCat您可以看到我们如何调用`query`,这意味着我们使用参数 ` value` 和 `value`{ cat(id: 1){ name } }调用 `query` 的解析器,并选择结果中的字段,该字段的类型为 `T` 。另一个查询则是简单地调用 `query` ,它与类中的相同方法匹配。catid1nameCatallCats{ getCats }AppResolver
添加变异器
目前我们已经拥有一个功能齐全的 GraphQL API,可以进行查询,但我们缺少修改器部分。如果我们想要支持添加、更新或删除猫咪信息,该怎么办呢?为此,我们需要执行以下操作:
- 向我们的模式添加修改器操作
AppResolver在我们的类中添加所需的解析器方法- 测试一下
更新我们的架构
好的,我们需要向模式中添加一些修改器,确保app.graphql现在看起来像这样:
type Cat {
id: Int
name: String
age: Int
}
input CatInput {
name: String
age: Int,
id: Int
}
type Mutation {
createCat(cat: CatInput): String,
updateCat(cat: CatInput): String,
deleteCat(id: ID!): String
}
type Query {
getCats: [Cat]
cat(id: ID!): Cat
}
如上所示,我们已经添加Mutation并CatInput
添加解析器
好了,现在我们需要回AppResolver教室,确保它看起来像这样:
// app.resolver.ts
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { ParseIntPipe } from '@nestjs/common';
@Resolver('Cat')
export class AppResolver {
cats = [{
id: 1,
name: 'Cat1',
age: 17
}]
@Mutation()
createCat(
@Args('cat')
cat: any
): Promise<string> {
this.cats = [...this.cats, {...cat, id: this.cats.length + 1}];
return Promise.resolve('cat created');
}
@Mutation()
updateCat(
@Args('cat')
cat: any
): Promise<string> {
this.cats = this.cats.map(c => {
if(c.id === cat.id) {
return {...cat}
}
return c;
});
return Promise.resolve('cat updated');
}
@Mutation()
deleteCat(
@Args('id', ParseIntPipe)
id: number
) : Promise<any> {
this.cats = this.cats.filter(c => c.id !== id);
return Promise.resolve('cat removed');
}
@Query()
getCats() {
console.log('getCats');
return this.cats;
}
@Query('cat')
async findOneById(
@Args('id', ParseIntPipe)
id: number,
): Promise<any> {
return this.cats.find(c => c.id === id);
}
}
新增部分是方法deleteCat(),updateCat()和createCat()。
附加功能
目前我们已经拥有一个功能齐全的 API。事实上,请确保您的浏览器窗口看起来像这样,您就可以测试完整的 CRUD 操作:
最佳实践指的是什么?其实,为了让我们的 API 更易于使用,我们还可以做更多事情,例如:
- 添加类型。目前,我们的文件中定义了很多类型
app.graphql,但我们可以提取这些类型并在解析器类中使用它们。 - 将我们的 API 拆分,没必要使用一个巨大的 schema 文件,完全可以将其拆分,然后让 Nest 来拼接所有这些文件。
- 可以通过装饰 DTO 来定义 API,还有第二种定义 API 的方法,哪种方法最好取决于您的判断。
添加类型
我说过我们可以从模式中提取类型,然后在解析器类中使用它们。这听起来很棒,但我想你肯定想知道具体该怎么做吧?
首先,你需要前往app.module.ts指定属性definitions并指定两项内容。第一项是生成类型文件的名称,第二项是输出类型。输出类型有两种选择:class或interface。你的文件现在应该如下所示:
@Module({
imports: [
GraphQLModule.forRoot({
debug: false,
playground: true,
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
}
}),
],
providers: [ AppResolver ]
})
export class AppModule { }
npm start如果你使用以下命令启动 API src/graphql.ts,则会创建该文件,其内容应如下所示:
//graphql.ts
/** ------------------------------------------------------
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
* -------------------------------------------------------
*/
/* tslint:disable */
export class CatInput {
name?: string;
age?: number;
id?: number;
}
export class Cat {
id?: number;
name?: string;
age?: number;
}
export abstract class IMutation {
abstract createCat(cat?: CatInput): string | Promise<string>;
abstract updateCat(cat?: CatInput): string | Promise<string>;
abstract deleteCat(id: string): string | Promise<string>;
}
export abstract class IQuery {
abstract getCats(): Cat[] | Promise<Cat[]>;
abstract cat(id: string): Cat | Promise<Cat>;
}
对我们来说,关键在于了解类型Cat,CatInput我们可以利用这些AppResolver类型来增强类的类型安全性。你的app.resolver.ts文件现在应该看起来像这样:
// app.resolver.ts
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import { ParseIntPipe } from '@nestjs/common';
import { Cat, CatInput } from './graphql';
@Resolver('Cat')
export class AppResolver {
cats:Array<Cat> = [{
id: 1,
name: 'Cat1',
age: 17
}]
@Mutation()
createCat(
@Args('cat')
cat: CatInput
): Promise<string> {
this.cats = [...this.cats, {...cat, id: this.cats.length + 1}];
return Promise.resolve('cat created');
}
@Mutation()
updateCat(
@Args('cat')
cat: CatInput
): Promise<string> {
this.cats = this.cats.map(c => {
if(c.id === cat.id) {
return {...cat}
}
return c;
});
return Promise.resolve('cat updated');
}
@Mutation()
deleteCat(
@Args('id', ParseIntPipe)
id: number
) : Promise<any> {
this.cats = this.cats.filter(c => c.id !== id);
return Promise.resolve('cat removed');
}
@Query()
getCats(): Array<Cat> {
return this.cats;
}
@Query('cat')
async findOneById(
@Args('id', ParseIntPipe)
id: number,
): Promise<Cat> {
return this.cats.find(c => c.id === id);
}
}
值得注意的是,上面的内部数组cats现在是 `T` 类型,并且Cat方法现在具有 `T` 类型的输入。此外,该方法返回一个 `T` 类型的数组,最后,该方法返回一个 `T` 类型的 Promise 。createCat()updateCat()CatInputgetCats()CatfindOneById()Cat
拆分我们的模式定义
我们之前说过,由于现有的架构,我们可以轻松实现这一点。只需创建一个名为 **.graphql 的文件即可。那么,什么时候应该这样做呢?当你的 API 中包含不同的主题时,进行拆分就很有意义了。例如,如果你要添加狗狗相关的数据,那么dogs.graphql为狗狗创建一个单独的主题类以及一个单独的解析器类就很有必要了。
本文旨在向您展示如何入门,以及如何逐步添加新的类型和解析器。希望对您有所帮助。
第二种定义方式
第二种定义模式的方法超出了本文的讨论范围,因为篇幅太长。不过,您可以参考这个代码库,并阅读标题为“代码优先”的文章,了解这种方法是如何实现的。
概括
现在我们已经完成了从创建新项目、学习定义模式及其解析器到从模式生成类型的所有步骤。我们应该为此感到非常自豪。
文章来源:https://dev.to/azure/how-you-can-use-nest-to-build-a-graphql-api-24ii


