前言:欢迎来到本篇博客文章,小编是一个刚刚接触 Nest.js 框架的新手,想通过这篇掘金推文记下笔记,加深一下个人的学习印象和分享一下学习到的知识点,如有错误之处烦请各位掘友在评论区指正谢谢。
首先,学习一门技术之前,通常要了解这个技术是什么,用来做什么的啦。Nest.js 是一个基于 TypeScript 的渐进式后端框架,它提供了一种现代化、可扩展的方式来构建 Node.js 应用程序。无论你是有经验的前端开发人员希望扩展到后端领域,还是一个全新的编程爱好者,本文都将带你了解 Nest.js 的基本概念和入门要点。小编将会在本篇以及后续的不断地更新推文中和大家一起探索 Nest.js 的模块化架构、依赖注入、控制器和服务等核心概念,并通过简单易懂的示例代码帮助你快速上手并编写简单的接口数据。本文旨在为你提供一个友好的起点,让你能够自信地开始在 Nest.js 中构建你的第一个后端应用程序。让我们一起开始这个令人兴奋的 Nest.js 之旅吧!
一. 基础工程
创建项目
js
// 使用 [Nest CLI] 建立新项目非常简单。 在安装好 npm 后,
// 您可以使用下面命令在您的 OS 终端中创建 Nest 项目:
npm i -g @nestjs/cli
// project-name 为项目名称
nest new project-name
// 将会创建 `project-name` 目录, 安装 node_modules 和一些其他样板文件,
// 并将创建一个 `src` 目录,目录中包含几个核心文件。
src
├── app.controller.spec.ts 带有单个路由的基本控制器示例。
├── app.controller.ts 对于基本控制器的单元测试样例
├── app.module.ts 应用程序的根模块。
├── app.service.ts 带有单个方法的基本服务
└── main.ts 应用程序入口文件。它使用 `NestFactory` 用来创建 Nest 应用实例。
// 安装过程完成后,您可以在系统命令行工具中运行以下命令,以启动应用程序
npm run start
二. 模块化架构
2.1 概念:模块化架构是 Nest.js 框架的一个核心概念,它帮助开发者将应用程序划分为独立的、可重用的模块,以提高代码的组织性、可维护性和可扩展性。每个模块都有自己的功能范围,可以包含控制器、服务、管道、中间件和其他相关的组件。
2.2 优势:
- 代码组织:模块化架构使得项目结构清晰明了,可以将相关的功能组织在一起,使代码更易于理解和维护。
- 可重用性:模块可以被多个应用程序和其他模块重用,提高了代码的可重用性和可测试性。
- 解耦和解藕:模块之间的依赖关系可以通过模块化架构进行解耦和解藕,使得修改一个模块不会对其他模块造成影响。
2.3 假设我们正在开发一个博客应用程序,我们可以将应用程序划分为以下几个模块
js
1. `UserModule`(用户模块):该模块负责处理与用户相关的功能,例如用户的注册、登录、个人资料管理等。
它可以包含 `UserController`(用户控制器)和 `UserService`(用户服务)等组件。
2. `PostModule`(文章模块):该模块负责处理与文章相关的功能,例如文章的创建、编辑、删除等。
它可以包含 `PostController`(文章控制器)和 `PostService`(文章服务)等组件。
3. `CommentModule`(评论模块):该模块负责处理与评论相关的功能,例如评论的添加、删除、获取等。
它可以包含 `CommentController`(评论控制器)和 `CommentService`(评论服务)等组件。
2.4示例
js
// 通过如下命令实现 cats 模块
nest g controller cats
// 通过生成 `cats` 控制器,Nest.js 创建了一个包含控制器的模块。这个模块可以包含其他与猫咪功能相关
// 的组件,例如服务、提供者、DTO(数据传输对象)等。我们可以根据需要在 `cats` 模块中添加其他组件,
// 以满足应用程序的需求。
2.5 总结
-
每个模块都有自己的职责范围,并且它们可以独立开发、测试和部署。在应用程序的主模块中,我们可以将这些模块进行导入和配置,以建立它们之间的关系。
-
通过模块化架构,我们能够更好地组织应用程序的代码,并提供了一种灵活的方式来扩展和维护应用程序。每个模块都可以专注于特定的功能领域,使得开发变得更加模块化和可扩展。
三. 依赖注入
3.1 概念: 依赖注入(Dependency Injection,简称 DI)是一种设计模式和编程技术,用于实现模块之间的解耦和组件的可替换性。在 Nest.js 中,依赖注入是一种核心的概念,用于管理和注入模块之间的依赖关系。
3.2 下面是一些详细的讲解关于依赖注入模块的重要概念:
js
1. 提供者(Providers):提供者是依赖注入模块中的组件,它们负责创建和提供依赖项的实例。
在 Nest.js 中,提供者可以是类、工厂函数、常量或外部模块的引用。提供者可以在模块中
通过 `providers` 属性进行注册。
2. 注入器(Injector):注入器是依赖注入模块的核心,它负责创建和管理依赖项的实例,并将
它们注入到需要它们的地方。注入器根据提供者的定义,自动解析和实例化依赖项,并在需要
时将它们注入到构造函数、方法参数或属性中。
3. 模块定义(Module Definition):依赖注入模块通过使用 `@Module()` 装饰器进行定义。
模块定义包括模块的元数据和配置,例如要导入的其他模块、要导出的提供者列表以及要提供
的全局作用域。
4. 导入(Imports):模块可以通过 `imports` 属性导入其他模块,以便共享其提供者和功能。
这样可以构建出具有层次结构的模块体系,使应用程序更加模块化和可扩展。
5. 导出(Exports):模块可以通过 `exports` 属性导出提供者,以便其他模块可以访问和使用
这些提供者。导出的提供者可以在其他模块中进行注入和使用,促进了模块之间的解耦和组件的可重用性。
3.2 示例
js
1. 图一通过将 `@Injectable()` 装饰器应用在 `ResponseInterceptor` 类上,你明确地将这个类标记
为一个可注入的提供者,符合依赖注入的思想。这样,Nest.js 就可以正确地创建和
管理 `ResponseInterceptor` 类的实例,并将其注入到其他需要它的地方。
`这样,所有经过这个拦截器的响应数据都会被统一格式化,包含状态码、消息和原始数据。`
2. 图二使用了 Nest.js 提供的 `@Injectable()` 装饰器来标记 `LoggingInterceptor` 类作为一个
可注入的提供者。
`这个拦截器的主要作用是在处理请求之前,记录请求的方法和 URL,以便进行日志记录和跟踪。`
3. 图三. 在代码示例中使用了依赖注入的概念。在`AppModule`类的`providers`数组中,提供了两个拦截器
作为依赖注入的提供者(Providers)。使用`provide`属性来指定提供者的令牌,这里使用了`APP_INTERCEPTOR`常量,
它是Nest.js提供的一个内置令牌,用于标识应用程序级别的拦截器。然后,我使用`useClass`属性指定
了要注入的实际类,分别是`ResponseInterceptor`和`LoggingInterceptor`。
4。 通过这样的配置,Nest.js将会在需要拦截器的地方自动注入这两个拦截器的实例。
例如,在图三的 `controllers`数组中列出的`AppController`和`CatsController`,如果
它们在请求处理过程中需要拦截器,Nest.js将会注入这两个拦截器的实例。
四. 控制器
4.1 概念:控制器(Controller)是 Nest.js 框架中的一个核心概念,它用于处理接收到的请求并生成响应。控制器作为应用程序的一部分,负责处理特定的路由和请求,以及协调其他组件(如服务、拦截器等)来完成请求的处理和生成响应。
4.2 作用:控制器的主要作用是定义路由处理程序,也就是用于处理特定路由的函数或方法。通过使用装饰器来标记控制器类和控制器方法,Nest.js 可以自动将路由与相应的处理程序关联起来,并在接收到请求时调用适当的处理程序。
js
以下是控制器的一些作用的详细说明:
1. 处理 HTTP 请求:控制器可以用于处理不同的 HTTP 请求方法(如 GET、POST、PUT、DELETE 等)和路由。
通过定义不同的路由处理程序,控制器可以根据接收到的请求执行相应的操作。例如,个 `UserController`
控制器可以定义 `GET /users` 路由来获取用户列表,以及 `POST /users` 路由来创建新用户。
2. 调用服务:控制器可以协调调用其他服务来完成业务逻辑的处理。通过将服务注入到控制器的构造函数中,
并在控制器方法中使用这些服务,可以实现对数据的处理、业务逻辑的执行等。例如,在上述的用户列表
示例中,`UserController` 控制器可以调用一个 `UserService` 来获取用户数据、创建新用户等操作。
3. 返回响应:控制器负责生成并返回适当的响应给客户端。通过在控制器方法中使用 `@HttpCode`、
`@Headers`、`@Redirect`、`@Res` 等装饰器来设置响应的状态码、头部信息、重定向等。
控制器方法可以返回普通对象、Promise、Observable 等,Nest.js 会根据返回值自动处理
并生成最终的响应。
让我们通过一个示例来说明这一点:(重点是理解路由与相应的处理程序之间的关系,就和我们在 Vue 或者 react 中理解的路由一样,它是一种映射关系,不同的路由(接口)就会对应不同的处理逻辑(接口逻辑))
js
import { Controller, Get, Post } from '@nestjs/common';
@Controller('users')
export class UserController {
@Get()
getUsers() {
return 'Get users';
}
@Post()
createUser() {
return 'Create user';
}
}
在上述示例中,我们创建了一个名为 UserController 的控制器类,并使用 @Controller('users') 装饰器指定了该控制器处理的路由前缀为 /users。
控制器类中定义了两个方法:getUsers() 和 createUser()。我们使用 @Get() 装饰器标记了 getUsers() 方法,表示它处理 GET 请求。同样,我们使用 @Post() 装饰器标记了 createUser() 方法,表示它处理 POST 请求。
现在,假设应用程序收到了一个 GET 请求,路径为 /users。Nest.js 将会自动调用 UserController 中的 getUsers() 方法来处理该请求。该方法返回字符串 'Get users',这将作为响应发送回客户端。
同样,如果收到一个 POST 请求,路径也为 /users,Nest.js 将会调用 UserController 中的 createUser() 方法来处理该请求。该方法返回字符串 'Create user',作为响应返回给客户端。
通过这种方式,控制器定义了路由处理程序,指定了相应的请求方法和路由路径。当应用程序接收到匹配的请求时,Nest.js 会自动调用相应的方法来处理请求并生成响应。这样,我们可以通过控制器来定义和组织应用程序的路由处理逻辑。
五. 编写简单的接口逻辑
5.1 以下是小编实现的一个控制器,里面包含一些接口,并定义了相关请求的返回数据
5.2 分析:
js
1. 控制器装饰器:`@Controller('cats')`
- 这个装饰器将控制器类 `CatsController` 与 `/cats` 路径关联起来。也就是说,
该控制器将处理与 `/cats` 路径相关的请求。
1. 路由处理方法:
- `create()` 方法:使用 `@Post()` 装饰器标记,处理 POST 请求到 `/cats` 路径。
它接收一个 `createCatDto` 对象作为请求体,并使用 `uuidv4()` 生成一个新的唯一 ID。
然后,它返回一个包含新创建的猫咪对象的响应。
- `findOneByQuery()` 方法:使用 `@Get()` 装饰器标记,处理 GET 请求到 `/cats/id=18`(18只是
一个示例,表示以这种格式上传的意思) 的路径。它使用 `@Query('id')` 装饰器获取查询参数
`id` 的值,并返回一个包含查询到的猫咪对象的响应。
- `findOneByParam()` 方法:使用 `@Get(':id')` 装饰器标记,处理 GET 请求到 `/cats/{id}` 路
径。它使用 `@Param('id')` 装饰器获取路径参数 `id` 的值,并返回一个包含查询到的猫咪对象的响应。
- `findAll()` 方法:使用 `@Get()` 装饰器标记,处理 GET 请求到 `/cats` 路径。它返回一个包含
字符串 `'hello World!!!'` 的响应。
- `update()` 方法:使用 `@Put(':id')` 装饰器标记,处理 PUT 请求到 `/cats/{id}` 路径。它
使用 `@Param('id')` 装饰器获取路径参数 `id` 的值,并使用 `@Body()` 装饰器获取请求体中
的数据。然后,它返回一个包含更新后的猫咪对象的响应。
- `remove()` 方法:使用 `@Delete(':id')` 装饰器标记,处理 DELETE 请求到 `/cats/{id}` 路径。
它使用 `@Param('id')` 装饰器获取路径参数 `id` 的值,并返回一个包含被删除的猫咪对象的响应。
1. 响应处理:
- 在每个路由处理方法中,使用 `res.status(HttpStatus.XXX).json(data)` 将包含状态码、
消息和数据的响应发送给客户端。这里使用了 `HttpStatus` 枚举来设置状态码。
5.3 cats.controller.ts的完整代码以及小编在试错过程中加入的一些必要注释
js
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
HttpStatus,
Res,
Query,
} from '@nestjs/common';
import { CreateCatDto, UpdateCatDto } from './create-cat.dto';
import { Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
@Controller('cats')
export class CatsController {
@Post()
// @HttpCode(200) //子定义响应状态码
// @Header('Cache-Control', 'none') //子定义响应头
async create(
@Body() createCatDto: CreateCatDto,
@Res({ passthrough: true }) res: Response,
) {
console.log('createCatDto', createCatDto);
const cat = {
id: uuidv4(), // 生成一个新的唯一ID
...createCatDto,
};
const response = {
statusCode: HttpStatus.CREATED,
message: 'Cat created successfully',
data: cat,
};
res.status(HttpStatus.CREATED).json(response);
// 调用 res.status().json() 方法时,它会设置响应的状态码并将数据以 JSON 格式发送给客户端。
// 在这种情况下,不需要显式地使用 return 语句,因为框架会负责处理响应并结束请求。
// return res.status(HttpStatus.CREATED).json(response);但是也可以这么写
}
//使用的是查询参数 id,那么可以继续使用 @Query() 装饰器来获取查询参数的值
//比如请求路径是 http://localhost:3000/cats?id=18
@Get('/query')
findOneByQuery(@Query('id') id: string, @Res() res: Response) {
const cat = `This action returns a #${id} cat by query`;
res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
message: '处理成功',
data: cat,
});
}
//使用的是路径参数 id,那么你可以使用 @Param() 装饰器来获取查询参数的值
// 比如请求路径是http://localhost:3000/cats/18
@Get(':id')
findOneByParam(@Param('id') id: string, @Res() res: Response) {
const cat = `This action returns a #${id} cat by param`;
return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
message: '处理成功',
data: cat,
});
}
//1.路径参数的使用场景:
// 标识资源:路径参数通常用于标识和定位特定的资源。它们可以用于表示资源的唯一标识符
// 或标识符的一部分。例如,/cats/{id} 表示获取 ID 为 {id} 的猫咪资源。
// 嵌套资源:当资源之间存在层级关系时,路径参数可以用于指定父资源和子资源之间的关系。
// 例如,/users/{userId}/posts/{postId} 表示用户 ID 为 {userId} 的用户发布的帖子中
// 的 ID 为 {postId} 的帖子。
// 2. 查询参数的使用场景:
// 过滤和排序:查询参数通常用于对资源进行筛选、过滤和排序。例如,/cats?color=black&size=small
// 表示获取颜色为黑色且尺寸为小的猫咪资源。
// 分页:查询参数可以用于分页结果集,允许客户端指定页码和每页的数量。
// 例如,/cats?page=2&limit=10 表示获取第二页的猫咪资源,每页显示 10 条。
// 参数化查询:查询参数可以用于传递参数化查询的参数。例如,/search?q=keyword
// 表示执行针对关键字 keyword 的搜索操作。
// 总的来说,路径参数用于标识和定位资源,而查询参数用于对资源进行筛选、过滤、排序
// 和参数化查询。路径参数通常出现在 URL 的路径中,而查询参数则出现在 URL 的查询字
// 符串中。在设计 API 时,根据具体的需求和语义来选择使用路径参数或查询参数。
@Get()
findAll(@Res() res: Response) {
// console.log('query', query);
const data = 'hello World!!!';
res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
message: '处理成功',
data,
});
}
@Put(':id')
update(
@Param('id') id: string,
@Query('name') name: string,
@Query('age') age: number,
@Body() updateCatDto: UpdateCatDto,
@Res() res: Response,
) {
console.log('name', name);
console.log('age', age);
console.log('updateCatDto', updateCatDto);
// 根据需要执行修改数据的操作
const cat = `This action updates a #${id} cat`;
return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
message: '处理成功',
data: cat,
});
}
@Delete(':id')
remove(@Param('id') id: string, @Res() res: Response) {
const cat = `This action removes a #${id} cat`;
return res.status(HttpStatus.OK).json({
statusCode: HttpStatus.OK,
message: '处理成功',
data: cat,
});
}
}
5.4 接口测试:
启动服务:默认在 http://localhost:3000/ 运行
post接口测试:
get接口测试(带有查询参数):
get接口测试(带有路径参数):
get接口测试(不带参数,常用于返回所有数据):
put接口测试:
delete接口测试: