什么是 NestJS
简单来说 NestJS 就是一个 NodeJS 服务端框架,它完全支持 TypeScript,并且有自己一套架构模式,开发者需要按照 NestJS 要求的架构来组织代码,而这套架构思想接近于传统后端的开发框架,所以作为一个完全的前端开发者理解起来相对来说会有些出入。下面是官网的介绍
Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify !
接下来就让我们走进 NestJS 的世界
第一个 NestJS 程序
作为一个 node 框架 NestJS 跑起来还是很容易的,npm 就完了~
npm i -g @nestjs/cli
然后
nest new project-name
这时候会让你选择一个安装工具(npm,yarn,pnpm),选择一个你喜欢的就行了,选择完毕之后等待你会项目就建好了,然后你就会看到这样的目录

有 Java 基础的同学一定会发出一声感叹: 哇!好 spring。先简单介绍一下这鸡哥文件:
-
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
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
return "Hello World!";
}
}
这里是业务层,在这里写一些与业务相关的逻辑。比如对数据库的 CRUD 就可以写到这里
-
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 {}
这里相当于一个应用程序的根模块,我们可以看到它将AppController
和AppService
都通过@Module
进行了一个注入
介绍完这些文件的作用,我们执行npm run start:dev
便可启动我们的项目,然后可以访问http://localhost:3000/
便可以以看到我们第一个接口的输出
这里简单说一下它的执行顺序,首先访问根路由则会进入到
app.controller.ts
中的 get 方法,即@Get()
修饰的方法getHello()
,然后调用this.appService.getHello()
进入app.service.ts
从而返回Hello World!
讲到这里或与有些小伙伴会有这样一个疑问,那就是在app.controller.ts
是如何直接调用this.appService
的?appService 并没有实例化啊! 好问题(假装是你们问的)。这里就要提一嘴 IOC(控制反转了),其实这个实例化操作交给了 IOC 容器,在app.module.ts
中,由 IOC 容器去获取 AppService,在调用的 AppController 的时候去帮我们自动注入,自动 new AppService。当然这些原理什么乱七八糟的不理解也没关系,毕竟我们才刚开始学 NestJS 吗(放不放弃还不知道呢)
装饰器
在上面的代码中我们看到了像什么@Controller()
,@get()
,@Module()
之类的东西,这些是什么玩意?以前好像没见过啊。这其实是 TS 中的装饰器,这里举个类装饰器的例子,聪明的你一定一看就理解了
const Get: ClassDecorator = (target: any) => {
target.prototype.type = "get";
};
@Get
class QueryData {
type: string;
constructor(public url: string) {}
}
const getData = new QueryData("xxxx");
console.log(getData.type); //get
从上面例子可以看出,类装饰器会将整个QueryData
类当成参数传递给Get
函数,在Get
对这个类进行一些处理。除了类装饰器,还用属性装饰器,方法装饰器等,这些大家可以自己查阅相关资料试一下
路由装饰器
接下来我们看一下 controller 中的装饰器,看它是如何利用装饰器来搞定这些前端过来的请求的。这里我们新建一个 user 模块,新建一个模块我们并不需要手动一个个再创建什么user.service.ts
,user.controller.ts
等文件,我们只需要一个命令nest g resource user
即可搞定,如果你不想生成测试文件,可以在nest-cli.json
中配置
"generateOptions": {
"spec": false
}
执行完毕后就自动生成了一个 user 文件夹,同时在app.module.ts
也进行了自动导入,也就是说创建完毕之后直接用就完了,当然 Cli 不止这一个生成全部文件的命令,下面列一下常用的命令
-
生成一个 module (nest g mo) 。 -
生成一个 controller (nest g co) 。 -
生成一个 service (nest g s) 。
来到user.controller.ts
中我们会发现它已经帮你写好了这些请求的示例
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') 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);
}
}
我们可以看到有很多装饰器,像@Controller(‘user’)定义的是请求路径user
,而@Get
,@Post
等这些就代表请求方式的装饰器,比如你用 POSt 请求调用http://localhost:3000/user
就会进入@Post()下面的 create()方法(这里你需要一个模拟请求的工具,比如 Apifox 或者 Postman 等),这里我使用 Apifox 进行模拟 post 请求,我们修改一下user.service.ts
中的create
函数
create(createUserDto: CreateUserDto) {
return {
code:200,
result:'请求成功'
};
}
我们就会发现我们的请求成功啦!

如果我们想要获取前端 Post 请求传过来参数,可以直接用@Body
装饰器即可,同样的 get 请求的话则使用@Query
,这里以 post 请求为例,我们回到user.controller.ts
中的 create 函数里
@Post()
create(@Body() createUserDto: CreateUserDto) {
console.log(createUserDto);
return this.userService.create(createUserDto);
}
前端 post 携带 body 请求

这时候控制台就会得到前端传来的参数,是不是非常的方便~

如果你想直接获得 body 中的 name,你可以直接
create(@Body('name') name: string) {
console.log(name);//小月
}
看到这有小伙伴就会问了CreateUserDto
干啥的,很简单,它是用来描述数据形状的,也就是说它可以定义应该接受前端传来的什么参数,参数类型等,比如在 create-user.dto.ts 中可以这样定义
export class CreateUserDto {
name: string;
}
很多情况下我们需要获取前端传过来的请求头,其实在 nestjs 中获取请求头也很简单,只需要一个 Headers 装饰器即可
@Post()
create(@Body() createUserDto: CreateUserDto, @Headers() headers) {
console.log(headers);
return this.userService.create(createUserDto);
}

关于装饰器还有很多,由于篇幅有限这里就不再过多介绍了,大家可以到官网自行查看呦~
连接 MySql 数据库
作为一个服务端框架那肯定离不开对数据库的操作,而 Nest 连接数据库以及操作数据库也是很简单的,毕竟我们有好用的工具啊,先安装
npm install @nestjs/typeorm typeorm mysql -S
然后在 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";
@Module({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "root",
database: "ceshi",
entities: ["dist/**/*.entity{.ts,.js}"],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
然后我们可以到 user/entities/user.entity.ts 中简单建一个用户表
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity("user")
export class UserEntity {
@PrimaryGeneratedColumn()
id: number; // 标记为主键,值自动生成
@Column({ length: 20 })
username: string;
@Column({ length: 20 })
password: string;
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
create_time: Date;
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
update_time: Date;
}
保存过后就会发现 user 表就被自动创建了

CRUD
接下来我们根据前端传来的参数对数据库进行一个简单的 crud 操作,注意这里只是演示,少了一些逻辑判断。 首先在user.module.ts
中导入数据库相关东西
import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { UserEntity } from "./entities/user.entity";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
来到create-user.dto.ts
定义一下接收前端传来的参数
export class CreateUserDto {
username: string;
password: string;
}
然后在user.service.ts
进行数据库数据的插入
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async create(createUserDto: CreateUserDto) {
this.userRepository.save(createUserDto);
return await this.userRepository.save(createUserDto);
}
}
在 ApiFox 模拟一个请求,会发现插入成功了

同时我们查看我们的 user 表出现了一条数据

我们再看一下查询,来到 user.service.ts 中的 findAll 方法
async findAll() {
return await this.userRepository.find();
}
因为 findAll 在 controller 中是 get 装饰,所以需要使用 get 请求

如果我们想加条件,可以这样写
async findAll() {
return await this.userRepository.find({ where: { id: 1 } });
}
对于数据的更新相对麻烦一点,比如我们想要更新一条数据可以这样写
在user.controller.ts
中我们需要接收前端传来的一个 id(动态路由传来的)以及 body
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
然后在user.service.ts
中的 update 方法中,根据 id 来更新前端传来的数据,注意这里用到了 TypeOrm 的查询构造器,想了解的可以到TypeOrm[2]查看
async update(id: number, updateUserDto: UpdateUserDto) {
const qb = await this.userRepository.createQueryBuilder();
return await qb.update().set(updateUserDto).where({ id }).execute();
}
我们可以使用Patch
方法在 apiFox 中试一下

我们可以看到数据更新成功啦~
最后看一下删除,其实和更新差不多
async remove(id: number) {
const qb = await this.userRepository.createQueryBuilder();
return await qb.delete().where({ id }).execute();
}
其实对于数据库操作的方法还有很多中,这里只是列举了其中的一小部分。想要熟练操作数据库还需要长期的日积月累才行
统一接口返回形式
通过上面的一通操作,细心的小伙汁肯定发现了上面接口返回的数据是个什么玩意.要状态没状态,要描述没描述的,这样拿给前端前端不得揍死你。所以为了把最好的留给前端,我们还需要对接口返回进行一个统一数据封装
一般接口返回的数据大致格式可能如下
{
data:业务参数,
code:状态码,
describe:状态描述
...
}
首先我们使用命令新建一个过滤器,用来抛出我们需要返回给前端的错误码以告知前端传来的是错误请求
nest g filter common/filter/http-exception
然后修改一下http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
code: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
在 main.ts 中注册
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { HttpExceptionFilter } from "./common/filter/http-exception/http-exception.filter";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//看这里看这里看这里~
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
这时候我们随便找个接口抛出一个错误码试一下,就你了findAll
方法
async findAll() {
throw new HttpException('错误的请求', 401);
return await this.userRepository.find();
}
然后我们在 ApiFox 试一下

然后我们再创建一个拦截器对请求成功的数据进行格式化
同样的使用
nest g interceptor common/interceptor/transform
创建一个拦截器,直接把官网示例抄过来transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
return next
.handle()
.pipe(map((data) => ({ code: 200, data, describe: "请求成功" })));
}
}
和过滤器一样在 main.ts 中注册
app.useGlobalInterceptors(new TransformInterceptor());
然后试一下查询接口

到这里我们就完成了返回结果的一个简单封装
写在最后
NestJS 的功能远远不止文章提到的这些,毕竟一个框架不是简单的一篇文章就能讲完的。就像本文标题一样,本篇文章旨在带领大家走进 NestJS 的大门,如果对 NestJS 感兴趣的小伙伴可以在以后的工作学习中慢慢探索,加油吧少年,祝你早日成为全干工程师!
需要文中代码的同学可以直接点击nest-template[3]获取
参考资料
https://juejin.cn/post/7207698564641996856/: https://juejin.cn/post/7207698564641996856/
[2]
https://typeorm.devjs.cn/update-query-builder: https://typeorm.devjs.cn/update-query-builder
[3]
https://github.com/qddidi/nest-template: https://github.com/qddidi/nest-template
原文始发于微信公众号(web前端进阶):你有一份NestJS入门指南,请查收~
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/232095.html