浅尝 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 即可访问资源,HTTPPOST,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, 通过名字就能知道它们的具体含义,它们主要是用来确定客户端需要进行什么操作

  1. query 查询:获取数据,上面图中战士的就是查询
  2. mutation 变更:对数据进行变更
  3. 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 引擎会负责告诉我们哪里有问题,和详细的错误信息,对开发调试十分友好。

简单来说,你就把它当做typescriptinterfacetype 等用来强类型规范的语法就行, 他们看起来是非常相似的

解析函数 Resolver

Resolver 的作用就是当客户端的请求发送过来以后,通过它来提供数据。比如我们请求一个query:

query {

author(id: 2) {

id,

firstName

}

}

而转到resolver解析函数中就会变成这样:

Query: {

author(obj, args, context, info) {

return context.db.loadHumanByID(args.id).then(

....

)

}

}

其中的四个参数分别是:

  1. obj:当前上一个解析函数的返回值

  2. args:查询中传入的参数

  3. context:提供给所有解析器的上下文信息

  4. info:一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值

而最后则返回数据,上例中是通过连接数据库返回数据,仓库里有详细的例子,主要依靠Sequlize

上手

了解了大致简单地概念,下面开始直接使用NestJs来开发GraphQL服务器,这块内容其实NestJs 官方文档里都有写,这里以一个实操型的选手做一个减小的总结

首先说明下,NestJs提供的@nestjs/graphql只是对 Apollo server 的包装器,具体的详情可以去官方文档看下,它是一个开源的 GraphQL 服务器

官方文档提供了两种方式来构建 GraphQL 应用程序:Schema firstCode 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.ts

import { 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.ts

import {

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

回到顶部