前言
最近项目中在做一个 BFF
, nest.js
和 GraphQL
这两个技术栈 是一个”新”的尝试,虽然 GraphQL
在 15
年就出来了,但是在与 nest.js
结合,得益于作者良好的封装,发生了奇妙的化学反应
当然这不是一篇 粘贴官方文档 然后教你如何使用的 水文,而是采坑心得的 水文
巨人的肩膀
type-graphql
将typescript
的定义转成graphql
的schema
@nestjs/graphql
是 作者 在 apollo-server 的基础上进行了 2 次封装data-loader
数据的聚合与缓存 解决resolver (n+1)
的问题
应用入口
在这里我们以一个 UserModule
为例
可以通过
1 | query UserList() { |
得到
1 | { |
1 | import { Module } from '@nestjs/common' |
在这里 每次 启动应用的时候 会遍历所有的 graphql
schema 文件 生成 graphql.ts
例如
1 | type User { |
会生成
1 | export class User { |
然后我们写 resolver
和 service
的时候 就可以用 graphql.ts
生成好的类型定义,但是这种方式有一点不方便,有点不符合编程习惯
如果想要先写 typescript
的定义,生成 graphql
的 schema
文件,那么就要用到 type-graphql
了
1 | import { Module } from '@nestjs/common' |
最后 只需要写对应的 model
即可
1 | import { Field, ID } from 'type-graphql' |
这里可以理解 是对 graphql
schema 的一个隐射 , @Field
装饰器映射的是 schema
里面 id
的类型
Class User
的 id 描述的 ts
的类型
值得注意的是 string
| boolean
等 基础 类型 @Field
可以省略,但是 number
默认会转成 float
, 所以需要显示声明,这点比较坑
另外一点是如果是枚举,需要使用 registerEnumType
注册一次
1 | import { registerEnumType } from 'type-graphql' |
Resolver
在 nest.js
里 一个 Graphql
模块 由 resolver
和 service
组成
1 | import { Module } from '@nestjs/common'; |
1 | import { Args, Resolver, Query } from '@nestjs/graphql' |
每个 @Query
装饰器 对应一个 方法 默认会将函数的名字 当成 query 的名字 , 使用 name 可以显示的指定,
这样当发起一个 Query
时,对应的 Resolver
会调用对应的 service
处理逻辑,即可
1 | query users { |
如果想查询第三个字段 age
但是 age
又不在 User
的数据里,比如要调另外一个接口查询,这时候 可以用到 @ResolveProperty
1 | import { Args, Resolver, ResolveProperty } from '@nestjs/graphql' |
但是别忘了 在 model
里面加上 age
字段
1 | import { Field, ID } from 'type-graphql' |
这样查询的时候 Resolver
会帮你合并在一起
1 | query users { |
1 | { |
DateLoader
由于 Resolver
的 N+1 查询问题
像上面 this.userService.getAge()
, 会执行多次,如果是 执行一些 sql
可能会有性能问题,和资源浪费,但是问题不大,
我们用 dataloader
来解决这个问题
1 | import DataLoader from 'dataloader'; |
原理大概就是 把当前 event loop 的 请求 放在 process.nextTick
去执行
Docker 部署
由于 docker
里面没有写入文件的权限,这样会带来一个问题,由于启动应用的时候
1 |
|
会自动生成 schema
文件,也就是 fs.writeFile
这样会导致 docker
启动不了,所以需要小小修改下 GraphqlModule
的配置
- 方法 1 :
1 | import { Module } from '@nestjs/common' |
在development
的时候 会生成 schema.gql
, 在 production
环境下 关闭自动生成
同时指定 typePaths
为 schema.gql
这样既可解决
- 方法 2 :
1 |
|
首先 使用 type-graphql
提供的 buildSchema
事实上 nest.js
的 GraphqlModule
也是使用的这个方法帮你自动生成的
1 | import { buildSchema } from 'type-graphql'; |
在每次 构建镜像的时候 将这个文件 copy 进去既可
权限验证
在 express
中 可以通过 中间键 拦截 request
来做权限验证,在 nest.js
中 可以很方便的 使用 Guards
实现
1 | import { Args, Resolver, ResolveProperty } from '@nestjs/graphql' |
由于 Graphql
有一个 context
的概念 可以通过 context
拿到 当前的 request
1 | // auth.guard.ts |
转换 error response
由于使用的事 apollo-server
, 在每次 Query
或 Mutation
报错时,发送到前端的 错误
层级会很深,
如果想自定义可以使用 formatError
和 formatResponse
, 但由于 这两个字段 nest.js
并没有提供 相应详细的定义
可能要去看下 apollo-server
的文档才行,尽管 TMD 文档只有几行
1 | import { Module } from '@nestjs/common' |
测试
你可能想写一点 单元测试
或者 e2e 测试
, 文档都有,这里就不当搬运工了
最后
当然,踩坑的心酸 远不止 这一点点文字,这一次也是收获颇多,继续加油