在Nest.js项目的整个生命周期中,当我们构建新功能时,我们经常需要向我们的应用程序添加新资源。这个时候第一反应是使用生成器直接开始逐个输入指令,手动创建DTO、实体等等,是不是有点烦?(快说是!)
想象一个场景
我们需要为 2 个实体创建 CRUD 接口,比如说用户和产品实体。按照最佳实践,对于每个实体,我们必须执行几个操作,如下所示:
- 生成一个模块 (
nest g mo
) 以保持代码有条理并建立清晰的边界(对相关组件进行分组) - 生成一个控制器 (
nest g co
) 来定义 CRUD 路由(或 GraphQL 应用程序的查询/突变) - 生成一个服务(
nest g s
)来实现和隔离业务逻辑 - 生成一个实体类/接口来表示资源数据形状
- 生成数据传输对象(或 GraphQL 应用程序的输入)以定义如何通过网络发送数据
这么多步骤!为了帮助加快这个重复过程,Nest CLI提供了一个生成器resource
,它会自动生成所有样板代码,以帮助我们避免这些重复操作。
Tips
支持生成的控制器包括:
- HTTP
- Microservice 微服务
- GraphQL 解析器(代码优先和模式优先)
- WebSocket 网关
快速生成新资源 nest g resource
要创建新资源,只需在项目的根目录中运行以下命令:
nest g resource [名称]
nest 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 的路由、GraphQL 的查询和突变、微服务和 WebSocket 网关的消息订阅)创建占位符——所有这些都毫不费力。
Tips
生成的服务类(Service Classes)不绑定到任何特定的ORM(或数据源) 。这使得生成器足够通用,而且可以满足任何项目的需求。默认情况下,所有方法都将包含占位符,需要你根据项目用自己的数据源来填充它。
比如这样,还是很贴心的:
// userService.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UserService {
create(createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
findAll() {
return `This action returns all user`;
}
findOne(id: number) {
return `This action returns a #${id} user`;
}
update(id: number, updateUserDto: UpdateUserDto) {
return `This action updates a #${id} user`;
}
remove(id: number) {
return `This action removes a #${id} user`;
}
}
同样,如果你想为 GraphQL 应用程序生成解析器,只需选择GraphQL (code first)
(或GraphQL (schema first)
)作为您的传输层。
在这种情况下,NestJS 将生成一个解析器类而不是 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)
Tips:
使用
--no-spec
标志可以避免生成测试文件,比如nest g resource users --no-spec
我们可以在下面看到,不仅创建了所有样板突变和查询,而且所有内容都捆绑在一起。
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);
}
}
终于,我们不需要再一个个敲最佳实践的生成器指令来生成新的资源内容啦~当然,你还是需要手动编写实体、DTO的内容等并在entities:[]
中使用实体,或者直接这样写省事:
// app.module.ts
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
type: 'mysql', // 数据库类型
url: configService.get('DATABASE_URL'), // 数据库连接地址
timezone: '+08:00', //服务器上配置的时区
synchronize: configService.get('SYNC'), //根据实体自动创建数据库表, 生产环境建议关闭
autoLoadEntities: true, //自动加载实体文件 <<<<<<< 看这里!
// entities: [__dirname + '/**/*.entity{.ts,.js}'], // 也有这样写的
// entities: [xxx, xxxx....], // 反正不是这样一个个写的
}),
}),