这是《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/typeormtypeorm安装nodejs侧的数据库驱动程序如
mysqlpg新建数据库配置文件配置数据库
在
app.module.ts引入数据库的配置文件调用TypeOrmModule.forRoot方法启动程序进行测试
安装依赖跳过数据库安装过程
1 | npm install --save @nestjs/typeorm typeorm pg |
按照上面的步骤创建文件src/config/database.config.ts
数据库配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'changeme',
database: 'demo-db',
entities: [`${__dirname}/../entity/**/*.{js,ts}`],
synchronize: false,
logging: ["error"],
} |
在src/app.module.ts中添加TypeOrm配置
1 2 3 4 5 6 7 8 9 10 11 12 | import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfig } from './config/typeorm.config';
@Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig)
],
providers: [],
})
export class AppModule {} |
然后就可以使用npm run start:dev来进行调试了。
集成MongoDB
非关系型数据库MongoDB同样可以使用TypeORM官方提供了@nestjs/mongoose包所以我们来介绍两种集成方法
官方@nestjs/mongoose
安装依赖
1 | npm install --save @nestjs/mongoose mongoose |
配置app.module.ts
1 2 3 4 5 6 7 | import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost:27017/test')],
})
export class AppModule {} |
使用mongoose库
安装依赖
1 | npm install --save mongoose |
新建src/database/database.providers.ts
1 2 3 4 5 6 7 8 9 | import * as mongoose from 'mongoose';
export const databaseProviders = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.connect('mongodb://localhost:27017/test'),
},
]; |
新建src/database/database.module.ts
1 2 3 4 5 6 7 8 | import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
providers: [...databaseProviders],
exports: [...databaseProviders],
})
export class DatabaseModule {} |
配置app.module.ts
1 2 3 4 5 6 7 8 9 | import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module.ts';
@Module({
imports: [
DatabaseModule,
],
})
export class AppModule {} |
配置
应用程序通常在不同的环境中运行根据环境DevelopmentProduction的不同应该使用不同的配置设置。
两种方法
使用
@nestjs/config来实现对.env的key=value对进行解析使用
config库解析yaml格式的文件
官方@nestjs/config
最简单的用法
1 | npm i --save @nestjs/config |
配置src/app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 | import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {} |
然后创建.env文件
1 2 | DATABASE_USER=test DATABASE_PASSWORD=test123 |
下面来使用src/app.controller.ts中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private configService: ConfigService,
) {}
@Get()
getHello(): string {
const dbUser = this.configService.get<string>('DATABASE_USER');
console.log(dbUser); // 这里来测试
return this.appService.getHello();
}
} |
如果访问localhost:3000即可以看到
1 2 3 4 5 6 7 8 | [Nest] 14039 - 2021/03/13 下午9:43:54 [NestFactory] Starting Nest application...
[Nest] 14039 - 2021/03/13 下午9:43:54 [InstanceLoader] ConfigHostModule dependencies initialized +95ms
[Nest] 14039 - 2021/03/13 下午9:43:54 [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 14039 - 2021/03/13 下午9:43:54 [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 14039 - 2021/03/13 下午9:43:54 [RoutesResolver] AppController {}: +7ms
[Nest] 14039 - 2021/03/13 下午9:43:54 [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 14039 - 2021/03/13 下午9:43:54 [NestApplication] Nest application successfully started +2ms
test |
进阶玩法

从这里点进去我们发现ConfigModuleOptions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import { ConfigFactory } from './config-factory.interface';
export interface ConfigModuleOptions {
cache?: boolean;
isGlobal?: boolean;
ignoreEnvFile?: boolean;
ignoreEnvVars?: boolean;
envFilePath?: string | string[];
encoding?: string;
validate?: (config: Record<string, any>) => Record<string, any>;
validationSchema?: any;
validationOptions?: Record<string, any>;
load?: Array<ConfigFactory>;
expandVariables?: boolean;
} |
所支持的参数。
我们可以利用envFilePath配合NODE_ENV来在不同的启动命令的时候使用不同的配置。
1 | npm i cross-env |
然后添加两个文件.env.development与.env.production比如.env.production
1 2 | DATABASE_USER=test1 DATABASE_PASSWORD=test123321 |
下面修改scripts
1 | "start:prod": "cross-env NODE_ENV=production node dist/main", |
可以设置app.module.ts中默认是development
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
const envPath = `.env.${process.env.NODE_ENV || 'development'}`;
console.log(' ~ file: app.module.ts ~ line 7 ~ envPath', envPath);
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: envPath,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {} |
同样大家可以启动了测试一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ➜ 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 ~ file: app.module.ts ~ line 7 ~ envPath .env.production
[Nest] 14977 - 2021/03/13 下午11:10:13 [NestFactory] Starting Nest application...
[Nest] 14977 - 2021/03/13 下午11:10:13 [InstanceLoader] ConfigHostModule dependencies initialized +34ms
[Nest] 14977 - 2021/03/13 下午11:10:13 [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 14977 - 2021/03/13 下午11:10:13 [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 14977 - 2021/03/13 下午11:10:13 [RoutesResolver] AppController {}: +6ms
[Nest] 14977 - 2021/03/13 下午11:10:13 [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 14977 - 2021/03/13 下午11:10:13 [NestApplication] Nest application successfully started +3ms
test1 |
上面打印的test1正是我们设置在.env.production中的内容。
解析yaml格式的配置
步骤
下载js-yaml与@types/js-yaml
1 2 | npm i js-yaml npm i -D @types/js-yaml |
创建配置config.yml
1 2 3 4 5 6 7 8 9 10 11 12 | http: 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 11 | import { 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 16 | import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import Configuration from './config/configuration'; // 这里调整
@Module({
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 19 | import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppService } from './app.service';
import { DatabaseConfig } from './interface';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private configService: ConfigService,
) {}
@Get()
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 14 | export interface DatabaseConfig {
postgres: PostgresConfig;
sqlite: SqliteConfig;
}
export interface PostgresConfig {
url: string;
port: number;
database: string;
}
export interface SqliteConfig {
database: string;
} |
最后测试
1 2 3 4 5 6 7 8 9 10 11 | [Nest] 16960 - 2021/03/13 下午11:34:00 [NestFactory] Starting Nest application...
[Nest] 16960 - 2021/03/13 下午11:34:00 [InstanceLoader] ConfigHostModule dependencies initialized +30ms
[Nest] 16960 - 2021/03/13 下午11:34:00 [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 16960 - 2021/03/13 下午11:34:00 [InstanceLoader] AppModule dependencies initialized +0ms
[Nest] 16960 - 2021/03/13 下午11:34:00 [RoutesResolver] AppController {}: +4ms
[Nest] 16960 - 2021/03/13 下午11:34:00 [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 16960 - 2021/03/13 下午11:34:00 [NestApplication] Nest application successfully started +1ms
{
postgres: { url: 'localhost', port: 5432, database: 'yaml-db' },
sqlite: { database: 'sqlite.db' }
} |
写到这里应该够用了代码可以查看本次提交。
使用config库解析
步骤
安装第三方包config
1 2 | npm 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 15 | import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import * as config from 'config';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
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 27 | import { 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'}`;
@Module({
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 2 | DATABASE_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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Module({
imports: [
ConfigModule.forRoot({
envFilePath: envPath,
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_USER: Joi.string().required()
}),
validationOptions: { // 这里加
allowUnknown: false,
abortEarly: true,
},
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {} |
@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 29 | import { plainToClass } from 'class-transformer';
import { IsEnum, IsNumber, validateSync } from 'class-validator';
enum Environment {
Development = "development",
Production = "production"
}
class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment;
@IsNumber()
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 19 | import { 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'}`;
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: envPath,
validate,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {} |
与使用Joi验证结果一致。
小结
使用第三方的包
config可以方便的读取配置信息但是校验却需要在读取的位置来加对于不需要验证而需要全局使用的配置项可以使用这种方式官方的
@nestjs/config可以方便的导入.env的文件同时结合js-yaml也可以导入yaml格式的配置。配置灵活而且可以配合验证工具
Joi进行参数的验证推荐自定义的校验第三方包
class-validator这里只是冰山一角后面在学习数据验证的时候还会使用到它

随时随地看视频