概述:本篇文章是NestJS笔记,包括了Nest的基本使用、连接数据库、日志操作。
一、NestJS 官方 cli
1. 快速上手
1.1 全局安装 cli
npm i -g @nestjs/cli
1.2 创建项目
nest new [项目名]
1.3 查看项目命令
- 查看
nest
命令
Usage: nest <command> [options]
Options:
-v, --version Output the current version. # 输出当前版本。
-h, --help Output usage information. # 输出使用情况信息。
Commands:
new|n [options] [name] Generate Nest application. # 生成Nest应用程序。
build [options] [app] Build Nest application. # 构建Nest应用程序。
start [options] [app] Run Nest application. # 运行Nest应用程序。
info|i Display Nest project details. # 显示Nest项目详细信息。
add [options] <library> Adds support for an external library to your project. # 将对外部库的支持添加到项目中。
generate|g [options] <schematic> [name] [path] Generate a Nest element. # 生成Nest元素。
Schematics available on @nestjs/schematics collection: # 可以在@nestjs/示意图集合中找到示意图:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ library │ lib │ Generate a new library within a monorepo │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ resource │ res │ Generate a new CRUD resource │
│ service │ s │ Generate a service declaration │
│ sub-app │ app │ Generate a new application within a monorepo │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
- 查看
nest g
的命令
nest g --help
Usage: nest generate|g [options] <schematic> [name] [path]
Generate a Nest element.
Schematics available on @nestjs/schematics collection:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ library │ lib │ Generate a new library within a monorepo │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ resource │ res │ Generate a new CRUD resource │
│ service │ s │ Generate a service declaration │
│ sub-app │ app │ Generate a new application within a monorepo │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
Options:
-d, --dry-run Report actions that would be taken without writing out results. # 测试文件
-p, --project [project] Project in which to generate files.
--flat Enforce flat structure of generated element.
--no-flat Enforce that directories are generated.
--spec Enforce spec files generation. (default: true)
--spec-file-suffix [suffix] Use a custom suffix for spec files.
--skip-import Skip importing (default: false)
--no-spec Disable spec files generation. # -g --no-spec 不创建测试文件
-c, --collection [collectionName] Schematics collection to use.
-h, --help Output usage information.
例如:
- 生成一个
user
模块
nest g module user
- 生成一个
user
模块的controller
nest g controller user
2.创建第一个 NestJS 应用
项目目录结构
src
|—— app.controller.spec.ts # 测试文件
|—— app.controller.ts # 控制器:书写路由
|—— app.module.ts # 根模块
|—— app.service.ts # 书写逻辑部分
|—— main.ts # 入口文件
3. 编写接口
- 在
app.controller.ts
文件中:
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
// 该路由模块的基础路径
@Controller("api")
export class AppController {
constructor(private readonly appService: AppService) {}
// get 请求路径:/api/app
@Get("app")
getApp(): string {
return "hello nestjs";
}
}
- 在
main.ts
文件中,添加全局统一请求路径
使用app.setGlobalPrefix(xx)
,例如:app.setGlobalPrefix('/api/v1')
,请求的路径就是:/api/v1/xxx
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix("/api/v1");
await app.listen(3000);
}
bootstrap();
- 将逻辑拆分到
xx.service.ts
中
以user.service.ts
为例:
- 在
user.service.ts
中:
import { Injectable } from "@nestjs/common";
@Injectable()
export class UserService {
getUser() {
return {
code: 200,
msg: "success",
data: [],
};
}
}
- 在
user.controller.ts
中:
将 userService 注入到容器中
constructor(private userService: UserService) {}
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
@Controller("user")
export class UserController {
// 将userService注入到容器中(依赖注入)
// 写法1:语法糖
constructor(private userService: UserService) {}
// 写法2:等价写法1
userService: UserService;
constructor() {
this.userService = new UserService();
}
@Get("user")
getUser(): Object {
return this.userService.getUser();
}
}
二、请求参数
使用注解的方式获取请求的参数
1. params参数
请求路径:http://127.0.0.1:3000/api/v1/user/1
- 写法1:
@Param()
@Get('/:id')
findParmas(@Param() params: any) {
console.log(params);
return 'success'
}
打印结果为:
{ id: '1' }
- 写法2:
@Param('id')
@Get('/:id')
findParmas(@Param('id') id: any) {
console.log(id);
return 'success'
}
打印结果为:
1
2. query参数
请求路径:http://127.0.0.1:3000/api/v1/user?username=wifi&password=123
@Get()
findUser(@Query() query: any) {
console.log(query);
return 'success'
}
打印结果为:
{ username: 'wifi', password: '123' }
3. body参数
请求路径:
POST /api/v1/user/body HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: 127.0.0.1:3000
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
username=wifi&password=123
@Post('/body')
getBody(@Body() body: any) {
console.log(body);
return 'success'
}
打印结果为:
{ username: 'wifi', password: '123' }
4. headers参数
请求路径:
添加了一个Authorization
GET /api/v1/user HTTP/1.1
Host: 127.0.0.1:3000
Authorization: ea341c68-0939-493f-824c-53bd76792a61
Host: 127.0.0.1:3000
@Get()
getHeaders(@Headers() headers: any) {
console.log(headers);
return 'success'
}
打印结果为:
{
authorization: '6e8b0c8e-b9f4-4a22-a289-c15f43f0a705',
host: '127.0.0.1:3000'
}
5. req参数(所有参数)
@Get()
getReq(@Request() req: any) {
console.log(req);
return 'success'
}
会打印请求对象所有信息
三、多环境配置两种方案
1. dotenv
支持.env
文件
1.1 安装dotenv
npm i dotenv
1.2 使用
- 新建
.env
文件
TOKEN=1234567890
USERNAME=wifi
PASSWORD=123456
- 在
index.js
使用
require("dotenv").config();
console.log(process.env);
打印的结果为:
{
...
npm_config_prefix: '/usr/local',
npm_node_execpath: '/usr/local/bin/node',
TOKEN: '1234567890',
USERNAME: 'wifi',
PASSWORD: '123456'
}
2. config
支持yaml
、yml
、json
文件
2.1 安装config
npm i config
2.2 json 文件格式(基本使用)
- 新建
config
目录,在config
目录下新建default.json
// config/default.json
{
"token": "my-token",
"db": {
"host": "localhost",
"port": 27017,
"username": "root",
"password": "123456"
}
}
- 在
index.js
使用
const config = require("config");
// 该db需要跟default.json的db对应
const dbConfig = config.get("db");
console.log(dbConfig);
输出结果为:
{
host: 'localhost',
port: 27017,
username: 'root',
password: '123456'
}
2.3 json 文件格式(进阶使用)
- 在
config
目录下新建production.json
文件(文件名不可自定义)
{
"db": {
"host": "www.wifi.com",
"port": 8080
}
}
- 在终端中切换 node 运行环境,将环境换为
production
win:
set NODE_ENV=production
mac:
export NODE_ENV=production
这时候他会合并default.json
和production.json
的字段,以production.json
为准,打印结果为:
{
host: 'www.wifi.com',
port: 8080,
username: 'root',
password: '123456'
}
2.4 yaml 文件格式
需要安装js-yaml
,也会自动合并字段
default.yaml
和production.yaml
# default.yaml
token: my-token
db:
host: localhost
port: 27017
username: root
password: 123456
# production.yaml
db:
host: www.wifi.com
port: 8080
- 运行
index.js
结果为:
{
host: 'www.wifi.com',
port: 8080,
username: 'root',
password: 123456
}
2.5 corss-env 使用
可以在package.json
中配置脚本环境
package.json
"scripts": {
"pro": "cross-env NODE_ENV=production nodemon index.js", // production环境
"dev": "cross-env NODE_ENV=development nodemon index.js" // development环境
}
-
config
目录下三个文件default.yaml
# default token: my-token db: host: localhost port: 27017 username: root password: 123456
development.yaml
# development.yaml db: host: 127.0.0.1 port: 7979
production.yaml
# production db: host: www.wifi.com port: 8080
-
执行脚本
npm run dev
{ host: '127.0.0.1', port: 7979, username: 'root', password: 123456 }
npm run pro
{ host: 'www.wifi.com', port: 8080, username: 'root', password: 123456 }
3. env 与 config 的优缺点
env
适合简单配置,config
适合嵌套复杂配置
四、@nestjs/config(在 nest 中使用官方 config 设置模块)
1. 安装
npm i @nestjs/config
2. 基本使用
2.1 user 模块分别导入
- 在
app.module.ts
中:
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [
UserModule,
// forRoot 读取根目录下的.env文件
// 在app模块的controllers和servivice中都可以使用,而在在跨模块的user模块中无法使用
ConfigModule.forRoot(),
],
controllers: [],
providers: [],
})
export class AppModule {}
- 在
user.controller.ts
中:
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";
@Controller("user")
export class UserController {
constructor(
private userService: UserService,
private configService: ConfigService
) {}
@Get("user")
getUser(): Object {
let db = this.configService.get("DB");
console.log(db);
return this.userService.getUser();
}
}
这个时候console.log(db)
会报错,需要在user.module.ts
中进行导入:
import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [ConfigModule.forRoot()],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
这个时候就可以正常读取根目录下的.env
文件了
2.2 user 模块全局导入
- 在
app.module.ts
中:
在ConfigModule.forRoot()
中配置isGlobal: true
,表示全局使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
// 配置isGlobal,可在全局使用
isGlobal: true,
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
- 在
user.controller.ts
中:
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";
@Controller("user")
export class UserController {
constructor(
private userService: UserService,
private configService: ConfigService
) {}
@Get("user")
getUser(): Object {
let db = this.configService.get("DB");
console.log(db);
return this.userService.getUser();
}
}
3. 进阶使用(env 文件)
3.1 envFilePath
结合croess-env
app.module.ts
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { ConfigModule } from '@nestjs/config';
const envFilePath = `.env.${process.env.NODE_ENV}`
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
// 配置isGlobal,可在全局使用
isGlobal: true,
// 加载指定路径的.env文件
envFilePath: envFilePath
})
],
controllers: [],
providers: [],
})
package.json
"scripts": {
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:prod": "cross-env NODE_ENV=production node"
}
3.2 load
结合dotenv
使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import * as dotenv from "dotenv";
const envFilePath = `.env.${process.env.NODE_ENV}`;
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
// 配置isGlobal,可在全局使用
isGlobal: true,
// 加载指定路径的.env文件
envFilePath: envFilePath,
// 读取.env文件作为公共配置文件
load: [() => dotenv.config({ path: ".env" })],
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
4. 进阶使用(yaml 文件)
嵌套配置 yaml 文件读取
- 安装
js-yaml
和@types/js-yaml
js-yaml
用于解析 yaml 文件,@types/js-yaml
存放 js-yaml 文件类型声明
npm i js-yaml
npm i -D @types/js-yaml
- 在根目录下新建
config
目录,在config
目录下新建config.yml
、config.development.yml
、config.production.yml
文件
# config.yml
db:
mysql1:
host: 127.0.0.1
name: mysql-dev
post: 3306
mysql2:
host: 127.0.0.2
name: mysql-dev
post: 3307
# config.development.yml
db:
mysql1:
name: mysql-dev1
mysql2:
name: mysql-dev2
# config.production.yml
db:
mysql1:
name: mysql-prod1
mysql2:
name: mysql-prod2
- 在
src
目录下新建configuration.ts
文件
安装lodash
,用于拼接文件
npm i lodash
import { readFileSync } from "fs";
import * as yaml from "js-yaml";
import { join } from "path";
import * as _ from "lodash";
const YAML_COMMON_CONFIG_FILENAME = "config.yml";
// 读取yml文件路径
const filePath = join(__dirname, "../config", YAML_COMMON_CONFIG_FILENAME);
const envPath = join(
__dirname,
"../config",
`config.${process.env.NODE_ENV}.yml`
);
const commonConfig = yaml.load(readFileSync(filePath, "utf-8"));
const envConfig = yaml.load(readFileSync(envPath, "utf-8"));
// 因为ConfigModule有一个load方法,需要传入一个函数
export default () => _.merge(commonConfig, envConfig);
- 在
app.module.ts
中
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import Configuration from "./configuration";
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
isGlobal: true,
load: [Configuration],
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
- 在
user.controller.ts
中
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";
@Controller("user")
export class UserController {
constructor(
private userService: UserService,
private configService: ConfigService
) {}
@Get("user")
getUser(): Object {
let db = this.configService.get("db");
console.log(db);
return this.userService.getUser();
}
}
执行npm run start:prod
后console.log(db);
的结果为:
# 环境不同,字段已经进行合并和覆盖
{
mysql1: { host: '127.0.0.1', name: 'mysql-prod1', post: 3306 },
mysql2: { host: '127.0.0.2', name: 'mysql-prod2', post: 3307 }
}
5. 进阶使用:配置文件参数校验 Joi
- 安装
npm i joi
- 使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import * as Joi from "joi";
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
// 环境参数校验
validationSchema: Joi.object({
// 默认3306,选择范围:3306、3309(只能是这两个)
DB_PORT: Joi.number().default(3306).valid(3306, 3309),
NODE_ENV: Joi.string()
.valid("production", "development")
.default("development"),
DB_URL: Joi.string().domain(),
DB_HOST: Joi.string().ip(),
}),
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
五、数据库使用(TypeORM)
1. ORM 是什么
ORM:对象关系映射,其主要作用是在编程中,把面向对象的概念跟数据库中的概念对应起来。举例:
定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。
2. 在 nest 中使用 TypeORM(基础)
- 安装
npm i @nestjs/typeorm typeorm mysql2
- 在
app.module.ts
中使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: "mysql",
host: "localost",
port: 3306,
username: "root",
password: "123456",
database: "nest_test",
entities: [],
// 同步本地的schema与数据库 => 初始化的时候去使用
synchronize: true,
// 日志等级
logging: ["error"],
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
3. 在 nest 中使用 TypeORM(结合 env 进阶使用)
- 在
app.module.ts
中使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm";
import { ConfigModule, ConfigService } from "@nestjs/config";
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
isGlobal: true,
}),
// 进阶写法 异步导入
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService], // 注入到useFactory中
useFactory: (configService: ConfigService) =>
({
type: "mysql",
host: configService.get("DB_HOST"),
port: 3306,
username: "nest_test",
password: "123456",
database: "nest_test",
entities: [],
synchronize: true,
logging: ["error"],
} as TypeOrmModuleOptions),
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
4. 使用 TypeORM 创建多个实体
- 在
src/user
目录下新建user.entity.ts
文件
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
// 主键
@PrimaryGeneratedColumn()
id: number;
// 列
@Column({ unique: true }) // unique: true 唯一数据
username: string;
@Column()
password: string;
}
- 在
app.module.ts
中
配置TypeOrmModule.forRootAsync
的entities
import { User } from "./user/user.entity";
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService], // 注入到useFactory中
useFactory: (configService: ConfigService) =>
({
type: "mysql",
host: configService.get("DB_HOST"),
port: 3306,
username: "nest_test",
password: "123456",
database: "nest_test",
// 配置
entities: [User],
synchronize: true,
logging: ["error"],
} as TypeOrmModuleOptions),
});
5. 一对一关系
需要有@JoinColumn()
,建立表的关联
- 新建
profile.entity.ts
文件
import {
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import { User } from "./user.entity";
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: number;
@Column()
photo: string;
@Column()
address: string;
// 使用函数是为了方便调用
@OneToOne(() => User)
// JoinColumn告诉TypeORM,在哪个表格去创建关联的字段
// 默认生成userId字段,通过user表的主键和表名进行拼接。通过name,可以自定义关联字段名
@JoinColumn({ name: "uid" })
// 注入到user字段,返回的是user实体
user: User;
}
user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
// 将profile注入到user实体上
@OneToOne(() => Profile, (profile) => profile.user)
profile: Profile;
}
- 在
app.module.ts
中进行导入
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { User } from "./user/user.entity";
import { Profile } from "./user/profile.entity";
import { Roles } from "./roles/roles.entity";
import { Logs } from "./logs/logs.entity";
@Module({
imports: [
UserModule,
ConfigModule.forRoot({
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) =>
({
type: "mysql",
host: configService.get("DB_HOST"),
port: 3306,
username: "nest_test",
password: "123456",
database: "nest_test",
// 导入的entity
entities: [User, Profile, Roles, Logs],
synchronize: true,
logging: ["error"],
} as TypeOrmModuleOptions),
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
6. 一对多关系
需要有@JoinColumn()
,建立表的关联
-
需要在
app.module.ts
中的entities
中导入(省略) -
在
user.entity.ts
中
import { Logs } from "src/logs/logs.entity";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
// 一对多
// 建立与数据库之间的关联关系,将查询出来的数据塞入logs属性中
@OneToMany(() => Logs, (logs) => logs.user)
logs: Logs[];
}
- 在
logs.entity.ts
中
import { User } from "src/user/user.entity";
import { Column, Entity, ManyToOne, JoinColumn, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Logs {
@PrimaryGeneratedColumn()
id: number;
@Column()
path: string;
@Column()
method: string;
@Column()
data: string;
@Column()
result: number;
// 多对一
@ManyToOne(() => User, (user) => user.logs) // 参数2:参数是user实体
@JoinColumn()
user: User;
}
7. 多对多关系
使用@JoinTable
建立中间表
-
需要在
app.module.ts
中的entities
中导入(省略) -
在
user.entity.ts
中
import { Logs } from "src/logs/logs.entity";
import {
Column,
Entity,
JoinTable,
ManyToMany,
OneToMany,
PrimaryGeneratedColumn,
} from "typeorm";
import { Roles } from "../roles/roles.entity";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@ManyToMany(() => Roles, (roles) => roles.users)
@JoinTable({ name: "user_roles" })
roles: Roles[];
}
- 在
roles.entity.ts
中
import { User } from "src/user/user.entity";
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Roles {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(() => User, (user) => user.roles)
users: User[];
}
8. 旧项目已有数据库,用 TypeORM 生成对接
typeorm-model-generator
生成器
- 安装
npm i -D typeorm-model-generator
- 在
package.json
中新增脚本
# typeorm-model-generator -h 地址 -p 端口号 -d 数据库名 -u 用户名 -x 密码 -e 数据库类型 -o 输出路径
typeorm-model-generator -h 127.0.0.1 -p 3306 -d nest_test -u root -x 123456 -e mysql -o .
9. CRUD 操作
9.1 基本使用
- 在
user.module.ts
导入
import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
@Module({
// 导入
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
- 在
user.service.ts
中使用
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { Repository } from "typeorm";
@Injectable()
export class UserService {
// 依赖注入
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>
) {}
async getUser() {
let res = await this.userRepository.find();
return {
code: 200,
msg: "success",
data: res,
};
}
}
9.2 查找
find()
findAll() {
return this.userRepository.find()
}
findAll()
find(username: string) {
return this.userRepository.findOne({ where: { username } })
}
9.3 新增
create()
create(user: User) {
const userTmp = this.userRepository.create(user)
return this.userRepository.save(userTmp)
}
9.4 更新
update()
update(id: number, user: Partial<User>) {
return this.userRepository.update(id, user)
}
9.5 删除
delete()
delete(id: number) {
return this.userRepository.delete(id)
}
9.6 联合查询:一对一
根据上面的 5 点(一对一关系),进行对表的一对一联合查询
findProfile(id: number) {
return this.userRepository.findOne({
where: {
id
},
relations: {
profile: true
}
})
}
9.6 联合查询:一对多
findUserLogs(id: number) {
const user = this.userRepository.findOne({ where: { id } })
return this.logsRepository.find({
where: {
user // logs返回的是user实体,具体查看上面第6点一对多
},
relations: {
user: true // 返回关联的字段信息
}
})
}
9.9 QueryBuilder 高级查询
聚合,分页查询
findLogsByGroup(id: number) {
// SELECT logs.result, COUNT(logs.result) from logs, user WHERE user.id = logs.userId AND user.id = 2 GROUP BY logs.result;
return this.logsRepository.createQueryBuilder('logs')
.select('logs.result') // logs.result是根据this.logsRepository.createQueryBuilder('logs')的logs来的
.addSelect('COUNT(logs.result)', 'count')
.leftJoinAndSelect('logs.user', 'user') // user对查询出来的字段取别名
.where('user.id = :id', { id }) // :id这种写法是为了防止sql注入
.groupBy('logs.result')
.orderBy('result', 'DESC') // 对result字段进行倒序排序
.addOrderby('count', 'DESC')
.offset(2)
.limit(10)
.getRawMany();
}
9.10 TypeORM 使用原生 sql 查询
通过query()
this.userRepository.query("select * from logs");
9.11 remove()
和delete()
的区别
建议使用:
remove()
remove
可以一次性删除单个或者多个实例,并且remove
可以触发BeforeRemove
,AfterRemove
钩子;
await repository.remove(user) // user实体
await repository.remove([user1, user2, user3])
钩子函数写在实体类上面:
user.entity.ts
中:
import { Logs } from "src/logs/logs.entity";
import { AfterInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { Roles } from '../roles/roles.entity';
import { Profile } from "./profile.entity";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
// AfterRemove 钩子函数
@AfterInsert()
afterInsert() {
console.log('afterInsert', this.id, this.username) // 可以拿到数据
}
}
delete
可以一次性删除单个或多个id实例,或者给定条件,delete()
是硬删除
await repository.delete(1) // id
10. 数据库代码重构
- 在项目根目录中创建
ormconfig.ts
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { User } from "./src/user/user.entity";
import { Logs } from "./src/logs/logs.entity";
import { Roles } from "./src/roles/roles.entity";
import { Profile } from "./src/user/profile.entity";
import * as fs from 'fs'
import * as dotenv from 'dotenv'
// 通过环境变量读取不同的.env文件
const getEnv = (env: string): Record<string, unknown> => {
if (fs.existsSync(env)) {
// 判断文件路径是否存在
return dotenv.parse(fs.readFileSync(env))
}
return {}
}
// 通过dotenv来解析不同的配置
const buiildConnectionOptions = () => {
const defaultConfig = getEnv('.env')
const envConfig = getEnv(`.env.${process.env.NODE_ENV || 'development'}`)
// 配置合并
const config = { ...defaultConfig, ...envConfig }
const entitiesDir =
process.env.NODE_ENV === 'development'
? [__dirname + '/**/*.entity.ts']
: [__dirname + '/**/*.entity{.js,.ts']
return {
type: "mysql",
host: config['DB_HOST'],
port: config['DB_PORT'],
username: "nest_test",
password: "123456",
database: "nest_test",
// entities: [User, Profile, Roles, Logs],
// 如果导入文件过多,依次导入会非常麻烦(windows下路径会有问题)
entities: entitiesDir,
synchronize: true,
logging: ["error"],
} as TypeOrmModuleOptions
}
export const connectionParams = buiildConnectionOptions()
- 在
app.module.ts
中
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { connectionParams } from "ormconfig";
import { UserModule } from "./user/user.module";
@Module({
imports: [
UserModule,
TypeOrmModule.forRoot(connectionParams)
],
controllers: [],
providers: [],
})
export class AppModule { }
根据上面步骤,就将app.module.ts
中的数据库配置文件提取到ormconfig.ts
中了。
六、日志收集
1. 使用 NestJS 内置的日志模块
内置Logger
实例
1.1 在main.ts
全局日志
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
// logger: false // 关闭所有日志
logger: ["error", "warn"], // 只打印错误和警告日志
});
app.setGlobalPrefix("/api/v1");
const port = 3000;
await app.listen(port);
// 打印日志
const logger = new Logger();
logger.log(`服务允许在${port}端口`);
logger.warn(`服务允许在${port}端口`);
logger.error(`服务允许在${port}端口`);
}
bootstrap();
[Nest] 9232 - 2024/07/15 22:42:00 LOG 服务允许在3000端口 # 颜色为绿色
[Nest] 9232 - 2024/07/15 22:42:00 WARN 服务允许在3000端口 # 颜色为橙色
[Nest] 9232 - 2024/07/15 22:42:00 ERROR 服务允许在3000端口 # 颜色为红色
1.2 在user.controller.ts
中
import { Controller, Get, Logger } from "@nestjs/common";
import { UserService } from "./user.service";
@Controller("user")
export class UserController {
private logger = new Logger(UserController.name); // 区分模块名称
constructor(private userService: UserService) {
this.logger.log("UserController Init");
}
@Get("user")
findAll(): Object {
this.logger.log(`请求findAll成功`);
return this.userService.findAll();
}
}
- 在
UserController
初始化好后,会执行:
private logger = new Logger(UserController.name) // 区分模块名称
constructor(
private userService: UserService
) {
this.logger.log('UserController Init')
}
# LOG [UserController] 这个里的UserController就是new Logger(UserController.name)的UserController.name
[Nest] 9232 - 2024/07/15 22:42:00 LOG [UserController] UserController Init
- 接口 log 日志
@Get('user')
findAll(): Object {
this.logger.log(`请求findAll成功`)
return this.userService.findAll()
}
[Nest] 9232 - 2024/07/15 22:54:03 LOG [UserController] 请求findAll成功
2. 集成第三方日志模块-Pino
2.1 安装
npm i nestjs-pino
2.2 使用
- 在
user.module.ts
中导入
import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { LoggerModule } from "nestjs-pino";
@Module({
imports: [
TypeOrmModule.forFeature([User]),
// 导入pino日志模块
LoggerModule.forRoot(),
],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
- 在
user.controller.ts
使用
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { Logger } from "nestjs-pino";
@Controller("user")
export class UserController {
constructor(private userService: UserService, private logger: Logger) {
this.logger.log("UserService Init");
}
@Get("user")
findAll(): Object {
this.logger.log("findAll接口请求成功");
return this.userService.findAll();
}
}
{"level":30,"time":1721055802833,"pid":34300,"hostname":"WKQT253","msg":"UserService Init"}
- this.logger.log(‘findAll 接口请求成功’)
{"level":30,"time":1721056214051,"pid":9076,"hostname":"WKQT253","req":{"id":1,"method":"GET","url":"/api/v1/user/user","query":{},"params":{"0":"user/user"},"headers":{"host":"localhost:3000","connection":"keep-alive","cache-control":"max-age=0","sec-ch-ua":"\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","sec-fetch-site":"none","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","accept-encoding":"gzip, deflate, br, zstd","accept-language":"zh-CN,zh;q=0.9","if-none-match":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""},"remoteAddress":"::1","remotePort":58082},"msg":"findAll接口请求成功"}
# 默认在接口请求的时候会打印一次
{"level":30,"time":1721056214078,"pid":9076,"hostname":"WKQT253","req":{"id":1,"method":"GET","url":"/api/v1/user/user","query":{},"params":{"0":"user/user"},"headers":{"host":"localhost:3000","connection":"keep-alive","cache-control":"max-age=0","sec-ch-ua":"\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","sec-fetch-site":"none","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","accept-encoding":"gzip, deflate, br, zstd","accept-language":"zh-CN,zh;q=0.9","if-none-match":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""},"remoteAddress":"::1","remotePort":58082},"res":{"statusCode":304,"headers":{"x-powered-by":"Express","etag":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""}},"responseTime":28,"msg":"request completed"}
2.3 pino-pretty 解决日志“丑”的方案
pino
终端打印的日志不便于查看,使用pino-pretty
中间件解决
- 安装
npm i pino-pretty
- 使用:在
user.module.ts
中
imports: [
LoggerModule.forRoot({
pinoHttp: {
transport: {
// 无需用import导入,只用安装即可
target: 'pino-pretty',
options: {
colorize: true
}
}
}
})
],
- this.logger.log(‘UserService Init’)
[23:17:37.516] INFO (37496): UserService Init
- 请求接口时
[23:24:59.120] INFO (31880): request completed
req: {
"id": 1,
"method": "GET",
"url": "/api/v1/user/user",
"query": {},
"params": {
"0": "user/user"
},
"headers": {
"host": "localhost:3000",
"connection": "keep-alive",
"cache-control": "max-age=0",
"sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"sec-fetch-site": "none",
"sec-fetch-mode": "navigate",
"sec-fetch-user": "?1",
"sec-fetch-dest": "document",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-CN,zh;q=0.9",
"if-none-match": "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""
},
"remoteAddress": "::1",
"remotePort": 60401
}
res: {
"statusCode": 304,
"headers": {
"x-powered-by": "Express",
"etag": "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""
}
}
responseTime: 29
2.4 pino-roll 用于生产环境(滚动日志)
pino-roll
可以将日志输出到文件
- 安装
npm i pino-roll
- 使用:在
user.module.ts
中
import { join } from "path";
LoggerModule.forRoot({
pinoHttp: {
transport: {
// targets设置多个中间件
targets: [
{
level: "info",
target: "pino-pretty",
options: {
colorize: true,
},
},
{
level: "info",
target: "pino-roll",
options: {
file: join("log", "log.txt"),
frequency: "daily",
size: "10m",
mkdir: true,
},
},
],
},
},
});
2.5 生产环境,开发环境全局配置
在app.module.ts
中
LoggerModule.forRoot({
pinoHttp: {
transport: {
targets: [
process.env.NODE_ENV === "development"
? {
// 安装pino-pretty
level: "info",
target: "pino-pretty",
options: {
colorize: true,
},
}
: {
// 安装pino-roll
level: "info",
target: "pino-roll",
options: {
file: join("log", "log.txt"),
frequency: "daily",
size: "10m",
mkdir: true,
},
},
],
},
},
});
3. 集成第三方日志模块-winston
3.1 安装
npm i nest-winston winston
3.2 使用
- 在
main.ts
中
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
// 导入包
import { createLogger } from "winston";
import * as winston from "winston";
import { WinstonModule, utilities } from "nest-winston";
async function bootstrap() {
const instance = createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(), // 日志时间
utilities.format.nestLike()
),
}),
],
});
const app = await NestFactory.create(AppModule, {
// 重构nest的logger实例
logger: WinstonModule.createLogger({
instance: instance,
}),
});
app.setGlobalPrefix("/api/v1");
const port = 3000;
await app.listen(port);
}
bootstrap();
- 在
app.module.ts
中
如果要全局注册使用 logger,需要在app.module.ts
中使用@Global()
注解,使app
模块变成全局模块,进行全局注册,使用exports
将 logger 导出使用
import { Global, Logger, Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
@Global()
@Module({
imports: [UserModule],
controllers: [],
// 从@nestjs/common进行导入。因为在main.ts中重构官方的logger实例
// 全局提供logger
providers: [Logger],
exports: [Logger],
})
export class AppModule {}
- 在
user.controller.ts
进行使用
import { Controller, Get, Inject, Logger, LoggerService } from "@nestjs/common";
@Controller("user")
export class UserController {
constructor(
// 写法1:
// @Inject(Logger) private readonly logger: LoggerService
// 写法2:
private readonly logger: Logger
) {
this.logger.log("init");
}
}
缺点:需要打印日志的时候,要手动导入 logger
3.3 winston-daily-rotate-file 滚动日志
- 安装
npm i winston-daily-rotate-file
- 使用:在
main.ts
中
-
import 'winston-daily-rotate-file'
文件全部导入 -
new winston.transports.DailyRotateFile
记录日志
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { createLogger } from "winston";
import * as winston from "winston";
import { WinstonModule, utilities } from "nest-winston";
import "winston-daily-rotate-file";
async function bootstrap() {
const instance = createLogger({
transports: [
new winston.transports.Console({
level: "info",
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
utilities.format.nestLike()
),
}),
// 将日志记录到文件中
new winston.transports.DailyRotateFile({
level: "info",
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
winston.format.simple()
),
dirname: "log",
filename: "application-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
zippedArchive: true, // 文件压缩
maxSize: "20m",
maxFiles: "15d", // 文件保存时间:15天
}),
],
});
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
instance: instance,
}),
});
app.setGlobalPrefix("/api/v1");
const port = 3000;
await app.listen(port);
}
bootstrap();
- 根据上面3.2节,即可正常保存日志到文件中
3.4 全局异常过滤器
- 异常过滤器-内置 http 异常
Exception
https://nestjs.inode.club/exception-filters#%E5%86%85%E7%BD%AEhttp%E5%BC%82%E5%B8%B8
- 基本使用
import { NotFoundException } from "@nestjs/common";
@Controller("user")
export class UserController {
@Get("user")
findAll(): Object {
throw new NotFoundException("用户不存在");
}
}
- 响应结果
{
"message": "用户不存在",
"error": "Not Found",
"statusCode": 404
}
- 进阶使用:全局异常过滤器
- 在
src
目录下新建filtters
目录,新建http-exception.filtter.ts
文件
// src/filtters/http-exception.filtter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from "@nestjs/common";
@Catch(HttpException) // 捕获HttpException错误,如果为空则捕获所有错误
export class HttpExceptionFiltter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// host表示整个nest进程
// switchToHttp 可以找到整个程序的上下文
const ctx = host.switchToHttp();
// 响应、请求对象
const response = ctx.getResponse();
const request = ctx.getRequest();
// http状态码
const status = exception.getStatus();
// 返回给接口的数据
response.status(status).json({
code: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
/**
* 第3点,throw new NotFoundException("用户不存在");
* 如果NotFoundException传入内容exception.message为传入的内容
* 如果NotFoundException未传入内容,则默认为HttpException信息
* */
msg: exception.message || HttpException.name,
});
}
}
- 在
main.ts
中
useGlobalFilters
全局过滤器
注意:全局过滤器
app.useGlobalFilters
只能有一个
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { HttpExceptionFiltter } from "./filtters/http-exception.filtter";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix("/api/v1");
// 将filtter配置成全局过滤器
app.useGlobalFilters(new HttpExceptionFiltter());
const port = 3000;
await app.listen(port);
}
bootstrap();
- 在
user.controller.ts
中使用
import {
Controller,
Get,
NotFoundException,
} from "@nestjs/common";
@Controller("user")
export class UserController {
@Get("user")
findAll(): Object {
// throw new NotFoundException()
throw new NotFoundException("用户不存在");
}
}
- 请求接口返回的数据
{
"code": 404,
"timestamp": "2024-07-15T18:20:48.503Z",
"path": "/api/v1/user/user",
"method": "GET",
"msg": "用户不存在"
}
3.5 全局异常过滤器配合 winston 记录日志
src/filtters/http-exception.filtter.ts
中
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, LoggerService } from "@nestjs/common";
@Catch(HttpException)
export class HttpExceptionFiltter implements ExceptionFilter {
// 依赖注入
constructor(private logger: LoggerService) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
const status = exception.getStatus()
// 记录错误日志
this.logger.error(exception.message, exception.stack)
response.status(status).json({
code: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
msg: exception.message || HttpException.name
})
}
}
- 在
main.ts
中
app.useGlobalFilters(new HttpExceptionFiltter(logger))
传入logger记录日志
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { createLogger } from 'winston';
import * as winston from 'winston'
import { WinstonModule, utilities } from 'nest-winston';
import 'winston-daily-rotate-file'
import { HttpExceptionFiltter } from './filtters/http-exception.filtter';
async function bootstrap() {
const instance = createLogger({
transports: [
new winston.transports.Console({
level: 'info',
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
utilities.format.nestLike()
)
}),
new winston.transports.DailyRotateFile({
level: 'info',
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
winston.format.simple()
),
dirname: 'log',
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true, // 文件压缩
maxSize: '20m',
maxFiles: '15d' // 文件保存时间:15天
})
]
})
const logger = WinstonModule.createLogger({
instance: instance
})
const app = await NestFactory.create(AppModule, {
logger: logger
});
app.setGlobalPrefix('/api/v1');
// 传入logger记录日志
app.useGlobalFilters(new HttpExceptionFiltter(logger));
const port = 3000;
await app.listen(port);
}
bootstrap();
3.6 日志模块代码重构
通过上面的知识,在main.ts
中的代码非常的臃肿,需要将不同的模块进行抽离,下面将讲述如何抽离日志模块的代码。
- 通过cli创建日志模块
nest g mo logs
会在src
下新建logs
目录,并且新建logs.module.ts
文件
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { WinstonModule, WinstonModuleOptions, utilities } from 'nest-winston';
import { Console } from 'winston/lib/winston/transports';
import * as winston from 'winston'
import * as DailyRotateFile from 'winston-daily-rotate-file';
const consoleTransPorts = new Console({
level: 'info',
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
utilities.format.nestLike()
)
})
const dailyTransPorts = new DailyRotateFile({
level: 'info',
format: winston.format.combine(
// winston.format.timestamp() 日志时间
winston.format.timestamp(),
winston.format.simple()
),
dirname: 'log',
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true, // 文件压缩
maxSize: '20m',
maxFiles: '15d' // 文件保存时间:15天
})
@Module({
imports: [
// 异步导入
WinstonModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
// configService主要用来读取环境变量的
useFactory: (configService: ConfigService) => {
return {
transports: [consoleTransPorts, dailyTransPorts]
} as WinstonModuleOptions
}
})
]
})
export class LogsModule { }
- 在
main.ts
中
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
});
// 用winston的provider去替换nest的logger
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER))
app.setGlobalPrefix('/api/v1');
const port = 3000;
await app.listen(port);
}
bootstrap();
- 在
user.controller.ts
文件中使用
import { Controller, Get, Inject, LoggerService } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
@Controller('user')
export class UserController {
constructor(
// 注入logger
@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
) {
this.logger.log('init')
}
@Get('user')
findAll(): Object {
this.logger.log('成功')
return 'ok'
}
}
打印结果:
# Nest
[Nest] 38236 - 2024/07/16 03:46:03 LOG [InstanceLoader] UserModule dependencies initialized +4ms
# Winston
[NestWinston] 38236 2024/7/16 03:46:03 LOG init
3.7 局部过滤器
- 使用cli创建文件
在filtters
目录下创建名为typeorm.filter.ts
的文件
nest g f filtters/typeorm --flat --no-spec
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { QueryFailedError, TypeORMError } from 'typeorm';
@Catch(TypeORMError) // 捕获TypeORM的错误
export class TypeormFilter implements ExceptionFilter {
catch(exception: TypeORMError, host: ArgumentsHost) {
const ctx = host.switchToHttp()
// 响应对象
const response = ctx.getResponse()
let code = 500
if (exception instanceof QueryFailedError) {
code = exception.driverError.errno
}
response.status(500).json({
code: code,
message: exception.message
})
}
}
- 在控制器中使用:
user.controller.ts
import { Controller, Get, UseFilters } from '@nestjs/common';
import { TypeormFilter } from '../filtters/typeorm.filter';
@Controller('user')
// 使用局部过滤器
@UseFilters(new TypeormFilter())
export class UserController {
@Get()
getData() {
}
}