新项目想要找个符合口味的开发技术栈,当时发了条Tweet

想要找个设计良好、社区强健的Web后端语言意外地不容易 Elixir - 社区似乎比较小,动态类型 Python - 语言不怎么样,异步生态一般般,还行吧 Rust - 语言设计好,异步生态目前很不稳定,等个半年? Typescript - 目前最优选择,不过是建立在 JS 之上 Kotlin / Swift - 不太了解,正在了解

除了这些,评论区有人推荐Scala。

最后选择了Typescript,最大的原因是自己的前端代码都是用TS写的因此会对TS比较熟悉,其他理由有「有魔性的类型系统,有毁誉参半的JS生态,和Rust桥接也比较容易」。

Typescript上成熟的Web框架是NestJS,ORM是TypeORM。现在记录一下遇到的问题以及需要注意的地方。

NestJS实际上是一个大型胶合层,把一些成熟的模块包装成一个整体了。所以用的时候NestJS本身的那点文档是不够看的,需要去了解背后的库以及胶合层对库做的抽象。组合起来的库会引发更多更多的坑,现在已经大致把坑都踩平了,于是这篇文章做个记录。

NestJS提供了CLI工具生成各种各样的样板代码,自动生成的项目模版其实不是真正的TS,各种类型检查都没开启,需要手动开启:

{
  "strictNullChecks": true,
  "noImplicitReturns": true,
  "noImplicitThis": true,
  "noImplicitAny": true,
  "strict": true
}

昨天才发现漏了strict,一开起来导致一片报错。

虽然NestJS的大多数特性是为了RESTFul设计的,但GraphQL和NestJS搭配起来还是挺不错的。搭配使用的时候有两种模式,一种是手动写schema并且在代码上加上相应钩子,另一种是依靠代码上的钩子(装饰器)全自动生成schema,为了减少重复劳动当然是选择后者啦。

这个功能是靠TypeGraphQL实现的。这里面最大的坑其实是TypeGraphQL自身的很多东西都被NestJS的胶合层(@nestjs/graphql)重新包装了一次,如果你在该使用NestJS胶合层中的类型的时候使用了未经包装的TypeGraphQL类型,那么跑起来不会报错,而是默默给出怪异的行为。使用的时候为了避免这种情况一定要注意首先从 @nestjs/graphql 里面导入,如果没有再从 type-graphql 导入。特别要注意的是要用 FieldResolver 而是用 ResolveProperty

接下来是TypeORM的坑。

一个entity的类型定义是这样的:


@Entity('channels')
@ObjectType('Channel')
export class Channel {
  @PrimaryColumn({ type: 'uuid' })
  @Field(() => ID)
  id!: string;

  @Column({ type: 'enum', enum: ChannelType, default: ChannelType.Discuss })
  @Field(() => ChannelType)
  type!: ChannelType;

  @Column({ type: 'boolean', default: false })
  @Field()
  isPublic!: boolean;

  @Column()
  @Index({ unique: true })
  @Field()
  name!: string;
}

其中 @ObjectType@Field 是TypeGraphQL的设施,给entity加上去以后就能自动产出GraphQL类型定义:

type Channel {
  id: ID!
  type: ChannelType!
  isPublic: Boolean!
  name: String!
}

需要注意的是field后面跟着的 ! 是断言这个字段不会为空的意思,如果不加它又不写构造器初始化这些字段的话,开了strict的Typescirpt会报错。

TypeORM使用起来还是不错的,除了一个最大的坑:.save()