前言

进行spring开发时,基于注解的AOPDI的编程风格以及ORM框架的良好支持使得编程层次分明,十分顺滑,但Node中,常规的使用koaexpress开发时总要一堆路由函数,且控制层与业务层无法分开,体验不是那么好,而nest提供了基于装饰器的依赖注入以及面向切面的编程风格,typeorm实现了node上的orm框架,并且支持MysqlPostgresSQL Server等等众多数据库,两者结合,可以实现基于装饰器的类似spring的开发体验,并且nest提供了对typeorm的内置支持。

创建Nest工程

当然,第一步首先是要创建一个nest工程,nesttypeorem主要是基于ts的,因此首先需安装ts模块:

yarn global add typescript

然后安装nest脚手架:

yarn global add @nestjs/cli

利用脚手架创建nest工程:

nest new NestWithOrm

开始会让选择使用npm还是yarn,根据个人喜好选择即可,完成后,会生成一个nest-with-orm工程(大写自动转成了中划线),将其重命名成NestWithOrm,然后进入NestWithOrm文件夹,执行运行命令即可:

yarn run start

打开浏览器,访问3000端口,即可看到HelloWord输出。观察工程结构,所有有效代码均放在了src目录中,其中main.ts是入口文件,我们先删除除app.service.ts以及app.controller.ts文件,我们要自己组织目录结构并编写控制器等。

组织Nest结构

一般mvc式风格分service层、entity层、controller层等,我们也以类似的结构组织工程,在src下新建entitycontrollerserviceprovider几个文件夹,src的目录结构将如下所示:

src
|----entity   # 放置实体类
|----controller  # 放置控制层代码
|----service  # 放置服务层代码
|----module # 放置模块代码
|----app.module.ts  # 提供可注入对象以及控制器注册
`----main.ts  # 入口函数

集成TypeOrm

安装依赖

# nest模块支持
yarn add --save @nestjs/typeorm
# typeorm核心模块
yarn add typeorm --save
# 为typeorm的装饰器提供元数据添加支持
yarn add reflect-metadata --save
# 提供类型定义支持
yarn add install @types/node --save
# 提供数据库驱动,使用哪种数据库,则相应安装哪种数据库的驱动
yarn add mysql2 --save

定义实体类

src/entity下新建一个User.ts,编写一个实体类,如下:

// User.ts
import {Entity, Column, PrimaryGeneratedColumn} from 'typeorm'

@Entity(“user”)
export class User {
    @PrimaryGeneratedColumn() id: number //主键,自增
    @Column() name: string
    @Column() password: string
}

定义服务基类

定义一个服务基类,暴露基本的CURD方法,各实体对应的服务类直接继承即可,这样基本方法每个服务类就不用再写一遍了。

src/service目录下新建BaseService.ts文件,内容如下:

// BaseService.ts
import { Repository, DeleteResult, SaveOptions, FindConditions, RemoveOptions } from "typeorm";
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
import { Injectable } from "@nestjs/common";

/**
 * 服务基类,实现一些共有的基本方法,这样就不用每个服务类在写一遍了,直接继承该类即可
 */
@Injectable() export class BaseService<T> {
    protected readonly repository: Repository<T>;
    constructor(repository: Repository<T>) {
        this.repository = repository;
    }

    async saveOne(entity: T, options?: SaveOptions): Promise<T> {
        return this.repository.save(entity, options);
    }

    async saveMany(entities: T[], options?: SaveOptions): Promise<T[]> {
        return this.repository.save(entities, options);
    }

    async findOne(options?: FindConditions<T>): Promise<T> {
        return this.repository.findOne(options);
    }

    async findMany(options?: FindConditions<T>): Promise<T[]> {
        return this.repository.find(options);
    }

    async findAll(): Promise<T[]> {
        return this.repository.find();
    }

    async removeOne(entity: T, options?: RemoveOptions): Promise<T> {
        return this.repository.remove(entity, options);
    }

    async removeMany(entities: T[], options?: RemoveOptions): Promise<T[]> {
        return this.repository.remove(entities, options);
    }

    async delete(options?: FindConditions<T>): Promise<DeleteResult> {
        return this.repository.delete(options);
    }

    async update(conditions: number | FindConditions<T>, newValue: QueryDeepPartialEntity<T>): Promise<number> {
        let updateResult = 1;
        await this.repository.update(conditions, newValue).catch(e => updateResult = 0);
        return updateResult;
    }
}

定义实体对应的服务类

src/service目录下新建UserService.ts文件,内容如下:

// UserService.ts
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

import { BaseService } from './BaseService';

@Injectable()
export class UserService extends BaseService<User> {
    constructor(
        @InjectRepository(User) private readonly userRep: Repository<User>
    ) {
        super(userRep);
    }
}

定义controller

src/controller目录下新建UserController.ts文件,定义控制层内容,如下:

import { Controller, Get, Param } from '@nestjs/common';
import { UserService } from '../service/UserService';

@Controller("user")
export class AppController {
    //通过构造函数注入service
    constructor(
        private readonly userService: UserService
    ){}
    
    //定义一个路由
    @Get("/")
    async getUser(): Promise<User> {
        let user = await this.userRep.save({name: 'user1', password: 'pass1'});
        return await this.userService.findOne({name: 'user1'});
    }
}

定义服务模块

上边定义了实体与实体对应的服务类,要把它们关联起来,需要定义一个模块,在src/module下新建ServiceModule.ts,如下:

import {Module} from '@nestjs/common';
import {TypeOrmModule} from '@nestjs/typeorm';

import {User} from 'src/entity/User';
import {UserService} from 'src/service/UserService';

@Module({
    imports: [
        // 配置数据源
        TypeOrmModule.forRoot({
            type: 'mysql',
            host: 'localhost',
            port: 3306,
            username: 'root',
            password: 'root',
            database: 'test',
            autoLoadEntities: true,
            synchronize: false
        }),
        // 引入实体
        TypeOrmModule.forFeature([
            User,
        ]),
    ],
    // 服务类作为提供者
    providers: [
        UserService,
    ],
    // 导出以可被其它模块使用
    exports: [
        UserService,
    ]
})

export class ServiceModule {}

将服务模块注册到根模块

最后,将服务模块以及控制器注册到根模块,如下:

import { Module } from '@nestjs/common'
import { UserController } from './controller/UserController'
import { ServiceModule } from './module/ServiceModule';

@Module({
  imports: [ServiceModule], // 引入DAO层模块
  controllers: [UserController], //注册所有控制器
})
export class AppModule {}

引入reflect-metadata

最后,typeorm的装饰器需要reflect-metadata模块的支持,我们需要在入口函数main.ts中显式引入一下该模块。且要放在引入的第一行,src/main.ts入口函数将如下所示:

import "reflect-metadata" //显示引入一下
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  await app.listen(3000)
}
bootstrap()

最后

通过上述步骤,nesttypeorm已经集成完毕,启动工程:

yarn run start

然后浏览器访问localhost:3000/user,将会看到以下输出:

{
    "id": 1,
    "name": "user1",
    "password": "pass1"
}