NestJS 是一个 NodeJS 的后端服务框架,它与传统的 NodeJS 框架不一样的是采用了控制反转(IOC)和依赖注入(DI)的模式进行开发,当然如果你对这中开发模式不熟悉的话也没关系,你可以查找资料去了解这种模式,或者直接开始阅读本篇文章你就会用 NestJS 了(会用就行)。接下来我们开始介绍 NestJS
全局安装 NestJS
npm i -g @nestjs/cli
第一个 NestJS 程序
nest new management_nest
执行完毕就会看到 src 下有这样一个目录
- app.controller.ts
这里控制层,这里主要是写路由相关代码以及处理前端传来的一些参数(后面文章会介绍如何接收参数)
import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } }
- app.service.ts
这里是业务层,在这里写一些与业务相关的逻辑。比如对数据库的 CRUD 就可以写到这里
import { Injectable } from "@nestjs/common"; @Injectable() export class AppService { getHello(): string { return "Hello World!"; } }
- app.module.ts
它可以组织应用程序中的许多功能,如控制器、服务以及可以导入其他模块等
import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {}
- main.ts
import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
main.ts 则是整个程序的入口文件
我们可以执行pnpm run start:dev
开启一个可以热更新的 NestJS 服务,浏览器打开http://localhost:3000/
便可发送一个 get 请求到app.controller.ts
中的 getHello 函数,然后再调用app.service.ts
里的 getHello 函数返回Hello World!
这里我们发现app.controller.ts
的 appService 并没有实例化就可以直接使用了,其实这里是因为在app.module.ts
已经进行了依赖注入(providers)这里已经将其处理好了
装饰器
在上面的代码中我们看到了譬如@Controller(),@get(),@Module()
之类的东西,其实这些东西就是装饰器,你可以把它看作一个函数就行了,比如@Controller()
它属于一个类装饰器,他会把下面的类当作参数传入然后进行一些处理从而实现处理路由的功能
为了更好的演示,我们新建一个模块,NestJS 给我们提供了一些命令可以创建对应文件,比如
- 生成一个 module (nest g mo) 。
- 生成一个 controller (nest g co) 。
- 生成一个 service (nest g s) 。
你可以执行nest -h
查看这些命令
我们可以执行nest g res user
生成一个 user 模块,包括它的module
,controller
,service
,执行之前可以在nest-cli.json
中配置
"generateOptions": { "spec": false }
让其不生成测试文件,执行命令我们可以选择 REST API 的形式,这样我们 src 下就会出现了 user 模块
我们可以看到user.controller.ts
引入了很多装饰器,已经给我们写好了 CRUD 的模板
import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.userService.create(createUserDto); } @Get() findAll() { return this.userService.findAll(); } @Get(':id') findOne(@Param() id: string) { return this.userService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.userService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.userService.remove(+id); } }
像@Post
,@Get
,@Patch
等就是对应的请求方式装饰器,比如你用 POST 请求调用http://localhost:3000/user
就会进入@Post()下面的 create()方法,@Body
,@Params
则是请求参数装饰器,我们可以从中获取到前端传来的参数
拿第一个Post
请求举例,发送 post 请求我们可以使用postman
,apifox
等工具进行测试,这里我使用apifox
进行演示
我们可以先打印一下前端@Body()
装饰的createUserDto
,然后发送一个 Post 请求
@Post() create(@Body() createUserDto: CreateUserDto) { console.log(createUserDto); return this.userService.create(createUserDto); }
此时我们就会发现控制台打印了前端传来的 Body 参数
如果想使用别的请求路径,可以在@Post 传入路径,比如@Post('list')
,请求路径就会变成/user/list
如果你想获取 Get 请求传来的参数可以使用@Query,获取 Header 中的参数可以使用@Header 等等,这些装饰器有很多这里就不过多介绍了,后面的教程中遇到会作一个详细说明
连接 MySql 数据库
作为一个后端框架肯定是离不开数据库的,NestJS 连接数据库其实很简单,可以先安装@nestjs/typeorm
和mysql
,typeorm
可以让对数据库的 sql 操作映射成对象的操作
pnpm install @nestjs/typeorm typeorm mysql -S
然后你需要在本地安装 mysql 数据库,这里可以自行百度~(注意记住你的用户名和密码以及数据库安装位置)
推荐一个 VSCode 数据库可视化插件Database Client
,安装完后连接我们的数据库就能进行一个可视化操作
一切准备就绪,我们新建一个数据库名为easyestadmin
,然后开始连接 mysql,我们来到app.module.ts
中进行数据库的配置,引入TypeOrmModule
调用forRoot
进行配置
import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { UserModule } from "./user/user.module"; import { TypeOrmModule } from "@nestjs/typeorm"; @Module({ imports: [ TypeOrmModule.forRoot({ type: "mysql", synchronize: true, autoLoadEntities: true, //自动加载实体 host: "localhost", port: 3306, // 端口号 username: "root", // 用户名 password: "root", // 密码 database: "management", //数据库名 synchronize: true, //是否自动同步实体文件,生产环境建议关闭 }), UserModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
当我们将autoLoadEntities
设置为 true 的时候,NestJS 会自动加载数据库实体文件xx.entity.ts
文件来创建数据表(如果没有的话),比如 user/entities/user.entity.ts,我们简单加一些字段
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; @Entity("user") export class User { @PrimaryGeneratedColumn("uuid") id: number; // 标记为主键,值自动生成 @Column({ length: 30 }) username: string; //用户名 @Column() password: string; //密码 }
启动项目,然后就会发现自动创建了一个 user 表
如果我们想对 user 表进行一些 CRUD 的操作.可以在user.module.ts
中导入
import { Module } from "@nestjs/common"; import { UserService } from "./user.service"; import { UserController } from "./user.controller"; import { User } from "./entities/user.entity"; import { TypeOrmModule } from "@nestjs/typeorm"; @Module({ controllers: [UserController], providers: [UserService], imports: [TypeOrmModule.forFeature([User])], }) export class UserModule {}
user.service.ts
中引入使用,比如在 create 函数中创建一条数据
import { Injectable } from "@nestjs/common"; import { CreateUserDto } from "./dto/create-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto"; import { User } from "./entities/user.entity"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User> ) {} async create(createUserDto: CreateUserDto) { return await this.userRepository.save(createUserDto); } async findAll() { return await this.userRepository.find(); } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } }
再调用 user 接口,传入 username 和 password
我们就完成了一条数据的插入,在 findAll 中查询所有数据,调用 get 请求便可拿到 user 表中的数据
一般来说数据库的配置包含了一些敏感信息不宜写在代码中提交到远程仓库,所以我们可以将配置写在配置文件中,然后提交 git 时候将生产环境的配置文件其忽略,这里我们新建
.env
和.env.prod
两个文件分别存放开发与生产环境配置
我们还安装 cross-env
来判断我们是处于什么环境
pnpm install cross-env
然后修改package.json
中的script
"start:prod": "cross-env NODE_ENV=production node dist/main",
这样我们生成环境的 NODE_ENV 就是 production 了,我们可以根据这个加载不同配置文件。想要加载配置文件,NestJS 给我们提供了@nestjs/config
,这个需要手动安装,安装完成之后,在app.module.ts
进行配置
import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { UserModule } from "./user/user.module"; import { TypeOrmModule } from "@nestjs/typeorm"; import { ConfigModule } from "@nestjs/config"; import * as path from "path"; const isProd = process.env.NODE_ENV == "production"; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: [isProd ? path.resolve(".env.prod") : path.resolve(".env")], }), TypeOrmModule.forRoot({ type: "mysql", host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), // 端口号 username: process.env.DB_USER, // 用户名 password: process.env.DB_PASSWD, // 密码 autoLoadEntities: true, //自动加载实体 synchronize: !isProd, //是否自动同步实体文件,生产环境建议关闭 database: process.env.DB_DATABASE, //数据库名 }), UserModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}