Skip to main content

· 9 min read
Zhouxiaoxiao

开始

我们以上篇课程 Nestjs 通用配置-基础进阶 2 的代码为基础,进行后续的学习。

本篇课程我们将学习如何通过 ORM 工具 typeOrm 对数据进行配置,然后建立一个 Coffee 实体类,使用 @nestjsx/crud 快速发布 CURD 接口并通过 @nestjs/swagger 对接口进行测试。

下面是我们需要安装的扩展包

pnpm add @nestjs/typeorm typeorm mysql2  ---typeorm包和Mysql数据库驱动mysql2
pnpm add @nestjsx/crud class-transformer class-validator @nestjsx/crud-typeorm ---crud包和校验扩展包
pnpm add @nestjs/swagger swagger-ui-express nestjs-knife4 ---swagger包和对应的UI扩展包
pnpm add nestjs-pino pino-http pino ---pino日志扩展包
pnpm add -D pino-pretty ---开发环境日志显示格式化扩展包

安装数据库

方法一(推荐)

建议安装小皮面板,这是个一键安装服务环境的软件,支持 linux,windows,mac 多环境,具体可以去官网看看。

方法二

使用remotemysql.com设置数据库,这是个免费的在线数据库网站,优点是不用占用本地资源,缺点是有点慢。

安装完成后,我们新建两个数据库 testdb-devtestdb-prod,并将数据库相关配置更新到配置文件 config.development.ymlconfig.production.yml。(注意:我们这里暂时都使用 mysql1 的配置)

配置数据库

新建配置文件 database.config.ts

为了从配置文件读取数据配置信息,我们使用 TypeOrmModule.forRootAsync 方法进行数据库配置,我们从 configService 中获取对应数据库配置信息,创建 TypeOrmModuleOptions 类型对象并利用此对象,初始化对象 TypeOrmModuleAsyncOptions

提示

对象 TypeOrmModuleOptionsTypeOrmModuleAsyncOptions 都是 TypeOrmModule.forRootAsync 方法所需要的参数。

初始化完成后,我们在 app.module.ts 中注册 TypeOrmModule

database.config.ts
export default class TypeOrmConfig {
static getOrmConfig(configService: ConfigService): TypeOrmModuleOptions {
return {
type: "mysql", //数据库类型
host: configService.get < string > "db.mysql1.dbHost", //数据库地址
port: configService.get < number > "db.mysql1.dbPort" || 3306, //端口,默认3306
username: configService.get < string > "db.mysql1.dbUser", //用户名
password: configService.get < string > "db.mysql1.dbPwd", //密码
database: configService.get < string > "db.mysql1.dbBase", //数据库名称
retryDelay: 500, // 重试连接数据库间隔
retryAttempts: 10, // 允许重连次数
entities: [__dirname + "/../**/*.entity{.ts,.js}"], //扫描根目录下的所有entity文件
synchronize:
configService.get < boolean > "db.mysql1.synchronize" || false, //是否自动同步数据结构到数据库, 这个参数正式环境一定要设置成false,默认 false
};
}
}

export const typeOrmConfigAsync: TypeOrmModuleAsyncOptions = {
imports: [ConfigModule],
useFactory: (configService: ConfigService): TypeOrmModuleOptions =>
TypeOrmConfig.getOrmConfig(configService),
inject: [ConfigService],
};
app.module.ts
...
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
}),
TypeOrmModule.forRootAsync(typeOrmConfigAsync),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

重新启动应用程序,如果能看到如下提示,则证明数据库配置成功了。

[Nest][InstanceLoader] TypeOrmCoreModule dependencies initialized +257ms
[Nest][InstanceLoader] TypeOrmModule dependencies initialized +1ms

新建目录 configuration 将文件 configuration.tsdatabase.config.ts 都放入其中,以后这个目录就存放所有配置相关的文件。

新建 Coffee 实体组件

Coffee 实体类里面我们暂时只存放两个字段 咖啡名称主键ID

我们使用 nest 命令新建 Coffee 实体类相关文件,这样做的好处是可以将信息自动注册对应的 Moudle。

使用终端进入到 src 目录执行如下命令:

nest g mo Coffee --- 新建module
nest g s Coffee --- 新建service
nest g co Coffee --- 新建controller

手工新建 Coffee.entity.ts文件

app.module.ts
@Entity()
export class Coffee {
@PrimaryGeneratedColumn()
@Exclude()
id: number;

@ApiProperty({
type: String,
description: "咖啡名称",
})
@Column()
@IsNotEmpty()
coffeeName: string;
}

新建 CRUD 接口

修改 CoffeeService 使其继承 TypeOrmCrudService,并在构造方法中自动注入 Repository<Coffee>

coffee.service.ts
@Injectable()
export class CoffeeService extends TypeOrmCrudService<Coffee> {
constructor(@InjectRepository(Coffee) repo: Repository<Coffee>) {
super(repo);
}
}

修改 CoffeeModule ,将 Coffee 实体注册到 Module 上来。

coffee.module.ts
@Module({
controllers: [CoffeeController],
providers: [CoffeeService],
imports: [TypeOrmModule.forFeature([Coffee])],
exports: [CoffeeService],
})
export class CoffeeModule {}

修改 CoffeeController,使其继承自 CrudController,增加 @Crud装饰器。

coffee.module.ts
@Crud({
model: {
type: Coffee,
},
})
@Controller('coffee')
@ApiTags('coffee')
@UseInterceptors(ClassSerializerInterceptor)
export class CoffeeController implements CrudController<Coffee> {
constructor(public service: CoffeeService) {}

@ApiOperation({
summary: 'Get all coffee',
description: '使用find()方法,获取所有咖啡数据',
})
@Get('/all-coffee')
getAllCoffees() {
return this.service.find();
}
}

完成以上工作后,基本的 CRUD 接口就完成了,这是因为使用了 @nestjsx/crud相关扩展包,会自动生成对应代码。

配置日志

configuration 目录中新建配置文件 logger.config.ts

logger.config.ts
const passUrl = new Set(['/health', '/graphql']);

export const loggerOptions: Params = {
pinoHttp: [
{
timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`,
quietReqLogger: true,
genReqId: (req: IncomingMessage): ReqId =>
(<Request>req).header('X-Request-Id'),
...(process.env.NODE_ENV === 'production'
? {}
: {
level: 'debug',
// https://github.com/pinojs/pino-pretty
transport: {
target: 'pino-pretty',
options: { sync: true },
},
}),
autoLogging: {
ignore: (req: IncomingMessage) =>
passUrl.has((<Request>req).originalUrl),
},
customAttributeKeys: {
req: '请求信息',
res: '响应信息',
err: '错误信息',
responseTime: '响应时间(ms)',
},
level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info',
serializers: {
req(req: {
httpVersion: any;
raw: { httpVersion: any; params: any; query: any; body: any };
params: any;
query: any;
body: any;
}) {
req.httpVersion = req.raw.httpVersion;
req.params = req.raw.params;
req.query = req.raw.query;
req.body = req.raw.body;
return req;
},
err(err: {
params: any;
raw: { params: any; query: any; body: any };
query: any;
body: any;
}) {
err.params = err.raw.params;
err.query = err.raw.query;
err.body = err.raw.body;
return err;
},
},
},
multistream(
[
// https://getpino.io/#/docs/help?id=log-to-different-streams
{ level: 'debug', stream: process.stdout },
{ level: 'error', stream: process.stderr },
{ level: 'fatal', stream: process.stderr },
],
{ dedupe: true },
),
],
};

app.module.ts中注册 LoggerModule

app.module.ts
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
}),
TypeOrmModule.forRootAsync(typeOrmConfigAsync),
LoggerModule.forRoot(loggerOptions),
CoffeeModule,
],
controllers: [AppController, CoffeeController],
providers: [AppService],
})
export class AppModule {}

配置 Swagger

main.ts中增加 Swagger 配置。

main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);

//增加 swagger配置
const options = new DocumentBuilder()
.setTitle("Coffee example")
.setDescription("The Coffee API description")
.setVersion("1.0")
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup("api", app, document);
//增加 knife4配置
knife4jSetup(app, {
urls: [
{
name: "2.X版本",
url: `/api-json`,
swaggerVersion: "3.0",
location: `/api-json`,
},
],
});
app.useLogger(app.get(Logger));
app.useGlobalInterceptors(new LoggerErrorInterceptor());
// 全局注册错误的过滤器
app.useGlobalFilters(new HttpExceptionFilter());
// 全局注册拦截器
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();

重启完成后访问地址http://localhost:3000/doc.html,就可以进行接口测试了。

配置全局返回统一格式

配置错误过滤器

配置错误过滤器 HttpExceptionFilter过滤所有报错返回信息,统一返回格式。

HttpException.filter.ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();

const message = exception.message;
Logger.log("错误提示", message);
const errorResponse = {
data: {
error: message,
}, // 获取全部的错误信息
message: "请求失败",
code: 1, // 自定义code
url: request.originalUrl, // 错误的url地址
};
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 设置返回的状态码、请求头、发送错误信息
response.status(status);
response.header("Content-Type", "application/json; charset=utf-8");
response.send(errorResponse);
}
}

配置全局拦截器

配置错误过滤器 TransformInterceptor拦截所有正常返回信息,统一返回格式。

Transform.interceptor.ts
interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler<T>
): Observable<Response<T>> {
return next.handle().pipe(
map((data) => {
return {
data,
code: 0,
message: "请求成功",
};
})
);
}
}

TransformInterceptorHttpExceptionFiltermain.ts中注册到 APP 中即可。

总结

通过以上的学习,我们完成了以下任务:

1、完成数据库的安装。
2、通过TypeOrm整合数据库。
3、通过nest的命令生成Coffee实体对象。
4、通过nestjs/pino增强日志输出。
5、通过nestjs/curd自动生成CURD接口。
6、通过Swagger测试接口。
7、通过拦截器和过滤器对返回的数据进行统一封装。

源码

源码可以参考这里config-typeorm,喜欢的话给个 star 吧。

· 4 min read
Zhouxiaoxiao

开始

随着工程越来越复杂,需要的配置项也越来越复杂,之前的配置方式也会在维护的时候产生麻烦,本篇文章将介绍使用yaml文件对工程配置项进行管理。

提示

这里假定你已经建立一个 Nestjs 工程,如果没有请执行如下命令:

nest new config-yml

首先我们需要安装几个插件,可以使用下面的命令进行安装。

pnpm add js-yaml  解析yml文件
pnpm add @types/js-yaml -D 解析yml文件
pnpm add cross-env 解析命令行环境变量
pnpm add lodash

新增 config.yml 文件

src同级别目录下新建目录 config,并新建三个文件,config.yml config.development.yml config.production.yml,三个文件内容如下:

config.yml
db:
mysql1:
dbType: mysql
dbUrl: http://localhost
dbPort: 3306
dbUser: root
mysql2:
dbType: mysql
dbUrl: http://localhost
dbPort: 3306
dbUser: root
config.development.yml
db:
mysql1:
dbName: mysql1-dev1
dbPwd: pwd-dev1
mysql2:
dbName: mysql1-dev2
dbPwd: pwd-dev2
config.production.yml
db:
mysql1:
dbName: mysql1-prod1
dbPwd: pwd-prod1
mysql2:
dbName: mysql1-prod2
dbPwd: pwd-prod2

在启动命令中设置环境变量

修改package.json,在启动命令上添加环境变量 cross-env NODE_ENV 参数

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

配置文件读取

src目录下新建configuration.ts文件,用以根据环境变量读取对应环境的配置信息

configuration.ts
import { readFileSync } from "fs";
import * as yaml from "js-yaml";
import { join } from "path";
import * as _ from "lodash";

// 公共配置文件名称
const YML_COMMON_CONFIG_FILENAME = "config.yml";
// 公共配置文件路径
const filePath = join(__dirname, "../config", YML_COMMON_CONFIG_FILENAME);
// 各个环境配置文件路径
const envPath = join(
__dirname,
"../config",
`config.${process.env.NODE_ENV || `development`}.yml`
);
//读取公共配置内容,并使用yml进行加载
const commonConfig = yaml.load(readFileSync(filePath, "utf8"));
//读取各个环境配置内容
const envConfig = yaml.load(readFileSync(envPath, "utf8"));

export default () => {
//讲读取的配置文件内容进行合并返回
return _.merge(commonConfig, envConfig);
};

配置文件内容获取完后,我们需要在app.module.ts中对configuration.ts进行加载

app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ConfigModule } from "@nestjs/config";
import configuration from "./configuration";

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

修改 API /config

app.controller.ts中修改 API /config

app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";

@Controller()
export class AppController {
constructor(private readonly configService: ConfigService) {}

@Get("config")
getConfig(): any {
const config = this.configService.get("db");
return config;
}
}

接着你就可以使用不同的启动命令 pnpm start:dev 或者 pnpm start:prod ,访问 API 接口http://localhost:3000/config,就可以得到不同环境的配置信息了。

总结

我们完成了使用 yml 文件对不同环境不同配置项的读取,并且统一管理公共配置项。那么此次对 yml 的配置管理就介绍完了!

源码

源码可以参考这里config-yml

· 4 min read
Zhouxiaoxiao

开始

提示

请先阅读前序章节,本文代码依赖前序章节,Nestjs 通用配置-基础配置

在我们开发过程中一般至少会有开发环境和正式环境,两个环境的配置项虽然相同但是配置值会存在不一样,这个时候就需要我们按照不同的环境进行不同的配置。

新建配置文件

先在.env文件同级目录下新建两个文件,拷贝.env内容到两个新文件:

.env.development
.env.production

修改三个文件中的DB_NAME的值,分别添加后缀 -dev -prod

DB_NAME=mydemo-dev
DB_NAME=mydemo-prod

在启动命令中设置环境变量

我们需要增加corss-env插件,我们就可以在启动命令行上增加环境变量传递给应用程序

pnpm add cross-env -D

修改package.json,在启动命令上添加环境变量 cross-env NODE_ENV 参数

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

配置文件读取

修改app.module.ts,引入环境变量,并在 ConfigModule 参数里面引入环境变量

app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ConfigModule } from "@nestjs/config";

const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: envFilePath,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

接着你就可以使用不同的启动命令 pnpm start:dev 或者 pnpm start:prod ,访问 API 接口http://localhost:3000/config,就可以得到不同环境的配置信息了。

抽离公共配置项

这样的配置可以满足 90%的 Nestjs 的配置需求了,但是会存在配置项冗余的问题,比如我们的新建的两个文件里面就冗余了配置项 DB_TYPE=MYSQL, 我们的例子里面只有数据库的配置项,如果项目的配置项很多,就会冗余非常多的配置,维护起来就非常麻烦。接下来我们解决这个问题。

我们要利用之前的.env配置文件,只在.env配置文件中配置DB_TYPE=MYSQL,其余配置文件删除DB_TYPE=MYSQL配置项。

先安装插件dotenv

pnpm add dotenv

app.module.ts中修改 ConfigModule 参数,给load赋值,指定读取.env配置文件

app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ConfigModule } from "@nestjs/config";
import * as dotenv from "dotenv";

const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: envFilePath,
load: [() => dotenv.config({ path: ".env" })],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

总结

我们完成了对不同环境不同配置项的读取,并且统一管理公共配置项。那么此次对配置管理就介绍完了!

源码

源码可以参考这里config-demo

· 3 min read
Zhouxiaoxiao

开始

通常我们建立后台应用程序的时候,是需要提前配置一些工程参数,例如数据库地址\用户名密码,redis 地址\密码等等,本篇文章将介绍如何通过@nestjs/config进行参数配置。

提示

这里假定你已经建立一个 Nestjs 工程,如果没有请执行如下命令:

nest new config-demo

进入工程目录,执行命令,安装@nestjs/config:

pnpm add @nestjs/config

新增配置文件

安装完成后,我们在和package.json同级别的目录上新建.env文件

.env
DB_TYPE=MYSQL
DB_NAME=mydemo
DB_URL=http://localhost:3306
DB_USER=root
DB_PWD=root

配置导入

同时我们修改app.module.ts文件,引入ConfigModule,并将isGlobal设置为true,让ConfigModule可以全局使用。

app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ConfigModule } from "@nestjs/config";

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

接下来我们修改app.controller.ts,并在构造函数中引入ConfigService,我们新增一个 API,在 API 里面解析出.env里面的参数并将参数返回。

app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import { ConfigService } from "@nestjs/config";

@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly configService: ConfigService
) {}

@Get("config")
getConfig(): any {
const config = {
dbType: this.configService.get("DB_TYPE"),
dbName: this.configService.get("DB_NAME"),
dbUrl: this.configService.get("DB_URL"),
dbUser: this.configService.get("DB_USER"),
dbPwd: this.configService.get("DB_PWD"),
};
return config;
}
}

启动应用程序,并请求 API 接口http://localhost:3000/config, 得到数据配置信息。至此@nestjs/config的基本使用介绍完毕。

{
"dbType": "MYSQL",
"dbName": "mydemo",
"dbUrl": "http://localhost:3306",
"dbUser": "root",
"dbPwd": "root"
}

总结

我们通过上述操作,完成了以下任务:

1、引入配置文件并读取配置文件。
2、新增接口config,返回所配置内容。

下一节我们将介绍 Nestjs 配置的进阶用法。

· 14 min read
Zhouxiaoxiao

一些常用的命令如下,不算全面但是日常对我来说是够用了。

  • git clone url :克隆项目,如需自定义本地文件夹的名称,在 url 之后加个名称即可。
  • git add :这是个多功能命令,可以用它开始跟踪新文件,或者把已跟踪的发生更改的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。
  • git rm :要从 Git 中移除某个文件,就必须要从暂存区域移除,然后提交。可以用 git rm 完成,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
  • git diff :查看尚未暂存的文件更新了哪些部分。在后面加一个 --staged 参数,将比对已暂存文件与最后一次提交的文件差异。