nestjs使用Typeorm实现数据库CRUD操作
Posted 维思自动化
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nestjs使用Typeorm实现数据库CRUD操作相关的知识,希望对你有一定的参考价值。
本示例在nestjs脚手架项目基础上,进行了一些修改,并通过TypeOrm实现了数据库的增删读写操作。由于Typeorm更适合关系型数据库,本示例为简便起见,选择sqlite3作为试验数据库。对于非关系型数据库如mongodb,则推荐使用mongoose或者typegoose等库进行数据库操作。
1.nestjs安装和快速启动
安装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文件
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文件夹,并新建错误处理和接口模块。
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 表
使用带有 TypeOrm 的 NestJS 连接 MySQL 数据库时出现问题
使用 NestJS 和 TypeOrm,在我运行 NestJS 应用程序后不会自动创建表