nestjs搭建通用业务框架(5):数据库+配置
这是《nestjs搭建通用业务框架》系列的第5篇,进入开发具体的功能之前,学习nest框架本身提供的CLI工具与规划合理的工程目录,对于要实现的内容进行架构与计划,这是实现健壮高可用的框架的前提。
技术整合会从三个层次进行介绍:
- 数据库 -> 配置(多环境) -> 配置验证 -> 系统日志(本篇)
- 跨域 -> 错误拦截器 -> 缓存Redis
- 数据校验 -> 日志拦截 -> 鉴权
数据库
ORM工具库
通过数据库集成库或 ORM
,例如 Sequelize (recipe)和 TypeORM ,以在更高的抽象级别上进行操作。
ORM:对象关系映射(英语:Object Relational Mapping)是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。 从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
应用场景:
- SQL -> DB:我们写一套配置,针对不同的数据库,都可以方便的接入
- DB -> SQL:针对不同的数据库,都可以通过抽象层进行联接
Nest
还提供了与现成的TypeORM
与@nestjs/typeorm
的紧密集成,我们将在本章中对此进行介绍,而与@nestjs/mongoose
的紧密集成将在官方的这一章中介绍
目前主要的ORM工具库与特点:
- typeorm:跨库查询,事务、TS支持,支持数据库:MySQL, MariaDB, Postgres, CockroachDB, SQLite, MSSQL, Oracle, SAPHana, sql.js, MongoDB
- objection: TS、事务、饥饿加载、数据效验,基于knexjs,支持数据库:Postgres**, MSSQL, MySQL, MariaDB, SQLite3, **Oracle, Amazon Redshift
- sequelize: 有非官方的中文文档,目前缺少核心的维护与开发。支持:PostgreSQL, MySQL, MariaDB, SQLite, MSSQL
- prisma:后起之秀(官方文档写的很不错),SQL自动合并,对接GraphQL,客户端、服务端+数据管理GUI,支持:PostgreSQL, MSSQL, MySQL, SQLite
通过上面的简单对比,目前来看TypeORM是nest
官方支持且推荐的,可以来这里看看它的特性。
集成Postgre(MySQL)
步骤:
- 安装
@nestjs/typeorm
,typeorm
- 安装nodejs侧的数据库驱动程序,如
mysql
,pg
- 新建数据库配置文件,配置数据库
- 在
app.module.ts
引入数据库的配置文件,调用TypeOrmModule.forRoot
方法 - 启动程序,进行测试
安装依赖(跳过数据库安装过程):
1 | npm install --save @nestjs/typeorm typeorm pg |
按照上面的步骤,创建文件src/config/database.config.ts
:
数据库配置信息:
1 | import { TypeOrmModuleOptions } from '@nestjs/typeorm' |
在src/app.module.ts
中添加TypeOrm
配置:
1 | import { Module } from '@nestjs/common'; |
然后就可以使用npm run start:dev
来进行调试了。
集成MongoDB
非关系型数据库MongoDB
同样可以使用TypeORM
,官方提供了@nestjs/mongoose
包,所以,我们来介绍两种集成方法:
官方@nestjs/mongoose
安装依赖:
1 | npm install --save @nestjs/mongoose mongoose |
配置app.module.ts
:
1 | import { Module } from '@nestjs/common'; |
使用mongoose
库
安装依赖:
1 | npm install --save mongoose |
新建src/database/database.providers.ts
1 | import * as mongoose from 'mongoose'; |
新建src/database/database.module.ts
:
1 | import { Module } from '@nestjs/common'; |
配置app.module.ts
:
1 | import { Module } from '@nestjs/common'; |
配置
应用程序通常在不同的环境中运行,根据环境(Development,Production)的不同,应该使用不同的配置设置。
两种方法:
- 使用
@nestjs/config
来实现对.env
的key=value
对进行解析 - 使用
config
库解析yaml
格式的文件
官方@nestjs/config
最简单的用法
1 | npm i --save @nestjs/config |
配置src/app.module.ts
:
1 | import { Module } from '@nestjs/common'; |
然后创建:.env
文件:
1 | DATABASE_USER=test |
下面来使用src/app.controller.ts
中使用:
1 | import { Controller, Get } from '@nestjs/common'; |
如果访问localhost:3000
即可以看到:
1 | [Nest] 14039 - 2021/03/13 下午9:43:54 [NestFactory] Starting Nest application... |
进阶玩法
从这里点进去,我们发现ConfigModuleOptions
:
1 | import { ConfigFactory } from './config-factory.interface'; |
所支持的参数。
我们可以利用envFilePath
配合NODE_ENV
来,在不同的启动命令的时候使用不同的配置。
1 | npm i cross-env |
然后添加两个文件:.env.development
与.env.production
,比如.env.production
:
1 | DATABASE_USER=test1 |
下面修改scripts
:
1 | "start:prod": "cross-env NODE_ENV=production node dist/main", |
可以设置app.module.ts
中默认是development
:
1 | import { Module } from '@nestjs/common'; |
同样,大家可以启动了测试一下。
1 | ➜ npm run start:prod |
上面打印的test1
正是我们设置在.env.production
中的内容。
解析yaml
格式的配置
步骤:
下载
js-yaml
与@types/js-yaml
1
2npm i js-yaml
npm i -D @types/js-yaml创建配置:
config.yml
1
2
3
4
5
6
7
8
9
10
11
12http:
host: 'localhost'
port: 8080
db:
postgres:
url: 'localhost'
port: 5432
database: 'yaml-db'
sqlite:
database: 'sqlite.db'配置自定义文件
configuration.ts
1
2
3
4
5
6
7
8
9
10
11import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { join } from 'path';
const YAML_CONFIG_FILENAME = 'config.yml';
const filePath = join(__dirname, YAML_CONFIG_FILENAME);
export default () => {
return yaml.load(readFileSync(filePath, 'utf8'));
};调用
forRoot
中的load方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import Configuration from './config/configuration'; // 这里调整
({
imports: [
ConfigModule.forRoot({
load: [Configuration], // load方法
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}修改
app.controller.ts
中的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppService } from './app.service';
import { DatabaseConfig } from './interface';
()
export class AppController {
constructor(
private readonly appService: AppService,
private configService: ConfigService,
) {}
()
getHello(): string {
const db = this.configService.get<DatabaseConfig>('db');
console.log(db);
return this.appService.getHello();
}
}定义
src/interface.ts
:1
2
3
4
5
6
7
8
9
10
11
12
13
14export interface DatabaseConfig {
postgres: PostgresConfig;
sqlite: SqliteConfig;
}
export interface PostgresConfig {
url: string;
port: number;
database: string;
}
export interface SqliteConfig {
database: string;
}
最后测试:
1 | [Nest] 16960 - 2021/03/13 下午11:34:00 [NestFactory] Starting Nest application... |
写到这里,应该够用了,代码可以查看本次提交。
使用config
库解析
步骤:
安装第三方包
config
1
2npm i config -S
npm i cross-env -D新建 配置文件
config/default.json
,同样还可以建立development.json
,production.json
1
2
3
4
5{
"server": {
"happy": "my default value"
}
}development.json
:1
2
3
4
5
6
7
8{
"server": {
"port": 3001,
"host": "localhost",
"username": "test",
"password": "test"
}
}production.json
:1
2
3
4
5
6
7
8{
"server": {
"port": 3002,
"host": "localhost",
"username": "prod",
"password": "prod"
}
}在
app.controller.ts
中使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import * as config from 'config';
()
export class AppController {
constructor(private readonly appService: AppService) {}
()
getHello(): string {
const server = config.get('server');
console.log(server);
return this.appService.getHello();
}
}配置脚本:
1
2"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main",运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32➜ npm run start:dev
[Nest] 34580 - 2021/03/14 上午12:50:42 [NestFactory] Starting Nest application...
[Nest] 34580 - 2021/03/14 上午12:50:42 [InstanceLoader] AppModule dependencies initialized +34ms
[Nest] 34580 - 2021/03/14 上午12:50:42 [RoutesResolver] AppController {}: +6ms
[Nest] 34580 - 2021/03/14 上午12:50:42 [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 34580 - 2021/03/14 上午12:50:42 [NestApplication] Nest application successfully started +2ms
{
happy: 'my default value',
port: 3001,
host: 'localhost',
username: 'test',
password: 'test'
}
➜ npm run start:prod
> nestjs-common-template@0.0.1 start:prod /Users/macos/Projects/nestjs/nestjs-common-template
> cross-env NODE_ENV=production node dist/main
[Nest] 34400 - 2021/03/14 上午12:50:03 [NestFactory] Starting Nest application...
[Nest] 34400 - 2021/03/14 上午12:50:03 [InstanceLoader] AppModule dependencies initialized +71ms
[Nest] 34400 - 2021/03/14 上午12:50:03 [RoutesResolver] AppController {}: +6ms
[Nest] 34400 - 2021/03/14 上午12:50:03 [RouterExplorer] Mapped {, GET} route +2ms
[Nest] 34400 - 2021/03/14 上午12:50:03 [NestApplication] Nest application successfully started +2ms
{
happy: 'my default value',
port: 3002,
host: 'localhost',
username: 'prod',
password: 'prod'
}附上:代码地址
配置验证
配置验证,主要是指在应用程序启动时,如果没有提供所需的环境变量或不符合某些验证规则,就会抛出一个异常。@nestjs/config
包实现了两种不同的方式来实现这一点。
Joi
内置验证器。通过Joi,你可以定义一个对象模式,并根据它验证JavaScript对象- 一个自定义的
validate()
函数,它将环境变量作为输入
Joi用法
特别说明:
- 最新版本的
joi
需要你运行Node v12或更高版本。旧版本的node请安装v16.1.8
。这主要是因为在v17.0.2
发布后,在构建的时候会出现错误。更多信息请参考其17.0.0发布说明,点击这里。 - joi最好配合官方的
@nestjs/config
进行使用
步骤:
安装依赖
1
npm install --save joi
定义验证Schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as Joi from 'joi';
import { ConfigModule } from '@nestjs/config';
const envPath = `.env.${process.env.NODE_ENV || 'development'}`;
({
imports: [
ConfigModule.forRoot({
envFilePath: envPath,
// 这里多了一个属性:validationSchema
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_USER: Joi.string().required()
}),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}验证测试
配置
错误
脚本:1
"start:dev": "cross-env NODE_ENV=development PORT=toimc nest start --watch",
配置正确的脚本:
1
"start:dev": "cross-env NODE_ENV=development PORT=3000 nest start --watch",
测试命令
1
npm run start:dev
错误的提示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17[下午7:33:38] Found 0 errors. Watching for file changes.
/Users/macos/Projects/nestjs/nestjs-common-template/node_modules/_@nestjs_config@0.6.3@@nestjs/config/dist/config.module.js:61
throw new Error(`Config validation error: ${error.message}`);
^
Error: Config validation error: "PORT" must be a number
at Function.forRoot (/Users/macos/Projects/nestjs/nestjs-common-template/node_modules/_@nestjs_config@0.6.3@@nestjs/config/dist/config.module.js:61:23)
at Object.<anonymous> (/Users/macos/Projects/nestjs/nestjs-common-template/dist/app.module.js:21:35)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Module.require (internal/modules/cjs/loader.js:952:19)
at require (internal/modules/cjs/helpers.js:88:18)
at Object.<anonymous> (/Users/macos/Projects/nestjs/nestjs-common-template/dist/main.js:4:22)
at Module._compile (internal/modules/cjs/loader.js:1063:30)或者修改
.env.development
中的配置信息:1
2DATABASE_USER=
DATABASE_PASSWORD=test123错误提示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/Users/macos/Projects/nestjs/nestjs-common-template/node_modules/_@nestjs_config@0.6.3@@nestjs/config/dist/config.module.js:61
throw new Error(`Config validation error: ${error.message}`);
^
Error: Config validation error: "DATABASE_USER" is not allowed to be empty
at Function.forRoot (/Users/macos/Projects/nestjs/nestjs-common-template/node_modules/_@nestjs_config@0.6.3@@nestjs/config/dist/config.module.js:61:23)
at Object.<anonymous> (/Users/macos/Projects/nestjs/nestjs-common-template/dist/app.module.js:21:35)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Module.require (internal/modules/cjs/loader.js:952:19)
at require (internal/modules/cjs/helpers.js:88:18)
at Object.<anonymous> (/Users/macos/Projects/nestjs/nestjs-common-template/dist/main.js:4:22)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
结论:使用Joi
可以很方便对传入应用程序的参数进行验证,可以限制传入的数据类型。
除了上面写的验证以外,还可以加入以下属性来验证输入的命令参数:
1 | ({ |
@nestjs/config
包使用的默认设置是:
allowUnknown
:控制是否允许在环境变量中使用未知键。默认为trueabortEarly
:如果为true,则在第一个错误时停止验证;如果为false,则返回所有错误。默认值为false。
注意上面的Joi的用法:
- 主要是校验
process.env
传入的参数 - 主要是校验
envFilePath
初次加载的时候的参数
使用class-validator
步骤:
安装依赖
class-validator
与class-transformer
1
npm i class-validator class-transformer
配置效验文件
src/env.validation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import { plainToClass } from 'class-transformer';
import { IsEnum, IsNumber, validateSync } from 'class-validator';
enum Environment {
Development = "development",
Production = "production"
}
class EnvironmentVariables {
Environment) (
NODE_ENV: Environment;
()
PORT: number;
}
export function validate(config: Record<string, unknown>) {
const validatedConfig = plainToClass(
EnvironmentVariables,
config,
{ enableImplicitConversion: true },
);
const errors = validateSync(validatedConfig, { skipMissingProperties: false });
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}调整
app.module.ts
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { validate } from './env.validation';
const envPath = `.env.${process.env.NODE_ENV || 'development'}`;
({
imports: [
ConfigModule.forRoot({
envFilePath: envPath,
validate,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
与使用Joi
验证结果一致。
小结
使用第三方的包
config
,可以方便的读取配置信息,但是校验却需要在读取的位置来加,对于不需要验证,而需要全局使用的配置项可以使用这种方式;官方的
@nestjs/config
可以方便的导入.env
的文件,同时结合js-yaml
也可以导入yaml
格式的配置。配置灵活,而且可以配合验证工具
Joi
进行参数的验证(推荐)自定义的校验第三方包
class-validator
这里只是冰山一角,后面在学习数据验证的时候还会使用到它;