CRUD 生成器(仅 TypeScript)
在一个项目的生命周期中,当我们构建新功能时,通常都需要为应用添加新的资源。这些资源往往需要多项重复操作,而每当我们定义一个新资源时,都得把这些步骤再做一遍。
介绍
让我们设想一个真实场景:我们需要为两个实体暴露 CRUD 端点,比如 User 和 Product 实体。 遵循最佳实践的话,对于每个实体,我们通常都要执行如下几项操作:
- 生成模块(
nest g mo),以保持代码组织清晰并建立明确边界(对相关组件进行分组) - 生成控制器(
nest g co),以定义 CRUD 路由(或 GraphQL 应用中的查询/变更) - 生成服务(
nest g s),以实现并隔离业务逻辑 - 生成实体类/接口,用于表示资源的数据结构
- 生成数据传输对象(DTO,或 GraphQL 应用中的 input),用于定义数据如何通过网络传输
步骤相当多。
为了加快这一重复过程,Nest CLI 提供了一个生成器(schematic),它可以自动生成所有样板代码,帮助我们避免手动完成这些重复工作,并让开发体验简单得多。
注意
该 schematic 支持生成 HTTP 控制器、微服务控制器、GraphQL 解析器(包括 code first 与 schema first),以及 WebSocket 网关。
生成新资源
要创建一个新资源,只需在项目根目录下运行以下命令:
$ nest g resourcenest g resource 命令不仅会生成所有 NestJS 基础构件(模块、服务、控制器类),还会生成实体类、DTO 类以及测试(.spec)文件。
下面你可以看到生成后的控制器文件(以 REST API 为例):
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}除此之外,它还会自动为所有 CRUD 端点创建占位实现(对于 REST API 是 routes,对于 GraphQL 是 queries 和 mutations,对于微服务和 WebSocket 网关则是消息订阅)而这一切都不需要你手动处理。
注意
生成出的服务类不会绑定到任何特定的 ORM(或数据源)。这使得该生成器足够通用,能够适配各种项目需求。默认情况下,所有方法都只包含占位实现,方便你根据项目所使用的数据源自行补充逻辑。
同样地,如果你想为 GraphQL 应用生成解析器,只需将传输层选择为 GraphQL (code first)(或者 GraphQL (schema first))。
这种情况下,NestJS 将生成一个 resolver 类,而不是 REST API 控制器:
$ nest g resource users
> ? What transport layer do you use? GraphQL (code first)
> ? Would you like to generate CRUD entry points? Yes
> CREATE src/users/users.module.ts (224 bytes)
> CREATE src/users/users.resolver.spec.ts (525 bytes)
> CREATE src/users/users.resolver.ts (1109 bytes)
> CREATE src/users/users.service.spec.ts (453 bytes)
> CREATE src/users/users.service.ts (625 bytes)
> CREATE src/users/dto/create-user.input.ts (195 bytes)
> CREATE src/users/dto/update-user.input.ts (281 bytes)
> CREATE src/users/entities/user.entity.ts (187 bytes)
> UPDATE src/app.module.ts (312 bytes)提示
如果你不想生成测试文件,可以传入 --no-spec 标志,例如:nest g resource users --no-spec
从下面可以看到,不仅所有样板 mutation 和 query 都被创建好了,而且所有部分也都已经彼此连接起来。这里使用了 UsersService、User 实体以及 DTO。
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { CreateUserInput } from './dto/create-user.input';
import { UpdateUserInput } from './dto/update-user.input';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
@Mutation(() => User)
createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
return this.usersService.create(createUserInput);
}
@Query(() => [User], { name: 'users' })
findAll() {
return this.usersService.findAll();
}
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.usersService.findOne(id);
}
@Mutation(() => User)
updateUser(@Args('updateUserInput') updateUserInput: UpdateUserInput) {
return this.usersService.update(updateUserInput.id, updateUserInput);
}
@Mutation(() => User)
removeUser(@Args('id', { type: () => Int }) id: number) {
return this.usersService.remove(id);
}
}