Nest实战 - 基础资源整合
缘起
前言
本章会基于Nest9.x搭建一个基于Nest通用的模板
基础篇
创建项目
nest new nest-study
- 选择一个喜欢的源,安装依赖,我选择yarn
删除测试文件
- 左侧是项目初始化的目录结构
- 红框圈起来的属于测试用例文件,一般不用。可通过添加
"generateOptions": {
"spec": false
}
进行删除
- 我们通过
nest g res user
创建一个user模块
看下配置是否生效
很明显,配置已经生效了
启动项目
- 执行
npm run start
启动项目
- 如图所示访问http://localhost:3000/ 页面已经启动成功了
拦截器 - 返回参数格式统一
- 问题
-
现在返回的数据是一段文本数据,一般前端需要的是JSON格式的数据,不慌,我们把AppService中的返回值改成对象即可
-
-
重启项目后,刷新页面就会发现数据以json的格式返回了
-
正常的后端返回的结构是这样的
{ data, // 数据 status: 0, // 接口状态值 extra: {}, // 拓展信息 message: 'success', // 异常信息 success:true // 接口业务返回状态 }
-
- 介绍
-
拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:
- 在函数执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数 (例如, 缓存目的)
-
- 使用
- 新建文件
src/common/interceptors/transform.interceptor.ts
- 写入
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; interface Response<T> { data: T; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept( context: ExecutionContext, next: CallHandler, ): Observable<Response<T>> { return next.handle().pipe( map((data) => ({ data, status: 0, extra: {}, message: 'success', success: true, })), ); } }
- 修改
app.module.ts
添加TransformInterceptor
到全局
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; import { UserModule } from './user/user.module'; @Module({ imports: [UserModule], controllers: [AppController], providers: [ AppService, { // 全局拦截器 provide: APP_INTERCEPTOR, useClass: TransformInterceptor, }, ], }) export class AppModule {}
- 启动项目
npm run start:dev
可以实现热重启,同时发现数据已经被拦截器拦截了
- 新建文件
异常过滤器 - 统一处理程序异常以及Http异常
- 问题
- 当我们程序抛出异常和访问路径404的时候为了规范格式,需要自定义处理异常
全部异常拦截
- 介绍
- 内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。
- 使用
-
新建文件
src/common/exceptions/base.exception.filter.ts
-
写入
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus, } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class BaseExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const request = host.switchToHttp().getRequest<Request>(); const response = host.switchToHttp().getResponse<Response>(); // 程序异常 response.status(HttpStatus.SERVICE_UNAVAILABLE).send({ statusCode: HttpStatus.SERVICE_UNAVAILABLE, timestamp: new Date().toISOString(), path: request.url, message: exception.message, }); } }
-
修改
app.module.ts
添加BaseExceptionFilter
到全局import { BaseExceptionFilter } from './common/exceptions/base.exception.filter'; { provide: APP_FILTER, useClass: BaseExceptionFilter, },
-
启动项目
npm run start:dev
,同时发现数据已经被异常过滤器处理掉了 -
-
问题
- 访问
err1
的时候statusCode应该是404才对,我们接着添加Http异常过滤器
- 访问
-
HTTP异常拦截
自定义异常
- 新建
src/common/exceptions/custom.business.ts
- 写入
import { HttpException, HttpStatus } from '@nestjs/common'; import { BUSINESS_ERROR_CODE } from './business.error.codes'; export type TBusinessError = { code: number; message: string; }; /** * 自定义异常 - 用于主动抛出 */ export class CustomException extends HttpException { constructor(error: TBusinessError | string) { if (typeof error === 'string') { error = { code: BUSINESS_ERROR_CODE.COMMON, message: error, }; } super(error, HttpStatus.OK); } static throwForbidden() { throw new CustomException({ code: BUSINESS_ERROR_CODE.ACCESS_FORBIDDEN, message: '抱歉哦,您无此权限!', }); } }
- 新建
src/common/exceptions/custom.business.error.codes.ts
- 写入
export const BUSINESS_ERROR_CODE = { // 公共错误码 COMMON: 10001, // 无访问权限 ACCESS_FORBIDDEN: 10003, };
- 打开
src/common/exceptions/http.exception.filter.ts
- 修改
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, } from '@nestjs/common'; import { Request, Response } from 'express'; import { CustomException, TBusinessError } from './custom.exception'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const request = host.switchToHttp().getRequest<Request>(); const response = host.switchToHttp().getResponse<Response>(); // 自定义异常走这个 if (exception instanceof CustomException) { const { code, message } = exception.getResponse() as TBusinessError; response.status(HttpStatus.OK).send({ data: null, status: code, extra: {}, message, success: false, }); } // http异常 response.status(HttpStatus.NOT_FOUND).send({ statusCode: HttpStatus.NOT_FOUND, timestamp: new Date().toISOString(), path: request.url, message: exception.getResponse(), }); } }
- app.controller.ts中更改以下代码
@Get('err') getErr() { throw new CustomException('自定义异常抛出'); return this.appService.getHello(); }
- 刷新浏览器效果如下
-
API多版本控制
- 介绍
- 接口版本可以在同一个应用程序中的控制器或者路由层面支持 不同的版本。 应用程序经常更改,在仍然需要支持以前版本的应用程序的同时,需要进行重大更改的情况并不少见。
main.ts
中写入// api多版本控制 app.enableVersioning({ type: VersioningType.URI, });
- 请求上添加
Version('1')
即可 - 如果所有接口想要增加
v1
版本前缀,添加app.enableVersioning
的defaultVersion
属性即可// api多版本控制 app.enableVersioning({ type: VersioningType.URI, defaultVersion: ['1'], });
- 这个时候直接访问
3000
端口就会出现404的问题
自定义配置文件
- 介绍
- 为了方便维护配置项,故需要集中管理
- 安装
yarn add cross-env @nestjs/config js-yaml
yarn add @types/js-yaml -D
- 新建
根目录/.config/.dev.yml
- 写入
HTTP: host: 'localhost' port: 3000
package.json
更改"start:dev": "cross-env RUNNING_ENV=dev nest start --watch"
- 新建
src/utils/ymlConfig.ts
- 写入
import { readFileSync } from 'fs'; import * as yaml from 'js-yaml'; import { join } from 'path'; const getEnv = () => process.env.RUNNING_ENV; export const getConfig = (key?: string) => { const ymlInfo = yaml.load( readFileSync(join(process.cwd(), `.config/.${getEnv()}.yml`), 'utf-8'), ) as Record<string, any>; if (key) { return ymlInfo[key]; } return ymlInfo; };
- 为了解决
process.env.
没提示的问题,新建src/types/index.d.ts
,写入declare namespace NodeJS { interface ProcessEnv { RUNNING: string; } }
通过TS
的类型合并使其生效
app.module.ts
下imports
中添加import { getConfig } from './common/utils/ymlConfig';
ConfigModule.forRoot({ isGlobal: true, ignoreEnvFile: true, load: [getConfig], }),
app.controller.ts
中添加constructor( private readonly appService: AppService, private readonly configService: ConfigService, ) {} Get() @Version('1') getHello1() { return this.configService.get('HTTP'); }
- 启动项目
swagger文档
- 介绍
- 提供文档支持
- 安装
yarn add @nestjs/swagger
- 新建
src/doc.ts
- 写入
import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { knife4jSetup } from 'nest-knife4j'; // 文档 export const generateDocmment = (app: INestApplication) => { const config = new DocumentBuilder() .setTitle('nest-study') .setDescription('The nest-study API description') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); };
main.ts
新增// 文档支持 generateDocmment(app);
- 启动项目访问
localhost:3000/api
swagger新皮 - knife4j
- 安装
yarn add nestjs-knife4j
doc.ts
中新增import { knife4jSetup } from 'nestjs-knife4j'; // doc.html knife4jSetup(app, { urls: [ { name: '1.X版本', url: `/api-json`, swaggerVersion: '3.0', location: `/api-json`, }, ], });
- 启动项目访问
localhost:3000/doc.html
热重载
- 介绍
- 对应用程序的引导过程影响最大的是
TypeScript
编译。但问题是,每次发生变化时,我们是否必须重新编译整个项目?一点也不。这就是为什么 webpackHMR
(Hot-Module Replacement)大大减少了实例化您的应用程序所需的时间。
- 对应用程序的引导过程影响最大的是
- 安装
yarn add webpack-node-externals run-script-webpack-plugin webpack -D
- 新建
更目录/webpack-hmr.config.js
- 写入
// eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; };
- 修改
main.ts
,注意
热更代码要放到启动项目之后,否则项目无法启动declare const module: any; async function bootstrap() { // 创建实例 const app = await NestFactory.create(AppModule); // api多版本控制 app.enableVersioning({ type: VersioningType.URI, }); // 日志 app.use(logger); // 文档支持 generateDocmment(app); // 启动项目 await app.listen(getConfig('HTTP').port); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap();
package.json
中添加热更命令"start:dev:hot": "cross-env RUNNING_ENV=dev nest build --webpack --webpackPath webpack-hmr.config.js --watch",
npm run start:dev:hot
启动项目,然后随便更改一个文件保存,效果如下- 可以看到热更已经成功启动
日志篇
log4j
- 介绍
- 我们将配置自定义日志信息,写入本地文件,方便排查问题
- 安装
yarn add log4js stacktrace-js moment chalk@4.1.2
- 新建
根目录/.config/log4js.ts
- 写入
import * as path from 'path'; const baseLogPath=path.resolve(__dirname,'../../logs');//日志要写入哪个目录 const log4jsConfig={ appenders:{ console:{ type:'console',//打印到控制台 }, access:{ type:'dateFile',//会写入文件,并且按照日期分类 filename:`${baseLogPath}/access/access.log`,//日志文件名,会命名为:access.当前时间.log alwaysIncludePattern:true, pattern:'yyyyMMdd',//时间格式 daysToKeep:60, numBackups:3, category:'http', keepFileExt:true,//是否保留文件后缀 }, app:{ type:'dateFile', filename:`${baseLogPath}/app-out/app.log`, alwaysIncludePattern:true, layout:{ type:'pattern', pattern: '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', }, //日志文件按日期切割 pattern:'yyyyMMdd', daysToKeep:60, numBackups:3, keepFileExt:true, }, errorFile:{ type:'dateFile', filename:`${baseLogPath}/errors/error.log`, alwaysIncludePattern:true, layout:{ type:'pattern', pattern: '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', }, //日志文件按日期切割 pattern:'yyyyMMdd', daysToKeep:60, numBackups:3, keepFileExt:true, }, errors:{ type: 'logLevelFilter', level: 'ERROR', appender: 'errorFile', }, }, categories:{ default:{ appenders:['console','app','errors'], level:'DEBUG', }, info: { appenders: ['console', 'app', 'errors'], level: 'info' }, access: { appenders: ['console', 'app', 'errors'], level: 'info' }, http: { appenders: ['access'], level: 'DEBUG' }, }, pm2:false,//使用pm2来管理项目时打开 pm2InstanceVar:'INSTANCE_ID',// 会根据 pm2 分配的 id 进行区分,以免各进程在写日志时造成冲突 } export default log4jsConfig;
- 新建
src/common/logger/log4js.ts
- 写入
// 日志 // src/common/logger/log4js.ts import * as Path from 'path'; import * as Log4js from 'log4js'; import * as Util from 'util'; import * as Moment from 'moment'; // 处理时间的工具 import * as StackTrace from 'stacktrace-js'; import Chalk from 'chalk'; import config from '../../../.config/log4js'; //日志级别 export enum LoggerLevel { ALL = 'ALL', MARK = 'MARK', TRACE = 'TRACE', DEBUG = 'DEBUG', INFO = 'INFO', WARN = 'WARN', ERROR = 'ERROR', FATAL = 'FATAL', OFF = 'OFF', } // 内容跟踪类 export class ContextTrace { constructor( public readonly context: string, public readonly path?: string, public readonly lineNumber?: number, public readonly columnNumber?: number, ) {} } Log4js.addLayout('Awesome-nest', (logConfig: any) => { return (logEvent: Log4js.LoggingEvent): string => { let moduleName = ''; let position = ''; //日志组装 const messageList: string[] = []; logEvent.data.forEach((value: any) => { if (value instanceof ContextTrace) { moduleName = value.context; //显示触发日志的坐标(行/列) if (value.lineNumber && value.columnNumber) { position = `${value.lineNumber},${value.columnNumber}`; } return; } if (typeof value !== 'string') { value = Util.inspect(value, false, 3, true); } messageList.push(value); }); //日志组成部分 const messageOutput: string = messageList.join(' '); const positionOutput: string = position ? `[${position}]` : ''; const typeOutput = `[${logConfig.type}]${logEvent.pid.toString()} - `; const dateOutput = `${Moment(logEvent.startTime).format( 'YYYY-MM-DD HH:mm:ss', )}`; const moduleOutput: string = moduleName ? `[${moduleName}]` : '[LoggerService]'; let levelOutput = `[${logEvent.level}]${messageOutput}`; //根据日志级别,用不同颜色区分 switch (logEvent.level.toString()) { case LoggerLevel.DEBUG: levelOutput = Chalk.green(levelOutput); break; case LoggerLevel.INFO: levelOutput = Chalk.cyan(levelOutput); break; case LoggerLevel.WARN: levelOutput = Chalk.yellow(levelOutput); break; case LoggerLevel.ERROR: levelOutput = Chalk.red(levelOutput); break; case LoggerLevel.FATAL: levelOutput = Chalk.hex('#DD4C35')(levelOutput); break; default: levelOutput = Chalk.grey(levelOutput); break; } return `${Chalk.green(typeOutput)} ${dateOutput} ${Chalk.yellow( moduleOutput, )}`; }; }); // 注入配置 Log4js.configure(config); //实例化 const logger = Log4js.getLogger(); logger.level = LoggerLevel.TRACE; export class Logger { static trace(...args) { logger.trace(Logger.getStackTrace(), ...args); } static debug(...args) { logger.debug(Logger.getStackTrace(), ...args); } static log(...args) { logger.info(Logger.getStackTrace(), ...args); } static info(...args) { logger.info(Logger.getStackTrace(), ...args); } static warn(...args) { logger.warn(Logger.getStackTrace(), ...args); } static warning(...args) { logger.warn(Logger.getStackTrace(), ...args); } static error(...args) { logger.error(Logger.getStackTrace(), ...args); } static fatal(...args) { logger.fatal(Logger.getStackTrace(), ...args); } static access(...args) { const loggerCustom = Log4js.getLogger('http'); loggerCustom.info(Logger.getStackTrace(), ...args); } // 日志追踪,可以追溯到哪个文件、第几行第几列 static getStackTrace(deep = 2): string { const stackList: StackTrace.StackFrame[] = StackTrace.getSync(); const stackInfo: StackTrace.StackFrame = stackList[deep]; const lineNumber: number = stackInfo.lineNumber; const columnNumber: number = stackInfo.columnNumber; const fileName: string = stackInfo.fileName; const basename: string = Path.basename(fileName); return `${basename}(line: ${lineNumber}, column: ${columnNumber}): \n`; } }
- 新建
src/common/middleware/logger.middleware.ts
- 写入
import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; import { Logger } from '../logger/log4js'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const code = res.statusCode; //响应状态吗 next(); // 组装日志信息 const logFormat = `Method:${req.method} Request original url: ${req.originalUrl} IP:${req.ip} Status code:${code} `; // 根据状态码进行日志类型区分 if (code >= 500) { Logger.error(logFormat); } else if (code >= 400) { Logger.warn(logFormat); } else { Logger.access(logFormat); Logger.log(logFormat); } } } // 函数式中间件 export function logger(req: Request, res: Response, next: () => any) { const code = res.statusCode; //响应状态码 next(); // 组装日志信息 const logFormat = ` >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip} Status code: ${code} Parmas: ${JSON.stringify(req.params, null, 2)} Query: ${JSON.stringify(req.query, null, 2)} Body: ${JSON.stringify( req.body, null, 2, )} \n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> `; //根据状态码,进行日志类型区分 if (code >= 500) { Logger.error(logFormat); } else if (code >= 400) { Logger.warn(logFormat); } else { Logger.access(logFormat); // Logger.log(logFormat); } }
- 修改
main.ts
import { logger } from './common/middleware/logger.middleware'; // 日志 app.use(logger);
- 启动项目输入
locaohost:3000?name=张三&age=18
,看效果如下
数据库篇
TypeOrm-Mysql
- 介绍
Nestjs
本身就是基于注解进行开发,搭配同样基于注解的ORM
框架TypeORM
开发,效果杠杠滴,废话不多说,开始吧
基础
- 新建名为
nest-study
的数据库 - 点击
OK
即可 - 安装依赖
yarn add @nestjs/typeorm typeorm mysql2
根目录/.config/.dev.yml
添加mysql配置# mysql MYSQL_CONFIG: type: mysql # 数据库链接类型 host: localhost port: 3306 username: "root" # 数据库链接用户名 password: "你的数据库密码" # 数据库链接密码 database: "nest-study" # 数据库名 logging: true # 数据库打印日志 synchronize: true # 是否开启同步数据表功能 , 生产环境一定要关闭,开启会删除所有数据 autoLoadEntities: true # 是否自动加载实体
app.module.ts
添加以下代码import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; TypeOrmModule.forRootAsync({ useFactory() { return { ...getConfig('MYSQL_CONFIG'), }; }, async dataSourceFactory(options) { if (!options) { throw new Error('Invalid options passed'); } return new DataSource(options); }, }),
- 如图所示
- 修改
user.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserService } from './user.service'; import { UserController } from './user.controller'; import { User } from './entities/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UserController], providers: [UserService], }) export class UserModule {}
- 修改
user.entity.ts
,注册实体import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; @Entity() export class User { @ApiProperty({ example: '1' }) @PrimaryGeneratedColumn('increment', { comment: '主键ID', }) id?: number; @ApiProperty({ example: 'admin' }) @Column({ comment: '用户名' }) name: string; @ApiProperty({ example: '123456' }) @Column({ comment: '密码' }) passowrd: string; @ApiProperty({ example: 'tyf' }) @Column({ comment: '昵称' }) nickName: string; }
- 修改
user.service.ts
import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; @Injectable() export class UserService { // 俩种注入方式 // 通过构造函数注入 constructor( @InjectRepository(User) private readonly userRepositroy: Repository<User>, ) {} // 直接注入 // @InjectRepository(User) // private userRepositroy: Repository<User>; /** * * @returns 列表查询 */ list() { return this.userRepositroy.find(); } /** * * @param user * @returns 新增用户 */ save(user: User) { return this.userRepositroy.save(user); } /** * * @param user * @returns 更新 */ update(user: User) { return this.userRepositroy.update({ id: user.id }, user); } /** * * @param user * @returns 删除 */ delete(id: User['id']) { return this.userRepositroy.delete({ id }); } }
- 俩种注入方式都可以,看个人习惯
- 接着修改
user.controller.ts
import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { UserService } from './user.service'; import { ApiTags, ApiOperation } from '@nestjs/swagger'; import { User } from './entities/user.entity'; @ApiTags('用户管理') @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} @ApiOperation({ summary: '用户列表', }) @Get() list() { return this.userService.list(); } @ApiOperation({ summary: '新增用户', }) @Post() save(@Body() user: User) { return this.userService.save(user); } @ApiOperation({ summary: '修改用户', }) @Put() update(@Body() user: User) { return this.userService.update(user); } @ApiOperation({ summary: '删除', }) @Delete(':id') delete(@Param('id') id: number) { return this.userService.delete(id); } }
- 启动项目会发现数据库中
user
表已经被创建成功了 - 访问
http://localhost:3000/api
- swagger文档已经被生成了,接下来就可以利用
swagger
文档进行接口测试了 - 当然你使用
knife4j
也是可以的,访问http://localhost:3000/doc.html
即可
查
data
返回空数组正常,因为表中还没数据
增
-
新增的时候id可以手动去掉,因为我们设置的id是自增的,(不去也没关系)
-
-
-
这个时候数据已经新增成功了,调用
用户列表接口
看能否查出来 -
该
- 修改用户的时候把
name
的值改成admin1
试试看 - 调用列表接口发现也没有问题
删
- 我们把刚才新增的
id
为1
的数据删掉,发现没问题 - 继续调用列表接口验证
- 到目前为止,基本的
增删改查
就全部实现了
驼峰命名转下划线
- 介绍
- 在工作中,数据库表头采用的是
下横线
方式,如图 - 工作中一般使用
小驼峰
的形式命名代码,由于typeORM
会把属性和数据库的表头做自动映射关系,所以生成的表头就是这样的
- 在工作中,数据库表头采用的是
解法一 设置注解Column
的name
字段即可
- 可以通过打印的
sql
或者刷新数据库
可以看到变化 - 问题
- 表结构多了,这样写会累死人的
解法二 全局处理
- 安装
yarn add typeorm-naming-strategies
- 修改
app.module.ts
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; TypeOrmModule.forRootAsync({ useFactory() { return { ...getConfig('MYSQL_CONFIG'), namingStrategy: new SnakeNamingStrategy(), }; }, async dataSourceFactory(options) { if (!options) { throw new Error('Invalid options passed'); } return new DataSource(options); }, })
- 启动项目,发现已生效
事务
-
介绍
- 为了解决同一个方法中执行多条
sql
语句,只成功写入部分sql
的问题
- 为了解决同一个方法中执行多条
-
安装
yarn add typeorm-transactional
-
main.ts
添加以下代码import { initializeTransactionalContext } from 'typeorm-transactional'; initializeTransactionalContext();
-
app.module.ts
中加入以下代码 -
-
这样注解就添加成功了,如果想要使用的话只需要在方法上添加注解
Transactional
即可,具体实践后期项目会有 -
写在最后
转载自:https://juejin.cn/post/7187646225858953274