- Nestjs - 基于 Express socket.io 封装的 nodejs 框架
- 亮点: Modules, Controllers, Components
- Modules 最大颗粒度的拆分,表示应用或模块
- Controllers 传统意义的控制器,一个 Modules 拥有多个 Controller
- Components 用于做 Services, 比如将数据库 CURD 封装在 Services 中,每个 Service 就是一个 Component
-
路由放置函数头上,做到路由去中心化
@Controller() export class UsersController { @Get('users') getAllUsers() {} @Get('users/:id') getUser() {} @Post('users') addUser() {} }
Modules, Controllers, Components 之间通过依赖注入相互关联, 通过装饰器声明
@Component() export class UserService { getAllUsers() { return [] } } @Module({ controllers: [UsersController], components: [UserService] }) export class ApplicationModule {}
-
在
AppliactionModule
申明其内部 Controllers 和 Components 后,就可以在 Controller 中注入 Components 了@Controller export class UsersController { constructor(private userService: UserService) {} @Get('users') getAllUsers() { return this.userService.getAllUsers() } }
-
NestJs 通过装饰器获取请求参数
@Get('/:id') public async getUser( @Response() res, @Param('id') id, ) { const user = await this.userService.getUser(id); res.status(HTTPStatus.OK).json(user) } // @Response 获取res @Param获取路由参数, @Query获取 url query参数, @Body 获取HTTP body参数
-
-
-
定义实体(每个实体对应数据库一张表,Typeorm 每次启动都会同步表结构到数据库,完全不用食用数据库查看表结构, 所有结构定义全在代码中)
@Entity() export class Card { @PrimaryGeneratedColumn({ // 定义主键列 comment: '主键', }) id: number, // 自动识别id为数字类型 @Column({ // 列定义唯一信息长度等 comment: '名称', length: 30, unique: true }) name: string = 'nick' // 自动识别name为string类型,且赋初始值nick }
- 通过@Entity 将类定义为实体
-
只有判断参数类型不够,还可以用
class-validator
做所有形式的校验@column({ comment: '配置JSON', length: 5000, }) @Validator.IsString({message: '必须为字符串'}) @Validator.length(0, 5000, {message: '长度在0~5000'}) content: string;
-
新增实体时,需要校验所有字段,但是更新实体,由于性能需要不会一次查询所有字段,指定更新时,不校验没有赋值的字段,通过 Typeorm 的
EventSubscriber
完成数据库操作前的代码校验。控制新增时的全字段校验,删除不做校验@EventSubcriber() export class EverythingSubcriber implement EntitySubscriberInterface<any> { async beforeInsert(event: InsertEvent<any>) { const validateErrors = await validate(event.entity); if(validateErrors.length > 0) { throw new HttpException(getErrorMessage(validateErrors), 404); } } async beforeUpdate(event: UpdateEvent<any>) { const validateErrors = await validate(event.entity, { skipMissingProperties: true, }); if(validateErrors.length > 0) 【 throw new HttpException(getErrorMessage(validateErrors), 404) } }
- HttpException 校验失败会终止执行,立即返回服务给客户端
const card = await this.cardService.add(name, description);
// 如果传入参数实体校验失败,会立刻返回失败,并提示 `@Validator.IsString({ message: '必须为字符串' })` 注册时的提示信息
// 如果插入失败,也会立刻返回失败
// 所以只需要处理正确情况
res.status(HttpStatus.OK).json(card);
- @OneToOne @OneToMany @ManyToOne @ManyToMany
@Entity()
export class Comment {
@PrimaryGeneratedColumn({
comment: '主键',
})
id: number;
@ManyToOne(type => User, user => Comments) // 关联关系
@JoinColumn()
user: User;
}
- 使用 typeorm 查询 User 时,自动外键查询到关联的评论, 保存在 user.comments 中, 查询 Comment 时,会自动查询到关联的 User,保存在 comment.user 中
- 使用 Docker 部署 Mysql + NodeJs,通过 docker-compose 将数据库和服务跑在 docker 上,内部通信
- nodejs 服务运行时,要等待数据库服务启动完毕,有一个启动等待的需求,通过
environment
拓展功能 docker-compose.yml
version: "2"
services:
app:
build: ./
restart: always
ports:
- "5000:8000"
links:
- db
- redis
depends_on:
- db
- redis
environment:
WAIT_HOSTS: db:3306 redis:6379
- nodejs 中间件实现的很精妙, 与 Modules 完美结合
- -如果前端可以设计 ie11,推荐纯 Proxy 实现的 dob, 配合 react 效率高