前言
前一篇写NestJS的特点,使用NestJS,开启我们的Hello World! 以及NestJS各种热更新方法,本篇会写NestJS在实际项目中的应用。(ORM、参数校验、全局错误处理等等)
NestJS 中使用 TypeORM
一、安装依赖
1npm install @nestjs/typeorm typeorm mysql2
这里假设使用 MySQL 数据库,你可以根据实际情况选择其他数据库驱动。
二、配置数据库连接
- 在项目根目录下创建一个文件
ormconfig.js
,内容如下:
1module.exports = {
2 type: 'mysql',
3 host: 'localhost',
4 port: 3306,
5 username: 'your_username',
6 password: 'your_password',
7 database: 'your_database_name',
8 entities: [__dirname + '/**/*.entity{.ts,.js}'],
9 synchronize: true,
10};
11
将 your_username
、your_password
和 your_database_name
替换为你的实际数据库连接信息。
- 在
main.ts
文件中引入并配置 TypeORM:
1
2 import { NestFactory } from '@nestjs/core';
3 import { AppModule } from './app.module';
4 import { TypeOrmModule } from '@nestjs/typeorm';
5
6 async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8 app.useGlobalPipes();
9
10 await app.listen(3000);
11 }
12
13 bootstrap();
14
三、创建实体
在 src/entities
目录下创建一个实体文件,例如 user.entity.ts
:
1import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
2
3@Entity()
4export class User {
5 @PrimaryGeneratedColumn()
6 id: number;
7 @Column()
8 name: string;
9 @Column()
10 email: string;
11}
12
四、创建数据访问层(Repository)
在 src/repositories
目录下创建一个文件,例如 user.repository.ts
:
1import { Repository } from 'typeorm';
2import { User } from '../entities/user.entity';
3export class UserRepository extends Repository<User> {}
五、在模块中配置 TypeORM 和使用数据访问层
在对应的模块文件中,例如 app.module.ts
:
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { UserEntity } from './entities/user.entity';
4import { UserRepository } from './repositories/user.repository';
5import { UsersService } from './services/users.service';
6import { UsersController } from './controllers/users.controller';
7
8@Module({
9 imports: [TypeOrmModule.forFeature([UserEntity])],
10 providers: [UsersService, UserRepository],
11 controllers: [UsersController],
12})
13export class AppModule {}
六、使用数据访问层进行数据库操作
在服务文件中注入数据访问层并进行数据库操作,例如在 users.service.ts
:
1import { Injectable } from '@nestjs/common';
2import { InjectRepository } from '@nestjs/typeorm';
3import { User } from './entities/user.entity';
4import { UserRepository } from './repositories/user.repository';
5
6@Injectable()
7export class UsersService {
8 constructor(
9 @InjectRepository(UserRepository)
10 private readonly userRepository: UserRepository,
11 ) {}
12
13 async findAll(): Promise<User[]> {
14 return this.userRepository.find();
15 }
16
17 async create(user: User): Promise<User> {
18 return this.userRepository.save(user);
19 }
20}
这样就可以在 NestJS 项目中使用 TypeORM 进行数据库操作了。
NestJS中全局错误处理
一、创建全局错误过滤器
- 创建一个类来实现
ExceptionFilter
接口,用于处理全局错误。例如,创建一个文件global-exception.filter.ts
:
1 import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
2 import { Request, Response } from 'express';
3
4 @Catch()
5 export class GlobalExceptionFilter implements ExceptionFilter {
6 catch(exception: any, host: ArgumentsHost) {
7 const ctx = host.switchToHttp();
8 const response = ctx.getResponse<Response>();
9 const request = ctx.getRequest<Request>();
10 const status = exception instanceof HttpException? exception.getStatus() : 500;
11 const message =
12 exception instanceof HttpException
13 ? exception.getResponse()['message'] || exception.message
14 : 'Internal server error';
15
16 response.status(status).json({
17 statusCode: status,
18 message,
19 timestamp: new Date().toISOString(),
20 path: request.url,
21 });
22 }
23 }
这个过滤器会捕获所有的异常,并返回一个统一格式的错误响应。
二、注册全局错误过滤器
- 在
main.ts
文件中注册全局错误过滤器:
1 import { NestFactory } from '@nestjs/core';
2 import { AppModule } from './app.module';
3 import { GlobalExceptionFilter } from './global-exception.filter';
4
5 async function bootstrap() {
6 const app = await NestFactory.create(AppModule);
7 app.useGlobalFilters(new GlobalExceptionFilter());
8 await app.listen(3000);
9 }
10
11 bootstrap();
现在,所有在应用程序中抛出的异常都会被全局错误过滤器捕获,并返回统一格式的错误响应。你可以根据实际需求进一步扩展错误处理逻辑,例如记录错误日志、发送通知等。
错误日志
在 NestJS 中可以添加错误日志来记录应用程序中的错误信息,以便于调试和故障排查。以下是一种添加错误日志的方法:
一、安装日志库
可以使用winston
和winston-daily-rotate-file
库来实现日志记录。
1npm install winston winston-daily-rotate-file
二、创建日志服务
创建一个日志服务来处理日志记录。例如,创建一个文件logger.service.ts
:
1import { Injectable, LoggerService as NestLoggerService } from '@nestjs/common';
2import * as winston from 'winston';
3import 'winston-daily-rotate-file';
4
5@Injectable()
6export class LoggerService implements NestLoggerService {
7 private logger: winston.Logger;
8
9 constructor() {
10 this.logger = winston.createLogger({
11 level: 'info',
12 format: winston.format.combine(
13 winston.format.timestamp(),
14 winston.format.json(),
15 ),
16 transports: [
17 new winston.transports.Console(),
18 new winston.transports.DailyRotateFile({
19 filename: 'logs/application-%DATE%.log',
20 datePattern: 'YYYY-MM-DD',
21 zippedArchive: true,
22 maxSize: '20m',
23 maxFiles: '14d',
24 }),
25 ],
26 });
27 }
28
29 log(message: string) {
30 this.logger.info(message);
31 }
32
33 error(message: string, trace: string) {
34 this.logger.error({ message, trace });
35 }
36
37 warn(message: string) {
38 this.logger.warn(message);
39 }
40
41 debug(message: string) {
42 this.logger.debug(message);
43 }
三、在全局错误过滤器中使用日志服务
修改全局错误过滤器,在捕获到错误时使用日志服务记录错误信息。文件global-exception.filter.ts
:
1import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
2import { Request, Response } from 'express';
3import { LoggerService } from './logger.service';
4
5@Catch()
6export class GlobalExceptionFilter implements ExceptionFilter {
7 constructor(private readonly logger: LoggerService) {}
8
9 catch(exception: any, host: ArgumentsHost) {
10 const ctx = host.switchToHttp();
11 const response = ctx.getResponse<Response>();
12 const request = ctx.getRequest<Request>();
13 const status = exception instanceof HttpException? exception.getStatus() : 500;
14 const message =
15 exception instanceof HttpException
16 ? exception.getResponse()['message'] || exception.message
17 : 'Internal server error';
18
19 this.logger.error({
20 statusCode: status,
21 message,
22 timestamp: new Date().toISOString(),
23 path: request.url,
24 });
25
26 response.status(status).json({
27 statusCode: status,
28 message,
29 timestamp: new Date().toISOString(),
30 path: request.url,
31 });
32 }
33}
在 main.ts
中注册全局错误过滤器和日志服务:
1 import { NestFactory } from '@nestjs/core';
2 import { AppModule } from './app.module';
3 import { GlobalExceptionFilter } from './global-exception.filter';
4 import { LoggerService } from './logger.service';
5
6 async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8 app.useGlobalFilters(new GlobalExceptionFilter(app.get(LoggerService)));
9 await app.listen(3000);
10 }
11
12 bootstrap();
四、在其他地方使用日志服务
可以在其他服务、控制器等地方注入日志服务,并使用它来记录日志信息。例如,在一个服务中:
1import { Injectable } from '@nestjs/common';
2import { LoggerService } from './logger.service';
3
4@Injectable()
5export class SomeService {
6 constructor(private readonly logger: LoggerService) {}
7
8 someMethod() {
9 this.logger.log('This is a log message.');
10
11 try {
12
13 } catch (error) {
14 this.logger.error('An error occurred in someMethod', error.stack);
15 }
16 }
17}
这样,在应用程序中发生错误时,错误信息会被记录到日志文件中,同时也可以在其他地方使用日志服务记录各种信息,方便调试和故障排查。
NestJS参数校验 zod
因为之前koa用的Joi,想找一个类似的。zod
是一个强大的类型安全和数据验证库。 优点:
- 提供了简洁直观的 API 来定义和验证数据结构。
- 支持类型推导,使得类型安全更加可靠。
Joi
和zod
都是 JavaScript 和 TypeScript 中常用的用于数据验证和校验的库,它们有一些相似之处,但也存在一些不同点:
一、相似之处
-
数据验证功能
- 两者都可以对输入数据进行各种类型的验证,包括字符串长度、数值范围、数据格式(如电子邮件地址)等。
- 例如,都可以验证一个字符串是否为有效的电子邮件地址,或者一个数值是否在特定的范围内。
-
链式调用语法
Joi
和zod
都提供了一种链式调用的语法,使得可以方便地组合多个验证规则。- 例如,可以通过连续调用方法来添加多个验证条件,使代码更加清晰和易读。
1import { z } from 'zod';
2
3const userSchema = z.object({
4 name: z.string().min(3),
5 email: z.string().email(),
6});
7
8export class CreateUserDto {
9 constructor(data: z.infer<typeof userSchema>) {
10 Object.assign(this, data);
11 }
12
13 name: string;
14 email: string;
15}
- 安装 zod 库:
1 npm install zod
-
创建数据验证 schema:
例如,创建一个用于用户创建的 schema:
1 import { z } from 'zod';
2
3 const createUserSchema = z.object({
4 name: z.string().min(3),
5 email: z.string().email(),
6 });
- 在控制器中使用:
1 import { Controller, Post, Body } from '@nestjs/common';
2 import { createUserSchema } from './schemas/user.schema';
3
4 @Controller('users')
5 export class UserController {
6 @Post()
7 async createUser(@Body() body: any) {
8 const parsedData = createUserSchema.safeParse(body);
9 if (!parsedData.success) {
10
11 return { error: parsedData.error };
12 }
13
14 const { name, email } = parsedData.data;
15
16 return { message: 'User created successfully' };
17 }
18 }
这样,当有 POST 请求到 /users
路径时,会使用 zod schema 对请求体进行校验,如果校验不通过,会返回校验错误信息。
后面把koa项目使用Nestjs重构完,就把代码开源。
参考: