NestJs 快速入门

  npm i -g @nestjs/cli(以后更新用npm update @nestjs/cli -g),nest new car-report创建car-report项目。src目录下的main.ts,监听3000端口,因此它是整个项目的入口文件,因为服务器都会监听一个端口。main.ts import了AppModule,但AppModule什么都没做,只是用@Module装饰。@Module接收一个对象,有三个属性,imports、controllers和providers。controllers用于注册控制器类,控制器来源于MVC架构,接受请求,返回响应。来到AppController,它只是一个装饰@Controller的类,其中getHello方法装饰了@Get,怎么接受请求,返回响应的?@Get就是接受get请求,请求的路径是@Controller()和@Get()参数的拼接,装饰到的方法处理请求,方法参数接受请求参数,返回值就是响应。相当于以下express.js路由

app.get('/', (req, res) => "Hello World!") // @Controller()和@Get()都没有参数,那就是根URL

  所以在Nest.js中,处理请求就是写一个@Controller() 装饰的类,用@Get,@Post定义请求路径并装饰方法,方法中处理请求,然后把这个类注册到@Module的controllers中。providers中的类是一个用Injectable()装饰的纯TypeScript类,Injectable是可注入的,这涉及到依赖注入。A对象依赖B对象,但它不直接创建B对象,而是用参数声明它依赖B对象,使用A对象之前前,要先把B对象注入进来。

class B { sayHello() {console.log( 'Hello World')} }

/*  不使用依赖注入 */
class A {
    constructor() {this.b = new B()} // 创建依赖的实例对象
    sayHello() { this.b.sayHello()}
}

(new A()).sayHello() // 直接使用A对象

/* 使用依赖注入 */
class A {
    constructor(b) {this.b = b;} // 声明使用的依赖,没有创建依赖对象
    sayHello() { this.b.sayHello()}
}

const b = new B();
const a = new A(b) // 创建A对象时,要把它的依赖注入进去
a.sayHello() // 再调用A对象的方法

  手动管理依赖很麻烦,通常依赖的管理(创建和注入)交由第三方(一个容器)。NestJs就实现了这么一个容器。如下图所示,Service类只声明了它依赖特定接口的dependency, injector(容器)创建了一个实现接口的实例,把它注入到service中。

  AppController中构造函数,

constructor(private readonly appService: AppService) {}

  声明依赖,它需要Appservice实例,provider中的AppService正好是可注入的。其实这是简写版的依赖注入,构造函数的完整版如下所示

constructor(@Inject(AppService) private readonly appService: AppService) {}

  @inject(AppService),注入AppService这个token指定的类的实例,只是@Inject中的参数和方法参数都是AppService,前面的@Inject(AppService)被省略了。providers完整版是一个对象,一个key是provide,表示提供什么,值是一个token标示符,用于@inject。另一个key是useValue或useClass或useFactory,值分别是value,类和工厂方法。

providers: [{provide: AppService, useClass: AppService}],

  只不过当使用useClass时,token是类本身,所以就简写了。当NestJs遇到provider指定的token(@inject(token))后,就会用useValue指定的value,或useClass指定的类的实例对象,或useFactory返回的对象(工厂方法创建对象),来注入到@Inject(token)修饰的参数。

  写一个登录注册的模块。在服务端开发时,controller负责接受客户端请求,service负责处理业务逻辑,repository负责和数据库打交道,controller调用service,service调用repository。新建users目录,目录里面users.controller.ts,users.module.ts, users.service.ts, users.repository.ts。controller调用service,依赖service, users.controller.ts

import { Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
    constructor(private usersService: UsersService) { } // 声明需要的依赖

    @Post('/signup')
    async createUser() { }

    @Post('/signin')
    async singin() { }

    @Get('/:id')
    async getUser() { }
}

  Post请求要接受请求体,Get请求要接受params。Express开发时,有请求对象和响应对象来获取这些内容。在Nest.js中,方法的参数用@Req和@Res进行装饰,就获得请求对象和响应对象。

async createUser(@Req() request: Request, @Res() response: Response) { 
    const body = request.body;
    response.json({body})
}

  request.body, request.params太常见了,于是Nest.js又有了@Body,@Param来装饰方法的参数,直接获取到对应的内容。POST请求如果接受一个复杂的对象,参数的类型是一个类,定义请求体的形状和期望的数据类型,这个类也称为DTO(数据传输对象),创建用户需要email和password, 那就创建一个类,有email 和password属性,createUser参数就是这个类型。user目录下创建User.ts

export class User {
    email: string;
    password: string
}

  然后

async createUser(@Body() body: User) { }

  客户端通过JSON.stringfy()把email和password对象以字符串形式传递过来,服务端用JSON.parse解析数据,创建一个User类对象,把解析出来的数据赋值给对象,再把对象赋值给body。最好对数据进行验证,NestJs有一个pipe的概念,数据到达路由处理函数之前要经过一系列过程,pipe就是其中之一,ValidationPipe用于验证。Class-validation 包使用注解验证,然后把验证结果给到validation pipe。npm install class-transformer class-validator, User 类添加验证

import { Transform } from "class-transformer";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";

export class User {
    @IsString()
    @IsNotEmpty()
    // @IsNotEmpty({ groups: ['create'] }) groups限制 @IsNotEmpty() 规则何时应用。仅当{ groups: ['create']显式传递到验证管道时才会执行验证。
    email: string;

    @IsString()
    @IsNotEmpty()
    password: string;

    @IsOptional()
    description?: string; // 可选
@IsOptional() @Transform(({ value }) => value.toUpperCase()) // 简单的数据转换 title?: string; }

  然后

async createUser(@Body(new ValidationPipe({
    transform: true, // 当validation 有transform 的时候,要开启transform
    // groups: ['create'] // 有条件时,开启条件判断
})) body: User) { }

  对每个客户端数据都要进行验证,可以开启全局验证。useGlobalPipes对所有的请求都应用它包含的pipe。ValidationPipe验证每一个请求,当然,如果DTO类没有添加验证规则,也不会对请求进行验证。main.ts

import { ValidationPipe } from "@nestjs/common";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
   transform: true, whitelist: true })) await app.listen(3000); }

  现在对接收到的数据进行业务处理,调用userService中的create方法,

async createUser(@Body() body: User) {
    await this.usersService.create(body.email, body.password)
    return {message: 'create successfully'}  // 当controller的方法返回基本数据类型时,直接返回。当返回对象或数组时,会序列化为json
}

  users.service.ts创建UsersService类,添加create方法,servcie被依赖,所以它能够被注入,需要@Injectable()装饰,同时调用repository,需要声明依赖

import { Injectable } from "@nestjs/common";
import { UserRepo } from "./users.repository";

@Injectable()
export class UsersService {
    constructor(private userRepo: UserRepo) { this.userRepo = userRepo } // 声明需要的依赖,
    async create(email: string, password: string) {
        return this.userRepo.create({ email, password, admin: true });
    }
}

  repository需要连接数据库,就要创建一个数据库连接实例。这个实例整个应用都会用到,最好让Nest.js来管理,依赖注入到应用的地方。但每一个provider,都属于一个模块(只能注册到@Module中的providers里面),怎么被其他模块使用?export出去。创建db目录,再创建db.module.ts。假设使用pg数据库,npm i pg

import { Module } from '@nestjs/common';
import { Pool } from 'pg'

const dbProvider = {
    provide: 'PG_CONNECTION',
    useValue: new Pool({ user: 'postgres', host: 'localhost', database: 'postgres', password: '123456', port: 5432 })
}

@Module({
    providers: [dbProvider],
    exports: [dbProvider]
})
export class DBModule { }

  一个模块要使用另一个模块功能,就要把另一个模块引入。@Module中的imports只能引入模块,所以数据库实例,也只能创建模块,这也证明模块是创建NestJs应用的基础。引入一个模块,这个模块export出来的provider,就相当于自己模块写了这个provider,和本模块中的其他provider没有什么区别。在本模块中碰到@inject的token,就会注入进来。users.module.ts引入DBModule,顺便把controller和provider 都注册了,

import { Module } from '@nestjs/common';
import { DBModule } from 'src/db/db.module';
import { UsersController } from './users.controller';
import { UserRepo } from './users.repository';
import { UsersService } from './users.service';

@Module({
    imports: [DBModule], // 使用db模块的功能
    providers: [UsersService, UserRepo], //service和repository都是被注入,要在module的provider中,表明遇到哪个token才注入
    controllers: [UsersController]
})
export class UsersModule { }

  users.repository.ts

import { Inject, Injectable } from "@nestjs/common";

@Injectable()
export class UserRepo {
    constructor(@Inject('PG_CONNECTION') private connect: any) { } // 注入数据库的连接实例
    async create({ email, password, admin }: any) {
        /* pg中创建users表
            CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(50), password VARCHAR(50),admin BOOLEAN);
        */
        const text = 'INSERT INTO users(email, password, admin) VALUES($1, $2, $3) RETURNING *'
        const values = [email, password, admin]

        const res = await this.connect.query(text, values)return res.rows[0]
    }
}

  整个Users模块开发完了,怎么使用?main.ts启动文件中只import了AppModule。AppModule的@Module装饰器中有import,可以引入其他module

import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule]
})
export class AppModule {}

  官方推荐使用TypeORM等ORM框架来操作数据库,npm i @nestjs/typeorm typeorm。TypeORM有实体(Entity)的概念,它是一个类,对应数据库的一张表,类名和表名对应,属性和列对应。数据库中有users表,所以创建Users类。users目录下创建users.entity.ts

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Users {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({default: true})
  admin: boolean;

  @Column()
  email: string;

  @Column()
  password: string;
}

  连接数据库,在AppModule

import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './users/users.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 5432,
      username: 'postgres', password: '123456', database: 'postgres',
      entities: [Users]
    }),
    UsersModule
  ]
})
export class AppModule { }

  TypeORM模块调用forRoot方法返回一个模块,这种模块称为动态模块,之所以动态,是因为它能接受参数,能配置,返回不同的内容。相应的,UsersModule是静态模块,模块功能是固定的。不管是静态还是动态,模块一旦创建,都是单例,存在某个地方,不全局可用(every module has its own set of imported modules in its scope)。Typeorm模块在AppMoule中创建,它就已经存在,Users模块要import它,使用Typeorm.forfeature()。users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { Users } from './users.entity';

@Module({
    imports: [TypeOrmModule.forFeature([Users])], // 使用TypeOrmModule
    providers: [UsersService], 
    controllers: [UsersController]
})
export class UsersModule { }

  去掉了users.repository.ts,因为有了实体,TypeORM会自动创建对应的repostiory对象来操作表,不用手动创建Repository类了,只需要在service中注入创建好的repostiory对象,users.service.ts

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { Users } from "./users.entity";

@Injectable()
export class UsersService {
    constructor(@InjectRepository(Users) private userRepo: Repository<Users>) { } // Repositoy类型使用泛型Repository<User>。
    
    // Typeorm生成的repository对象有create,save,insert,update,delete,find等方法。
    // 向数据库中插入数据,有两种实现方式,create之后调用save 或直接insert
    async create(email: string, password: string) {
        const user = this.userRepo.create({ email, password, admin: true });
        await this.userRepo.save(user);
        // 或者
        // await this.userRepo.insert({ email, password, admin: false }
    }
}

  npm run start:dev启动服务,

  登录singin,都会返回token,以后每一个请求都带有token,就知道谁在请求。npm install  @nestjs/jwt。在UsersModule中,

import { JwtModule } from '@nestjs/jwt';
export const secret = 'addfsdsfdf'

imports: [TypeOrmModule.forFeature([Users]), JwtModule.register({secret,signOptions: { expiresIn: '1h' }})]

  再UsersService中注入JwtService,实现signIn方法

import { JwtService } from '@nestjs/jwt';

async signIn(email: string, password: string) {
    const user = await this.userRepo.findOne({where: { email }});
    if (user?.password !== password) { throw new Error("登录失败");}
    return await this.jwtService.signAsync({ sub: user.id });
}

  在UsersController 中

async singin(@Body() body: User) {
    const token = await this.usersService.signIn(body.email, body.password)
    return { access_token: token };
}

  getUser方法,用户登录才能访问,没有登录不能访问,这些路由需要保护。这就用到guard,它有一个canActivate()方法, 返回true表示允许访问,可以继续到其它路由处理程序,返回false则拒绝访问,无法继续了。用户登录就是验证token,创建AuthGuard.ts

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { secret } from './users.module';

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private jwtService: JwtService) { }

    async canActivate(context: ExecutionContext) {
        const request = context.switchToHttp().getRequest();
        const token = request.headers.authorization;
        if (!token) { throw new UnauthorizedException(); }

        try { await this.jwtService.verifyAsync(token,{secret}); } 
        catch { throw new UnauthorizedException(); }
        return true;
    }
}

  getUser方法,那就使用UseGuard进行保护

@Get('/:id')
@UseGuards(AuthGuard) // AuthGuard此时是方法作用域,位于整个模块的controller内,所以它里面的jwtService,能被注入这个模块注册的jwt provider
// @Param 有两种使用方式,不带参数,获取整个对象,带参数,获取指定的某个参数。// ParseIntPipe 把id转换成number类型。
async getUser(@Param('id', ParseIntPipe) id: number, @Param('sex') sex: string, @Param() all: any) {
    return await this.usersService.findOne(id);
}

  userService实现findOne,

async findOne(id: number) {
    if (!id) throw new Error("没有id");
    return await this.userRepo.findOneBy({id});
}

  返回值中有password,需要去掉,可以用拦截器。拦截器实现一个NestInterface,intercept方法中正常写,拦截请求,return next.handle() 拦截响应,对路由处理函数的返回值进行处理。它返回的是rxjs的observer,map中的data 就是路由处理器返回的data。创建serialInteceptor.ts

import { CallHandler, NestInterceptor } from "@nestjs/common";
import { map } from "rxjs";

export class SerializeIntercepter implements NestInterceptor {
    intercept(_, next: CallHandler<any>) {
        return next.handle().pipe( // pipe方法将多个操作符链接在一起,操作符对数据进行处理
            map(data => { // map操作符,类似数组中的map,对流中的每一个值进行处理,返回新值
                Reflect.deleteProperty(data, 'password'); // 删除password
                return data;
            })
        )
    }
}

  getUser用userInterceptor.

@Get('/:id')
@UseGuards(AuthGuard)
@UseInterceptors(SerializeIntercepter)
async getUser(@Param('id', ParseIntPipe) id: number) {
    return await this.usersService.findOne(id);
}

  可以把拦截器包起来,形成一个装饰器,serialInteceptor.ts

export function Serialize() {
    return UseInterceptors(SerializeIntercepter);
}

  getUser 去掉@UseInterceptors(SerializeIntercepter), 直接使用@Serialize()。在以上方法中都抛出了异常,NestJs有一层Exception filter,当程序抛出了异常,而没有被捕获时,它会把异常转换成合适response,比如 throw NotFoundExeption, nextJs会返回404,not found。对exception 进行过滤,返回合适的响应。

  再创建一个report模块,一辆汽车的报告,用户创建它,admin 用户批准它。nest cli 提供了一些命令来创建module,controller和service, nest g module reports,nest g controller reports, nest g service reports,手动在reports目录建reports.entity.ts

import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';

@Entity()
export class Report {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({default: false})
  approved: boolean;

  @Column()
  price: number;

  @Column()
  year: number

  @Column()
  mileage: number
}

  然后在AppModule 中,Typeorm的配置项entities中,添加Report

 entities: [Users, Report],

  用户创建report, createReport 中要知道用户的信息,admin批准report,那还要判断登录的用户是不是admin,如要不是,批准的api就不能被调用,需要创建AdminGuard。从客户端请求中,只能得到userId,所以其它信息还要从数据库里面取。这里要用到中间件,这是由中间件,guard,拦截器的执行顺序决定的。

   在中间件中,调用userService,获取到用户信息,然后把信息添加到request对象上,后面执行的guard,拦截器,路由处理器都能获取到request对象上在user信息。在src目录下,创建current-user.middlewire.ts。 Middleware is great for tasks that do not involve decision-making concerning the continuation of the request-response cycle based on business logic. 中间件非常适合那些不涉及基于业务逻辑的请求-响应周期延续的决策的任务。

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { UsersService } from './users/users.service';
import { JwtService } from '@nestjs/jwt';
import { secret } from './users/users.module';

@Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
    constructor(private user: UsersService, private jwtService: JwtService) { }

    async use(req: Request, res: Response, next: NextFunction) {
        const [, token] = req.headers.authorization?.split(' ') ?? [];
        if (token) {
            try {
                const result = await this.jwtService.verify(token, { secret });
                const user = await this.user.findOne(result.sub);
                // @ts-ignore
                req.currentUser = user;
            } catch (error) {
                console.log(error)
            }
        }
        next();
    }
}

  中间件的使用比较特别,使用中间件的module要实现NestModule, 在configure中配置,比如在AppModule中配置中间件

import { CurrentUserMiddleware } from './current-user.middlewire';
import { MiddlewareConsumer, NestModule } from '@nestjs/common';

export class AppModule implements NestModule  {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(CurrentUserMiddleware)
      .forRoutes('*'); // 或for('/reports')
  }
}

  由于中间件在AppModule中引入的,使用了UserService,UserModule还要exports 出去UserService。

@Module({
  // ....
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule { }

  现在createReport可以获取到user信息了,但怎么在report中保存user信息呢?这涉及到了关系,report和user有1对多的关系,

  在user实体中, 添加属性

@Entity()
export class User {
  // ...
  @OneToMany(() => Report, (report) => report.user )
  reports: Report[] // 数组表示多个report
}

  在report 实体添加属性

@Entity()
export class Report {
  // ...
  @ManyToOne(() => User, (user) => user.reports)
  user: User
}

  oneToMany或ManyToOne为什么第一个参数是函数。这是因为,User Entity中使用Report Entity, Report Entity 中又使用User Entity,循环依赖了,不能直接使用,所以要用函数包起来,以后执行,而不是加载文件的时候执行。第二个函数参数的意思是关联的实体,返回值是定义的实体, 通过关联的实体report怎么找回到定义report的实体(User),report entity 有一个user字段,就是定义reports的实体(User实体中有reports属性)。Report实体有一个user字段,存储report时,给report的user属性赋值一个user实体,当真正存储到数据库时,会从user实体中取出id,存储到数据库。ReporstController

import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../users/AuthGuard';
import { ReportsService } from './reports.service';

@Controller('reports')
export class ReportsController {
    constructor(private readonly reportsService: ReportsService) { }

    @Post()
    @UseGuards(AuthGuard)
    async createReport(@Body() body: any, @Req() req: any) { //body 的类型本来是一个DTO类型,简单起见,写了any
        const userReturn = await this.reportsService.create(body, req.currentUser)
        return userReturn
    }
}

  ReportsService

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from 'src/users/users.entity';
import { Repository } from 'typeorm';
import { Report } from './reports.entity'

@Injectable()
export class ReportsService {
    constructor(
        @InjectRepository(Report)
        private reportsRepository: Repository<Report>,
    ) { }
    create(reportDto: any, user: User) {
        const report = this.reportsRepository.create(reportDto);
        // @ts-ignore
        report.user = user;
        return this.reportsRepository.save(report)
    }
}

  由于在Service中注入了Report,所以在ReportsModule中 imports: [TypeOrmModule.forFeature([Report])],

  repository的save方法把整个关联的user 实体都返回了。还有就是controller 接收了@req参数,能不能也像@Body一样,直接获取user?这要自定义一个参数装饰器createParaDecorator. 在src目录下,currentUser.ts

import {createParamDecorator, ExecutionContext} from '@nestjs/common'

export const CurrentUser = createParamDecorator(
    (data: never, context: ExecutionContext) => {
        const request = context.switchToHttp().getRequest();
        return request.currentUser
    }
)

  controller

import { CurrentUser } from '../currentUser';
import { User } from '../users/users.entity';

@Post()
@UseGuards(AuthGuard)
async createReport(@Body() body: any,  @CurrentUser() user: User) {
    const userReturn = await this.reportsService.create(body, user)
    // @ts-ignore
    const newUser = { ...userReturn, userId: userReturn.user.id };
    // @ts-ignore
    delete newUser.user;
    return newUser
}

  现在写一个approve, 就是把report的approve属性,改成true.  它需要admin权限,写一个AdminGuard。在report目录下,admin.guard.ts

import { CanActivate,  ExecutionContext} from '@nestjs/common'

export class AdminGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean {
        const request = context.switchToHttp().getRequest();
        if(!request.currentUser) {
            return false
        }

        if(request.currentUser.admin) {
            return true
        } else {
            return false
        }
    }  
}

  ReportsController 添加一个patch 路由

@Patch('/:id')
@UseGuards(AdminGuard)
async approveReport(@Param('id') id: number, @Body() body: { approved: boolean }) {
    return await this.reportsService.changeApproval(id, body.approved);
}

  ReportsService 添加 changeApproval 方法

async changeApproval(id: number, approved: boolean) {
    const report = await this.reportsRepository.findOne({ where: { id } })
    if (!report) {
        throw new NotFoundException('not found')
    }

    report.approved = approved;

    return this.reportsRepository.save(report)
}

  当查询条件比较复杂的时候,就不能简单地用findOne和find了,就要使用createQueryBuilder,比如查询price是5000, mileage 也是5000等。在Controller 中,

@Get()
async getOneReport() {
   return this.reportsService.getReport();
}

  在Service 中

async getReport() {
  return await this.reportsRepository.createQueryBuilder('report')
        .where('report.price= :price', {price: 5000})
        .andWhere("report.mileage = :mileage", { mileage: 5000 })
        .getOne()

}

  当fetch reprot时,不会自动fetch user。同样的,当fetch user的时候,也不会自动fetch report。 

  配置环境变量,npm i @nestjs/config, @nestjs/config内部使用dotenv。Dotenv的目的是, 把不同的环境变量(命令行定义的环境变量, .env 文件定义的环境变量)收集起来, 形成一个对象(process.env),返回给你。 如果各个方法定义的环境变量有冲突,命令行中定义的优先级高。

"start:dev": "cross-env ENV=dev nest start --watch",
"start:prod": "cross-env ENV=prod node dist/main",

 

 

A good approach for using this technique in Nest is to create a ConfigModule that exposes a ConfigService which loads the appropriate .env file. 

   @nestjs/config 提供了依赖注入的功能。 每一个环境不同的.env 文件,然后,configroot.forRoot() 加载不同的配置文件(命令行配置env环境变量),

  isGlobal全局模块。envFilePath加载哪一个环境变量配置文件。

    // my-service.service.ts
    import { Injectable } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';

    @Injectable()
    export class MyService {
      constructor(private readonly configService: ConfigService) {}

      getDatabaseHost(): string {
        return this.configService.get<string>('DATABASE_HOST');
      }
    } 

  日志的作用,记录错误并定位问题,比如什么时候发生的,发生了什么事情,错误是什么?日志的等级,waring,error。按照功能分,错误日志,调试日志,请求日志,通常记录到文件中,比较敏感的日志,记录到数据库中。生产环境通常用第三方日志库,Winston。

 

  VS Code 调试

  点击调试图标(Ctrl+Shift+D),简单的,面板中点击"Show all automatic debug configurations",  选Node.js...,弹出Run Script命令,选“Run Script: start:debug” 开启调试。复杂的,面板中点击“创建launch.json文件”,选Node.js,自己配置。configurations 数组中,输入npm,弹出提示

图片

   选择Node.js: Launch via npm

{
    "name": "Launch via NPM",
    "request": "launch",
    "runtimeArgs": [
        "run-script",
        "start:debug" // npm 要执行的命令
    ],
    "runtimeExecutable": "npm",
    "runtimeVersion": "18", // 如果是nvm切换过版本,要指定本项目使用的node版本
    "internalConsoleOptions": "neverOpen", // 要不要打开VS code 内置的debug console
    "skipFiles": [
        "<node_internals>/**"
    ],
    "type": "node"
}

 

posted @ 2024-08-30 18:38  SamWeb  阅读(160)  评论(0)    收藏  举报