浅尝 GraphQL 与 NestJS 的完美结合
前言
NestJs
相信大部分人都听过它的大名,而我基本把社区比较流行的node
框架都试过了一遍之后,唯独对Nest
情有独钟。可能是因为它的logo
是个喵星人吧🐱🙂
GraphQL
已经出来很多年了,当初因为种种原因一直没有大火,而我目前涉及到的工作正在使用 node
, 考虑到简洁以及自我提升等各方面问题,决定去尝试下graphql
,结果不得不说,是真香!🤪
本文涉及到的所有代码均在这个 GitHub 下的graphQL
分支里。master
分支是Nest
+JWT
+Mysql
+Sequlize
+Docker
的简易模板,有兴趣的也可以看看
NestJs
创建脚手架
跟 官方文档 一致,首先使用cli
命令工具创建官方简易脚手架
$ npm i -g @nestjs/cli$ nest new nestjs-project
这里不过多介绍nest
的规则及语法,否则三天三夜都写不完,官方文档已经是最完美的解释
GraphQL
理解
GraphQL
是一种用于 API 的查询语言,很多人第一次对于这句话是无法理解的。其实概念这个东西无所谓的,只要理解它的作用及功能就行了,你可以把它当成是 RESTful
的另一种替代方案
展开来说就是现在 RESTful
的概念中,通过 URL 即可访问资源,HTTP
的POST
,DELETE
,PUT
,GET
分别对应增删改查,传输数据的格式绝大部分都以json
为主,比如如下的例子:
// 一个 RESTful 请求GET https://api.domain.com/authors/7
// JSON 数据的响应
{
"id": "7",
"name": "Robin Wieruch",
"avatarUrl": "https://domain.com/authors/7",
"firstName": "Robin",
"lastName": "Wieruch"
}
但是在平时的工作开发中,有没有遇到过一些场景,比如:我需要请求一个资源,但是我需要的数据是由很多个接口组合成的,并且接口之间互相关联,比如上一个接口的返回是下一个接口的参数,而且我们只需要每个接口其中的一小块数据,这样就造成了数据的冗余和复杂度的提升,前端数据代码的可读性变得非常差。
这个时候GraphQL
就能非常好地解决这个问题,只要看张图,我想就都能明白了
通过上面的一张图,我们把author
当成一张表, posts
当成另外一张表,你需要什么参数就写什么参数, 而且post
里面的数据是根据author.id
的数据查找的,非常方便 and 快捷
具体的GraphQL
概念可以在GraphQL 官方文档 里找到,这里主要介绍几个特别重要的概念
操作类型 Operation Type
操作主要包括三个:query、mutation 或 subscription
, 通过名字就能知道它们的具体含义,它们主要是用来确定客户端需要进行什么操作
- query 查询:获取数据,上面图中战士的就是查询
- mutation 变更:对数据进行变更
- substription 订阅:当数据发生更改,进行消息推送
对象类型和标量类型 Object Type & Scalar Type
这个我们直接拿一个例子来说
// 对应上方图中的结构的 Schema// Author 就是 Object Type
// Int、String 就是GraphQL 内置的 Scalar Type
// ! 表示此标量不可为空
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post!]!
}
模式 Schema
这个就很好理解,它定义了字段的类型、数据的结构,描述了接口数据请求的规则,当我们进行一些错误的查询的时候 GraphQL 引擎会负责告诉我们哪里有问题,和详细的错误信息,对开发调试十分友好。
简单来说,你就把它当做typescript
的interface
,type
等用来强类型规范的语法就行, 他们看起来是非常相似的
解析函数 Resolver
Resolver
的作用就是当客户端的请求发送过来以后,通过它来提供数据。比如我们请求一个query
:
query {author(id: 2) {
id,
firstName
}
}
而转到resolver
解析函数中就会变成这样:
Query: {author(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
....
)
}
}
其中的四个参数分别是:
obj
:当前上一个解析函数的返回值args
:查询中传入的参数context
:提供给所有解析器的上下文信息info
:一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值
而最后则返回数据,上例中是通过连接数据库返回数据,仓库里有详细的例子,主要依靠Sequlize
上手
了解了大致简单地概念,下面开始直接使用NestJs
来开发GraphQL
服务器,这块内容其实NestJs 官方文档
里都有写,这里以一个实操型的选手做一个减小的总结
首先说明下,NestJs
提供的@nestjs/graphql
只是对 Apollo server 的包装器,具体的详情可以去官方文档看下,它是一个开源的 GraphQL 服务器
官方文档提供了两种方式来构建 GraphQL 应用程序:Schema first
和 Code first
, 这里主要展示Code first
, 不要问我为什么,就是任性
初始化
首先执行以下语句
$ npm i @nestjs/graphql graphql-tools graphql apollo-server-express
接下来在app.module.ts
中加入
import { Module } from'@nestjs/common';import { GraphQLModule } from'@nestjs/graphql';
import { AuthorModule } from'./author/author.module'
import { join } from'path';
@Module({
imports: [
...
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // 最后生成的`Schema 文件,不可修改`
}),
ConfigModule.forRoot({
load: [configuration],
}),
AuthorModule
],
})
model
根据上图的例子,创建author
文件夹,开始编写 model
, 项目启动后会整合所有的model
生成 schema.gql
// author/author.model.tsimport { Field, Int, ObjectType } from'@nestjs/graphql';
import { Post } from'./post.graphql';
@ObjectType() // 对象类型是 Author
exportclass Author {
@Field(type => Int)
id: number;
@Field({ nullable: true })
firstName?: string;
@Field({ nullable: true })
lastName?: string;
@Field(type => [Post])
posts: Post[];
}
// author/post.model.ts
import { Field, Int, ObjectType } from'@nestjs/graphql';
@ObjectType()
exportclass Post {
@Field(type => Int)
id: number;
@Field()
title: string;
@Field(type => Int, { nullable: true })
votes?: number;
}
上面的例子最后生成的样子其实就是:
type Post {id: Int!
title: String!
votes: Int
}
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post!]!
}
resolver
接下来编写resolver
解析函数, 用于根据用户请求去获取数据
// author/author.resolver.tsimport {
Resolver,
Query,
Parent,
ResolveField,
Args,
Int,
} from'@nestjs/graphql';
import { Author } from'./graphql/author.graphql';
import { Post } from'./graphql/post.graphql';
@Resolver(() => Author)
exportclass AuthorsResolver {
constructor(
private authorsService: AuthorsService,
private postsService: PostsService) {}
// @Query 表示创建Query 操作类型
// @Args 表示传入的参数
@Query(() => Author)
async author(@Args('id', { type: () => Int }) id: number): Promise<any> {
// 这里注释掉的是启用 `service` 对数据库进行访问
// return this.authorsService.findOneById(id);
return {
id,
firstName: 'name',
lastName: 'mase',
};
}
// @ResolveField 表示下面装饰的方法与父类型(在当前示例中为Author类型)相关联
@ResolveField()
async posts(@Parent() author: Author): Promise<any> {
const { id } = author;
return [
{
id: 4,
title: 'hello',
votes: 2412,
},
];
}
}
author.module
最后及在 author.module
中注入即可
import { Module } from'@nestjs/common';import { AuthorsResolver } from'./authors.resolver'
@Module({
providers: [AuthorsResolver],
})
exportclassAuthorModule{}
再次运行项目, 转到http://localhost:3000/graphql
地址下,即可验证
提示
上面的例子只是最基本的Query
操作,而且没有与数据库相关联,在我发布的仓库地址里,结合了MySql
+Sequlize
进行对数据库数据的操作,以及Docker
部署的具体配置,有兴趣的可以去看看
结语
GraphQL
很香很好用,但是这是对前端数据请求而言的,主要的压力在于后端,对于很多公司,重新选择GraphQL
代替REST
需要对整个服务端暴露数据的方式进行改写,这显然是不可能的。但是对于个人或者是全栈项目而言,这是个非常好的尝试机会,技多不压身嘛
参考文献
NestJs GraphQL
GraphQL
GraphQL 从入门到实践
以上是 浅尝 GraphQL 与 NestJS 的完美结合 的全部内容, 来源链接: utcz.com/a/34298.html