• 视频地址: https://www.bilibili.com/vide...

学习指标

  • 简略地整合nestjs框架与[typeorm][]
  • 实现根本的CRUD数据操作
  • 应用[class-validator][]验证申请数据
  • 更换更加疾速的[fastify][]适配器
  • 应用Thunder Client对测试接口

装置Mysql

理论生产环境中倡议应用PostgreSQL,因为教程以学习为主,所以间接应用相对来说比拟通用和简略的Mysql

应用以下命令装置Mysql

如果本机不是应用linux(比方应用wsl2),请到mysql官网点击download按钮下载安装包后在chrome查看下载地址,而后在开发机用wget下载

如果本机应用MacOS,应用brew install mysql,如果本机应用Arch系列,应用sudo pacman -Syy mysql

# 下载镜像包cd /usr/local/srcsudo wget sudo wget https://repo.mysql.com/mysql-apt-config_0.8.22-1_all.deb# 增加镜像(其它选项不必管,间接OK就能够)sudo apt-get install ./mysql-apt-config_0.8.22-1_all.deb# 升级包列表sudo apt-get update# 开始装置,输出明码后,有一个明码验证形式,因为是开发用,所以抉择第二个弱验证即可sudo apt-get install mysql-server # 初始化,在是否加载验证组件时抉择No,在是否禁用近程登录时也抉择Nosudo mysql_secure_installation# 因为是近程SSH连贯开发所以须要开启近程数据库链接,如果是本地或者wsl2则不须要开启mysql -u root -p CREATE USER 'root'@'%' IDENTIFIED BY '明码';GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;FLUSH PRIVILEGES;

接着应用Navicat等客户端就能够连贯了

预装依赖

  • [lodash][]是罕用的工具库
  • [cross-env][]用于跨平台设置环境变量
  • [class-transformer][]用于对申请和返回等数据进行序列化
  • [class-validator][]用于验证申请dto
  • [typeorm][]一个TS编写的[node.js][]ORM
  • [@nestjs/typeorm][]Nestjs的TypeOrm整合模块
  • [@nestjs/platform-fastify][]Fastify适配器,用于代替express
  • [nestjs-swagger][]生成open api文档,目前咱们应用其PartialType函数是UpdateDto中的属性可选
  • [fastify-swagger][]生成Fastify的Open API
~ pnpm add class-transformer \  @nestjs/platform-fastify \  class-validator \  lodash \  @nestjs/swagger \  fastify-swagger \  mysql2 \  typeorm \  @nestjs/typeorm ~ pnpm add @types/lodash cross-env @types/node typescript -D

生命周期

要正当的编写利用必须当时理解分明整个程序的拜访流程,本教程会解说如何一步步演进每一次拜访流,作为第一步课时,咱们的拜访流非常简单,能够参考下图

文件构造

咱们通过整合[typeorm][]来连贯mysql实现一个根本的CRUD利用,首先咱们须要创立一下文件构造

倡议初学者手动创立,没必要应用CLI去创立,这样目录和文件更加清晰
  1. 创立模块
  2. 编写模型
  3. 编写Repository(如果有需要的话)
  4. 编写数据验证的DTO
  5. 编写服务
  6. 编写控制器
  7. 在每个以上代码各自的目录下建设一个index.ts并导出它们
  8. 在各自的Module里进行注册提供者,导出等
  9. AppModule中导入这两个模块

编写好之后的目录构造如下

.├── app.module.ts                           # 疏导模块           ├── config                                  # 配置文件目录│   ├── database.config.ts                  # 数据库配置│   └── index.ts├── main.ts                                 # 利用启动器├── modules    ├── content                             # 内容模块目录    │   ├── content.module.ts               # 内容模块    │   ├── controllers                     # 控制器    │   ├── dtos                            # DTO拜访数据验证    │   ├── entities                        # 数据实体模型    |   ├── index.ts                  │   ├── repositories                    # 自定义Repository    │   ├── services                        # 服务    └──  core        ├── constants.ts                    # 常量        ├── core.module.ts                  # 外围模块        ├── decorators                      # 装璜器        └── types.ts                        # 公共类型

利用编码

在开始编码之前须要先更改一下package.jsonnestjs-cli.json两个文件

package.json中批改一下启动命令,以便每次启动能够主动配置运行环境并兼容windows环境

"prebuild": "cross-env rimraf dist","start": "cross-env NODE_ENV=development nest start","start:dev": "cross-env NODE_ENV=development nest start --watch","start:debug": "cross-env NODE_ENV=development nest start --debug --watch","start:prod": "cross-env NODE_ENV=production node dist/main",

为了在每次从新编译前主动删除上次的产出,在nestjs-cli.json中配置 "deleteOutDir": true

main.ts

把适配器由[express][]换成更快的[fastify][],并把监听的IP改成0.0.0.0不便内部拜访.为了在应用[class-validator][]的DTO类中也能够注入nestjs容器的依赖,须要增加useContainer

// main.tsimport { NestFactory } from '@nestjs/core';import {  FastifyAdapter,  NestFastifyApplication,} from '@nestjs/platform-fastify';import { useContainer } from 'class-validator';import { AppModule } from './app.module';async function bootstrap() {  const app = await NestFactory.create<NestFastifyApplication>(    AppModule,    new FastifyAdapter()  );  useContainer(app.select(AppModule), { fallbackOnErrors: true });  await app.listen(3000,'0.0.0.0');}bootstrap();

连贯配置

创立一个src/config/database.config.ts文件

export const database: () => TypeOrmModuleOptions = () => ({    // ...    // 此处entites设置为空即可,咱们间接通过在模块外部应用`forFeature`来注册模型    // 后续魔改框架的时候,咱们会通过自定义的模块创立函数来重置entities,以便给本人编写的CLI应用    // 所以这个配置前面会删除    entities: [],     // 主动加载模块中注册的entity    autoLoadEntities: true,    // 能够在开发环境下同步entity的数据结构到数据库    // 前面教程会应用自定义的迁徙命令来代替,以便在生产环境中应用,所以当前这个选项会永恒false    synchronize: process.env.NODE_ENV !== 'production',});

CoreModule

外围模块用于挂载一些全局类服务,比方整合[typeorm][]的`TypeormModule

留神: 这里不要应用@Global()装璜器来构建全局模块,因为前面在CoreModule类中增加一些其它办法

返回值中增加global: true来注册全局模块,并导出metadata.

// src/core/core.module.tsexport class CoreModule {    public static forRoot(options?: TypeOrmModuleOptions) {        const imports: ModuleMetadata['imports'] = [TypeOrmModule.forRoot(options)];        return {            global: true,            imports,            module: CoreModule,        };    }}

AppModule导入该模块,并注册数据库连贯

// src/app.module.ts@Module({    imports: [CoreModule.forRoot(database())],  ...})export class AppModule {}

自定义存储类

因为原来用于自定义Repository的@EntityRepository在typeorm0.3版本后曾经不可用,特地不不便,所以依据这里的示例来自定义一个CustomRepository装璜器

// src/modules/core/constants.ts// 传入装璜器的metadata数据标识export const CUSTOM_REPOSITORY_METADATA = 'CUSTOM_REPOSITORY_METADATA';// src/modules/core/decorators/repository.decorator.ts// 定义装璜器import { CUSTOM_REPOSITORY_METADATA } from '../constants';export const CustomRepository = <T>(entity: ObjectType<T>): ClassDecorator =>    SetMetadata(CUSTOM_REPOSITORY_METADATA, entity);// src/modules/core/decorators/index.tsexport * from './repository.decorator';

定义静态方法用于注册自定义Repository

 public static forRepository<T extends Type<any>>(        repositories: T[],        dataSourceName?: string,    ): DynamicModule {        const providers: Provider[] = [];        for (const Repo of repositories) {            const entity = Reflect.getMetadata(CUSTOM_REPOSITORY_METADATA, Repo);            if (!entity) {                continue;            }            providers.push({                inject: [getDataSourceToken(dataSourceName)],                provide: Repo,                useFactory: (dataSource: DataSource): typeof Repo => {                    const base = dataSource.getRepository<ObjectType<any>>(entity);                    return new Repo(base.target, base.manager, base.queryRunner);                },            });        }        return {            exports: providers,            module: CoreModule,            providers,        };    }

ContentModule

内容模块用于寄存CRUD操作的逻辑代码

// src/modules/content/content.module.ts@Module({})export class ContentModule {}

AppModule中注册

// src/app.module.ts@Module({    imports: [CoreModule.forRoot(database()),ContentModule],  ...})export class AppModule {}

实体模型

创立一个PostEntity用于文章数据表

PostEntity继承`BaseEntity,这样做是为了咱们能够进行ActiveRecord操作,例如PostEntity.save(post),因为纯DataMapper的形式有时候代码会显得啰嗦,具体请查看此处

@CreateDateColumn @UpdateDateColumn是主动字段,会依据创立和更新数据的工夫主动产生,写入后不用关注

// src/modules/content/entities/post.entity.ts// 'content_posts'是表名称@Entity('content_posts')export class PostEntity extends BaseEntity {...    @CreateDateColumn({        comment: '创立工夫',    })    createdAt!: Date;    @UpdateDateColumn({        comment: '更新工夫',    })    updatedAt!: Date;}

存储类

本节存储类是一个空类,前面会增加各种操作方法

这里用到咱们后面定义的自定义CustomRepository装璜器
// src/modules/content/repositories/post.repository.ts@CustomRepository(PostEntity)export class PostRepository extends Repository<PostEntity> {}

注册模型和存储类

在编写好entityrepository之后咱们还须要通过Typeorm.forFeature这个静态方法进行注册,并把存储类导出为提供者以便在其它模块注入

// src/modules/content/content.module.ts@Module({    imports: [        TypeOrmModule.forFeature([PostEntity]),        // 注册自定义Repository        CoreModule.forRepository([PostRepository]),    ],     exports: [        // 导出自定义Repository,以供其它模块应用        CoreModule.forRepository([PostRepository]),    ],})export class ContentModule {}

DTO验证

DTO配合管道(PIPE)用于控制器的数据验证,验证器则应用[class-validator][]

class-validator是基于validator.js的封装,所以一些规定能够通过validator.js的文档查找,前面教程中咱们会编写大量的自定义的验证规定,这节先尝试根本的用法

其根本的应用办法就是给DTO类的属性增加一个验证装璜器,如下

groups选项用于配置验证组
// src/modules/content/dtos/create-post.dto.ts@Injectable()export class CreatePostDto {    @MaxLength(255, {        always: true,        message: '文章题目长度最大为$constraint1',    })    @IsNotEmpty({ groups: ['create'], message: '文章题目必须填写' })    @IsOptional({ groups: ['update'] })    title!: string;    ...}

更新验证类UpdatePostDto继承自CreatePostDto,为了使CreatePostDto中的属性变成可选,须要应用[@nestjs/swagger][]包中的PartialType办法,请查阅此处文档

// src/modules/content/dtos/update-post.dto.ts@Injectable()export class UpdatePostDto extends PartialType(CreatePostDto) {    @IsUUID(undefined, { groups: ['update'], message: '文章ID格局谬误' })    @IsDefined({ groups: ['update'], message: '文章ID必须指定' })    id!: string;}

服务类

服务一共包含5个简略的办法,通过调用PostRepository来操作数据

// src/modules/content/services/post.service.ts@Injectable()export class PostService {    // 此处须要注入`PostRepository`的依赖    constructor(private postRepository: PostRepository) {}    // 查问文章列表    async findList()     // 查问一篇文章的详细信息    async findOne(id: string)    // 增加文章    async create(data: CreatePostDto)    // 更新文章    async update(data: UpdatePostDto)    // 删除文章    async delete(id: string)}

控制器

控制器的办法通过@GET,@POST,@PUT,@PATCH,@Delete等装璜器对外提供接口,并且通过注入PostService服务来操作数据.在控制器的办法上应用框架自带的ValidationPipe管道来验证申请中的body数据,ParseUUIDPipe来验证params数据

// 控制器URL的前缀@Controller('posts')export class PostController {    constructor(protected postService: PostService) {}    ...   // 其它办法请自行查看源码    @Get(':post')    async show(@Param('post', new ParseUUIDPipe()) post: string) {        return this.postService.findOne(post);    }    @Post()    async store(        @Body(            new ValidationPipe({                transform: true,                forbidUnknownValues: true,                // 不在谬误中裸露target                validationError: { target: false },                groups: ['create'],            }),        )        data: CreatePostDto,    ) {        return this.postService.create(data);    }}

注册控制器等

  • 为了前面`DTO中可能会导入服务,须要把DTO,同样注册为提供者并且革新一下main.ts,把容器退出到class-containter
  • PostService服务可能后续会被UserModule等其它模块应用,所以此处咱们也间接导出
// src/modules/content/content.module.ts@Module({    imports: [        TypeOrmModule.forFeature([PostEntity]),        // 注册自定义Repository        CoreModule.forRepository([PostRepository]),    ],    providers: [PostService, CreatePostDto, UpdatePostDto],    controllers: [PostController],    exports: [        PostService,        // 导出自定义Repository,以供其它模块应用        CoreModule.forRepository([PostRepository]),    ],})export class ContentModule {}
// src/main.ts...async function bootstrap() {    const app = await NestFactory.create<NestFastifyApplication>(        AppModule,        new FastifyAdapter(),    );    useContainer(app.select(AppModule), { fallbackOnErrors: true });    await app.listen(3000, '0.0.0.0');}

最初启动利用在Thunder Client中测试接口