Nest.js基础入门

一、介绍

Nest 是一个用于构建高效,可扩展的Node.js服务器端应用程序的框架。它使用渐进式JavaScript,内置并完全支持TypeScript(但仍然允许开发人员使用纯 JavaScript编写代码)并结合了OOP(面向对象编程),FP(函数式编程)和FRP(函数式响应编程)的元素。在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和Fastify。Nest在这些框架之上提供了一定程度的抽象,同时也将其API直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。

二、安装与启动

1.全局安装脚手架
1
2
3
```
npm i -g @nestjs/cli
```
2.创建项目
1
2
cd /dirName
nest new project-name
3.启动项目

默认启动3000端口,可在主文件main.ts修改端口

1
2
cd nest-project
npm run start
4.快速打开项目
1
code .

小技巧:命令行快速打开vscode

  1. 手动打开vscode
  2. command + shift + p 打开命令面板(或者点击菜单栏 查看>命令面板)
  3. 输入shell(选择”install code command in PATH”)
  4. 打开终端 进入需要用IDE打开的文件夹 输入”code .”

三、基本使用

1.快速生成module,service,controller模块文件
  • folderName是在src下新生成的目录名
  • module.ts生成后会自动导入app.module.ts文件
  • 先生成module.ts,后面生成的controller.ts和service.ts会自动导入module.ts
  • 详细内容请看CLI
    1
    2
    3
    nest g mo -p src ${folderName}
    nest g co -p src ${folderName}
    nest g service -p src ${folderName}
2.小技巧(写自己一段生成三个模块文件的代码,写来玩玩)

新建一个文件config文件夹,把createFolder.js放进去

createFolder.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// eslint-disable-next-line @typescript-eslint/no-var-requires
const child_process = require('child_process');

if (process.argv.length === 2 ) {
throw new Error(
"名称不能为空"
);
}

const folderName = process.argv[2];
const cmd = `nest g mo -p src ${folderName} && nest g co -p src ${folderName} && nest g service -p src ${folderName}`

child_process.exec(cmd, function (error) {
if (error !== null) {
console.log('exec error: ' + error);
}
else console.log("finished");
})
package.json配置

"scripts":{
"createFolder": "node config/createFolder.js"
}

终端执行

1
npm run createFolder talks

执行结果

抛出异常

3.Controller控制器

控制器负责处理传入的 请求 和向客户端返回响应;
控制器所需的 @Controller()装饰器及请求方法装饰器如@Get(),@Post()可声明路由;
如下面例子,@Controller(‘talks’)与@Get(“getInfo”)会为请求生成路由映射 /talks/getInfo

talks.controller.ts

1
2
3
4
5
6
7
8
9
10
import { Controller, Get } from '@nestjs/common';

@Controller('talks')
export class TalksController {

@Get("getInfo")
getInfo(): any {
return { "name": "莫彩梅" }
}
}
4.Providers(service)提供者

Controller处理请求,把复杂的任务交给Providers处理,例如数据处理,数据库操作等。

talks.service.ts

1
2
3
4
5
6
7
8
9
import { Injectable } from '@nestjs/common';

@Injectable()
export class TalksService {

getInfo():any{
return "这是TalksService返回的内容"
}
}

在Controller中使用

talks.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Controller, Get } from '@nestjs/common';
import { TalksService } from "./talks.service"


@Controller('talks')
export class TalksController {

@Get("getInfo")
getInfo(): any {
return (new TalksService()).getInfo()
}
}

或者你可以将TalksService实例私有化

talks.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Controller, Get } from '@nestjs/common';
import { TalksService } from "./talks.service"

@Controller('talks')
export class TalksController {
constructor(private readonly talksService: TalksService){}

@Get("getInfo")
getInfo(): any {
return this.talksService.getInfo()
}
}

结果:

5.Module模块

模块构成应用程序

(1)根模块

根模块(必须),组织各种模块(如功能模块);
一般用cli命令生成的模块会自动导入到根模块中

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { ChatModule } from './chat/chat.module';
import { TalksModule } from './talks/talks.module';

@Module({
imports: [
UsersModule,
ChatModule,
TalksModule,
],
})
export class AppModule {}
(2)功能模块

负责处理某种功能的模块,例如上面例子的talks模块就算一个功能模块了吧;
一般用cli命令生成的模块同个目录下的controllers,providers(service)会自动导入到模块module文件中

1
2
3
4
5
6
7
8
9
import { Module } from '@nestjs/common';
import { TalksController } from './talks.controller';
import { TalksService } from './talks.service';

@Module({
controllers: [TalksController],
providers: [TalksService]
})
export class TalksModule {}

四、知识点

1.DTO(Data Transfer Object)数据传输对象

A DTO is an object that defines how the data will be sent over the network
展示层与服务层之间的数据传输对象,就是调用接口时传过来的数据

(1)users.dto.ts(定义数据传输对象)
1
2
3
4
5
6
export class UserInfoDto{
readonly name: string;
readonly lasttName: string;
readonly age: number;
readonly sex: string;
}
(2)users.controller.ts(在controller中使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
import { Post, Body } from '@nestjs/common';
import { UserInfoDto } from './dto/users.dto';

@Controller('users')
export class UsersController {

@Post('add')
async create(@Body() userInfoDto: UserInfoDto): string {
const { name, age } = userInfoDto;
console.log(name, age)
return ''
}
}
2.管道

拦截方法的调用参数,在执行方法前对参数进行转换或者验证
管道有两个类型:
转换:管道将输入数据转换为所需的数据输出
验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;

Nest内置管道
1
2
3
4
5
ValidationPipe
ParseIntPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
全局管道

用useGlobalPipes()方法全局管道,用于整个应用程序中的每个路由处理器

3.数据校验

class-validator对于接收到的数据进行判断校验(是否为空、类型是否正确、长度等等)

(1)安装依赖包
1
npm i --save class-validator class-transformer

ValidationPipe 需要同时安装 class-validator 和 class-transformer 包)

(2)users.dto.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { IsNotEmpty, IsString, IsInt, MaxLength  } from 'class-validator';

export class UserInfoDto{
@IsNotEmpty({ message: '名称不能为空' })
readonly name: string;

@IsString({ message: '必须的字符类型' })
readonly firstName: string;

@IsString({ message: '必须的字符类型' })
readonly lastName: string;

@IsInt({ message: '必须的整数' })
readonly age: number;

@MaxLength(5, {message: '长度不能超过5' })
readonly sex: string;
}
(3)局部验证

将管道实例绑定到路由参数装饰器@Body

users.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Post, Body, ValidationPipe } from '@nestjs/common';
import { UserInfoDto } from './dto/users.dto';

@Controller('users')
export class UsersController {

@Post('add')
async create(@Body(new ValidationPipe()) userInfoDto: UserInfoDto): string {
const { name, age } = userInfoDto;
console.log(name, age)
return ''
}
}
(4)全局验证

设置全局管道,用于整个应用程序、每个控制器和每个路由处理程序。
无需再局部绑定 。

main.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger, ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局使用管道
app.useGlobalPipes(new ValidationPipe());
await app.listen(3001,() => {
Logger.log('服务已经启动,请访问localhost:3000');
});
}
bootstrap();

局部与全局验证结果一致:

五、数据库

1.安装ORM库

ORM即对象-关系映射,可以不用写sql,直接操作数据库;
Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成。

1
npm install --save @nestjs/typeorm typeorm mysql
2.创建ormconfig.json

配置你要连接的数据库信息

1
2
3
4
5
6
7
8
9
10
11
12
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "*******",//填你的数据库密码
"database": "nest-test",
"entities": [
"dist/**/*.entity{.ts,.js}" //静态全局路径
],
"synchronize": true // 是否自动将实体类同步到数据库
}
3.创建实体类

实体类一般都与数据库中的表结构一一对应
表→类
字段→属性

(1)@Entity()

对应一个表,如果实体类名和表名不一致,可以在@Entity()注解中传入表名字段。

1
@Entity('new_user')
(2)@Column()

可以传入一个Object,可以指定字段的类型、大小等待属性

1
@Column({ type: "varchar", width: 200 });

如果想要实体名称与表字段名称不同, 设置name属性

user.entity.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
import { Entity, Column, PrimaryGeneratedColumn, Timestamp } from 'typeorm';

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

@Column()
name: string;

@Column()
firstName: string;

@Column()
lastName: string;

@Column()
age: number;

@Column()
sex: string;

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

updateAt: Timestamp;
}
4.模块注入
(1)单个数据库

TypeOrmModule.forRoot()在根模块使用,默认注入默认数据库连接模块
TypeOrmModule.forFeature()在子模块使用,定义应在当前范围中注册的存储库

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
imports: [
UsersModule,
TypeOrmModule.forRoot(),
],
})
export class AppModule {}

users.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';

@Module({
imports: [
TypeOrmModule.forFeature([User])
],
controllers: [UsersController],
providers: [UsersService]
})
(2)连接多个数据库

1.直接用ormconfig.json好像有点问题,因为forRoot接收的参数是TypeOrmModuleOptions,需要把数据库配置传完整,单单传name不能区分。不传的时候会读取默认的配置,详细可以看nest文档中的数据库描述
2.记得用name区分

app.module.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
30
31
32
33
34
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';

const ormconfig: any = {
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "*******",
"database": "nest-test",
"entities": [
"dist/**/*.entity{.ts,.js}"
],
"synchronize": true
}

@Module({
imports: [
UsersModule,
TypeOrmModule.forRoot(ormconfig),
TypeOrmModule.forRoot({
...ormconfig,
name: "connection2",
database: "nest-test2"
}),
],
// controllers: [AppController],
// providers: [AppService],
})
export class AppModule {
constructor(private readonly connection: Connection) { }
}

users.module.ts
用name区分注册实体到哪个连接,使用哪个库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';

@Module({
imports: [
TypeOrmModule.forFeature([User]),
TypeOrmModule.forFeature([User],'connection2')
],
controllers: [UsersController],
providers: [UsersService]
})
5.在service中使用,一些基本的数据库操作

@InjectRepository(User,’connection2’)装饰器—注入存储库,将 UsersRepository 注入到 UsersService中
使用哪个实体,哪个存储库

users.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Injectable } from '@nestjs/common';
import { UserInfo } from "./interfaces/users.interface";
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { UserInfoDto } from "./dto/users.dto"

@Injectable()
export class UsersService {
constructor(
@InjectRepository(User,'connection2')
private usersRepository: Repository<User>,
){}
private readonly users: UserInfo[] = []

async create(userInfoDto: UserInfoDto): Promise<User> {
return await this.usersRepository.save(userInfoDto);
}
}
(1)新增数据

typeorm封装好的save方法保存数据假如数据库没有user表,会帮你创建一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//service
async create(userInfoDto: UserInfoDto): Promise<User> {
return await this.usersRepository.save(userInfoDto);
}

//controller
@Post('add')
async create(@Body() userInfo: UserInfoDto): Promise<any> {
return this.usersService.create(userInfo);
}


//http://localhost:3001/users/add Post
//{"name": "mocai","firstName": "mo","lastName":"cai","age": 18,"sex": "女"}
(2)查找一条数据findOne
1
2
3
4
5
6
7
8
9
10
11
//service
async findOne(id): Promise<User> {
return await this.usersRepository.findOne({id});
}


//controller
@Get('findOne')
async findOne(@Query('id') id): Promise<any> {
return this.usersService.findOne(id);
}
(3)查找全部数据find
1
2
3
4
5
6
7
8
9
10
11
//service
findAll(): Promise<User[]> {
return this.usersRepository.find();
}


//controller
@Get('findAll')
async findAll(): Promise<any> {
return this.usersService.findAll();
}
(4)QueryBuilder或query原生sql查询
1
2
3
4
5
6
7
8
9
10
//query写法
selectmocai(): Promise<User[]> {
return this.usersRepository.query("SELECT * FROM user WHERE `name`='mocai'");
}


//QueryBuilder写法
selectmocai(): Promise<User[]> {
return this.usersRepository.createQueryBuilder("user").where("name = 'mocai'").getMany();
}
(5)typeorm更多操作

中文文档 Git Homepage Nest/typeorm