nestjs使用Typeorm实现数据库CRUD操作

Posted 维思自动化

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nestjs使用Typeorm实现数据库CRUD操作相关的知识,希望对你有一定的参考价值。

本示例在nestjs脚手架项目基础上,进行了一些修改,并通过TypeOrm实现了数据库的增删读写操作。由于Typeorm更适合关系型数据库,本示例为简便起见,选择sqlite3作为试验数据库。对于非关系型数据库如mongodb,则推荐使用mongoose或者typegoose等库进行数据库操作。

1.nestjs安装和快速启动

nestjs使用Typeorm实现数据库CRUD操作


安装nestjs命令行工具


#安装cli工具
$ npm i-g @nestjs/cli 
#新建项目
$ nest new projectName
#如果用yarn安装的话
$ yarn global add @nestjs/cli


2.模版项目helloworld说明


不同版本的cli命令行生成的默认项目可能有所不同。这里将默认的helloworld修改为async异步形式来对nestjs方法进行说明。


2.1 app.controller.ts文件


import { Controller, Get, Inject } from '@nestjs/common';

import { AppService } from './app.service';

/**
 * 应用程序控制器,@Controller() 可以指定参数,用于定义类的父路由,如 @Controller("cat"),此时这个类的所有父路由就会成为 /cat
 *
 * 被 @Controller() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块
 */
@Controller()
export class AppController {

    /**
     * 构造函数,用于注入这个类的依赖,注入类时,需要使用 @Inject() 修饰符,其参数是被注入的类的类名
 
     * 在注入被 @Injectable() 修饰的类时,可以不使用 @Inject() 修饰参数,此时依赖注器入会使用参数的类型完成注入
     *
     * Tips: 这里使用 @Inject(AppService) 是为了规范代码风格
     */
    constructor(
        @Inject(AppService) private readonly appService: AppService,
    ) { }

    /**
     * @Get() 可以指定参数,用于定义方法路由,如 @Get(":id"),此时这个方法路由就会成为 /:id,即查询指定ID
     */
    @Get()
    async root() {
        return this.appService.root();
    }
}


2.2 app.service.ts文件


nestjs使用Typeorm实现数据库CRUD操作


import { Injectable } from '@nestjs/common';

/**
 * 被 @Injectable() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块
 */
@Injectable()
export class AppService {
    constructor() { } // 构造函数,一般用于处理依赖注入

    async root() {
        return 'Hello World!';
    }
}


2.3 app.module.ts文件


import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatModule } from 'cats/cat.module';
import { ErrorsInterceptor } from 'common/errors.interceptor';

import { AppController } from './app.controller';
import { AppService } from './app.service';

/**
 * @Module() 定义一个模块,并管理这个模块的导入集合、控制器集合、提供者集合、导出集合
 */
@Module({
    // TypeOrmModule.forRoot() 默认加载项目根目录下的 ormconfig.json 配置文件用于配置数据库连接
    // TypeORM 配置文件详细文档 https://typeorm.io/#/using-ormconfig
    imports: [TypeOrmModule.forRoot(), CatModule],  // 导入其他模块的集合
    controllers: [AppController],  // 当前模块的控制器集合
    providers: [
        {
            provide: APP_INTERCEPTOR,
            useClass: ErrorsInterceptor
        },
        AppService
    ],  // 当前模块的提供者集合
    exports: [], // 导出当前模块的提供者,用于被其他模块调用
})
export class AppModule { }


2.4 main.ts入口文件


import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);  // 创建应用程序实例,此时所有被 AppModule 导入的其他模块的所有实例都会被加载
    await app.listen(3000);  // 使用3000端口监听应用程序
}

bootstrap();  // 启动应用程序 -> localhost:3000


2.5 app.controller.spec.ts测试文件


修改了上述文件之后,命令行默认生成的测试文件也需要修改为异步的方式才能通过测试。


import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();

    appController = app.get<AppController>(AppController);
  });

  describe('get', () => {
    it('should return "Hello World!"', async() => {
      const data=await appController.root();
      expect(data).toBe('Hello World!');
    });
  });
});


3. 通用错误处理和接口文件


在项目实践中,注入错误处理、接口等模块化文件需要重复使用。最好集中在公用模块中。在项目根目录下创建common文件夹,并新建错误处理和接口模块。


nestjs使用Typeorm实现数据库CRUD操作

3.1 common/errors.interceptor.ts错误处理文件


import { CallHandler, ExecutionContext, HttpException, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        // 异常拦截器,拦截每个请求中的异常,目的是将异常码和异常信息改写为 { code: xxx, message: xxx } 类型
        return next.handle().pipe(catchError((error, caught): any => {
            if (error instanceof HttpException) {
                return Promise.resolve({
                    code: error.getStatus(),
                    message: error.getResponse()
                });
            }
            return Promise.resolve({
                code: 500,
                message: `出现了意外错误:${error.toString()}`
            });
        }));
    }
}


3.2 common/result.interface.ts接口文件


// 定义通用的API接口返回数据类型
export interface Result {
    code: number;
    message: string;
    data?: any;
}


4.创建数据库增删读写API文件


在根目录下新建cat文件夹,并依次创建下列文件。


4.1 cat.controller.ts


import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { Result } from 'common/result.interface';

import { Cat } from './cat.entity';
import { CatService } from './cat.service';

@Controller('cat')
export class CatController {
    constructor(
        @Inject(CatService) private readonly CatService: CatService,
    ) { }

    @Post()
    async createCat(@Body() Cat: Cat): Promise<Result> {
        await this.CatService.createCat(Cat);
        return { code: 200, message: '创建成功' };
    }

    @Delete(':id')
    async deleteCat(@Param('id') id: number): Promise<Result> {
        await this.CatService.deleteCat(id);
        return { code: 200, message: '删除成功' };
    }

    @Put(':id')
    async updateCat(@Param('id') id: number, @Body() Cat: Cat): Promise<Result> {
        await this.CatService.updateCat(id, Cat);
        return { code: 200, message: '更新成功' };
    }

    @Get(':id')
    async findOneCat(@Param('id') id: number): Promise<Result> {
        const data = await this.CatService.findOneCat(id);
        return { code: 200, message: '查询成功', data };
    }
}


4.2 cat.service.ts文件


import { HttpException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { Cat } from './cat.entity';

@Injectable()
export class CatService {
    constructor(
        @InjectRepository(Cat) private readonly catRepo: Repository<Cat>,  // 使用泛型注入对应类型的存储库实例
    ) { }

    /**
     * 创建
     *
     * @param cat Cat 实体对象
     */
    async createCat(cat: Cat): Promise<Cat> {
        /**
         * 创建新的实体实例,并将此对象的所有实体属性复制到新实体中。 请注意,它仅复制实体模型中存在的属性。
         */
        // this.catRepo.create(cat);

        // 插入数据时,删除 id,以避免请求体内传入 id
        delete cat.id;
        return this.catRepo.save(cat);

        /**
         * 将给定实体插入数据库。与save方法不同,执行原始操作时不包括级联,关系和其他操作。
         * 执行快速有效的INSERT操作。不检查数据库中是否存在实体,因此如果插入重复实体,本次操作将失败。
         */
        // await this.catRepo.insert(cat);
    }

    /**
     * 删除
     *
     * @param id ID
     */
    async deleteCat(id: number): Promise<void> {
        await this.findOneById(id);
        this.catRepo.delete(id);
    }

    /**
     * 更新
     *
     * @param id ID
     * @param cat Cat 实体对象
     */
    async updateCat(id: number, cat: Cat): Promise<void> {
        await this.findOneById(id);
        // 更新数据时,删除 id,以避免请求体内传入 id
        delete cat.id;
        this.catRepo.update(id, cat);
    }

    /**
     * 根据ID查询
     *
     * @param id ID
     */
    async findOneCat(id: number): Promise<Cat> {
        return this.findOneById(id);
    }

    /**
     * 根据ID查询单个信息,如果不存在则抛出404异常
     * @param id ID
     */
    private async findOneById(id: number): Promise<Cat> {
        const catInfo = await this.catRepo.findOne(id);
        if (!catInfo) {
            throw new HttpException(`指定 id=${id} 的猫猫不存在`, 404);
        }
        return catInfo;
    }
}


4.3 cat.entity.ts数据库实体文件


import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity('cat')
export class Cat {
    /**
     * 自增主键
     */
    @PrimaryGeneratedColumn({
        comment: '自增ID'
    })
    id: number;

    /**
     * 昵称
     */
    @Column({
        comment: '昵称'
    })
    nickname: string;

    /**
     * 品种
     */
    @Column({
        comment: '品种'
    })
    species: string;
}


4.4 cat.module.ts模块文件


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

import { CatController } from './cat.controller';
import { Cat } from './cat.entity';
import { CatService } from './cat.service';

@Module({
    imports: [TypeOrmModule.forFeature([Cat])],
    controllers: [CatController],
    providers: [CatService],
})
export class CatModule { }


5.配置数据库


typeorm可以和各种数据库配合使用。为了方便调试,这里用sqlite3替换掉原案例教程中的Postgresql(否则还要装一个Postgresql数据库)。

在项目中安装sqlite3驱动。


> npm i --save sqlite3


在根目录ormconfig.json文件中进行如下配置。注意,原案例中entities的目录在src/文件夹下面,实际使用时可能会报错(如果是直接运行TypeScript不会报错,如果编译成javascript就会报错,后面一种情况下需要修改目录到dist或者其他编译目标文件夹下)。


{
    "type": "sqlite",
    "database": "./mydb.sql",
    "entities": [
        "dist/**/**.entity{.ts,.js}"
    ],
    "synchronize": true,
    "logging": true
}


6.项目依赖包


项目中依赖的包文件如下,其中nestjs cli 已经安装好了一部分,typeorm等需要手动添加。


"dependencies": {
    "@nestjs/common": "^6.7.2",
    "@nestjs/core": "^6.7.2",
    "@nestjs/platform-express": "^6.7.2",
    "@nestjs/typeorm": "^6.2.0",
    "mongodb": "^3.4.1",
    "pg": "^7.16.0",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.0",
    "rxjs": "^6.5.3",
    "sqlite3": "^4.1.1",
    "typeorm": "^0.2.22"
  },
  "devDependencies": {
    "@nestjs/cli": "^6.9.0",
    "@nestjs/schematics": "^6.7.0",
    "@nestjs/testing": "^6.7.1",
    "@types/express": "^4.17.1",
    "@types/jest": "^24.0.18",
    "@types/node": "^12.7.5",
    "@types/supertest": "^2.0.8",
    "jest": "^24.9.0",
    "prettier": "^1.18.2",
    "supertest": "^4.0.2",
    "ts-jest": "^24.1.0",
    "ts-loader": "^6.1.1",
    "ts-node": "^8.4.1",
    "tsconfig-paths": "^3.9.0",
    "tslint": "^5.20.0",
    "typescript": "^3.7.4"
  }


7. 其他资源


-typeorm仓库

-Nestjs中文文档

-Jest测试框架中文文档

-github nest学习资源




以上是关于nestjs使用Typeorm实现数据库CRUD操作的主要内容,如果未能解决你的问题,请参考以下文章

使用 NestJS、TypeORM、GraphQL 更新具有实体之间关系的 PSQL 表

NestJS 基于接口注入自定义 TypeOrm 存储库

使用带有 TypeOrm 的 NestJS 连接 MySQL 数据库时出现问题

使用 NestJS 和 TypeOrm,在我运行 NestJS 应用程序后不会自动创建表

播种时NestJS中的TypeOrm:RepositoryNotFoundError

如何使用 NestJS 和 TypeORM 转换输入数据