在当前后端开发领域,随着业务复杂度的提升,对代码的可维护性、可扩展性和规范性要求越来越高。Node.js 生态下的 Express、Koa 等框架虽灵活轻便,但缺乏强约束性,在大型团队协作和企业级项目开发中容易出现代码混乱、架构松散等问题。NestJS 的出现恰好弥补了这一短板,它以 TypeScript 为基础,融合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(响应式编程)的思想,提供了一套完整的企业级后端开发解决方案。本文基于实操代码和核心概念,系统梳理 NestJS 的学习要点,助力快速掌握这一框架的使用。
一、NestJS 框架基础认知
1.1 框架定位与核心优势
NestJS 并非从零构建的全新框架,而是基于 Express(默认)或 Fastify 封装的企业级开发框架,其核心目标是解决大型后端应用的架构设计问题。与传统的 Express 框架相比,NestJS 具有以下显著优势:
- 强类型支持:基于 TypeScript 开发,提供完善的类型定义,减少运行时错误,提升代码可读性和维护性,尤其适合大型团队协作。
- 模块化架构:采用模块化设计思想,将应用拆分为多个独立模块,每个模块负责特定功能,实现高内聚低耦合。
- 依赖注入:内置依赖注入(DI)系统,实现组件间的解耦,便于单元测试和代码复用,符合面向对象设计原则。
- 丰富的生态集成:无缝集成 TypeORM、Sequelize 等 ORM 工具,支持 Redis、MongoDB 等数据库,同时兼容 Express 中间件,降低技术选型成本。
- 语义化路由:支持 RESTful API 设计规范,提供清晰的 HTTP 请求动作映射,使接口设计更具语义化。
- 装饰器模式:通过装饰器简化配置,使代码结构更清晰,减少冗余代码,提升开发效率。
需要注意的是,Express 更偏向于极简框架,适合快速开发小型应用或原型验证,而 NestJS 则聚焦于企业级场景,通过强约束带来架构上的规范性,代价是学习成本相对较高,但长期来看能显著提升项目的可维护性。
1.2 环境搭建与项目初始化
NestJS 提供了官方 CLI 工具,可快速搭建项目骨架、生成组件(模块、控制器、服务等),大幅提升开发效率。以下是完整的环境搭建步骤:
1.2.1 安装 CLI 工具
首先通过 npm 全局安装 NestJS CLI,确保 Node.js 版本在 16.x 及以上(推荐 18.x 版本):
npm i -g @nestjs/cli
安装完成后,可通过以下命令验证 CLI 是否安装成功:
nest --version
若输出具体版本号(如 10.3.2),则说明安装成功。
1.2.2 新建 NestJS 项目
使用 CLI 命令新建项目,执行后会提示选择包管理器(npm、yarn、pnpm),根据个人习惯选择即可:
nest new nest-test-demo
项目创建完成后,进入项目目录并启动开发服务器:
cd nest-test-demo
npm run start:dev
启动成功后,访问 http://localhost:3000,若页面显示 "Hello World!",则说明项目初始化正常。
1.2.3 项目目录结构解析
初始化后的项目目录结构如下(核心文件说明):
nest-test-demo/
├── src/
│ ├── app.controller.ts # 根控制器,处理路由请求
│ ├── app.service.ts # 根服务,处理业务逻辑
│ ├── app.module.ts # 根模块,应用入口模块
│ └── main.ts # 应用入口文件,启动 Nest 应用
├── .env # 环境变量配置文件(需手动创建)
├── package.json # 项目依赖与脚本配置
└── tsconfig.json # TypeScript 配置文件
后续开发中,我们会在 src 目录下新增模块(如 todos、database),每个模块对应独立的控制器、服务等文件,保持目录结构的规范性。
二、NestJS 核心概念解析
2.1 模块(Module)
模块是 NestJS 应用的基本构建块,用于组织代码、划分功能边界。每个应用至少有一个根模块(AppModule),所有其他模块都是功能模块。模块通过装饰器 @Module() 定义,该装饰器接收一个配置对象,包含 imports、controllers、providers、exports 四个核心属性。
2.1.1 模块配置属性详解
结合提供的 AppModule 代码,逐一解析各属性功能:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodosModule } from './todos/todos.module';
import { DatabaseModule } from './database/database.module';
@Module({
// 引入其他模块,使当前模块可使用引入模块的导出内容
imports: [TodosModule, DatabaseModule],
// 注册当前模块的控制器,负责处理路由请求
controllers: [AppController],
// 注册当前模块的服务,负责处理业务逻辑,可通过依赖注入供控制器使用
providers: [AppService],
// 导出当前模块的服务或令牌,供其他引入该模块的模块使用(可选)
// exports: [AppService]
})
export class AppModule {}
- imports:用于引入其他模块,实现模块间的依赖共享。例如,AppModule 引入了 TodosModule(待办事项模块)和 DatabaseModule(数据库模块),因此 AppController 可使用这两个模块导出的服务。
- controllers:用于注册控制器,控制器负责接收 HTTP 请求、校验参数、调用服务处理逻辑并返回响应。
- providers:用于注册服务(或其他可注入对象),服务是业务逻辑的核心载体,通过依赖注入机制供控制器或其他服务使用。
- exports:用于导出 providers 中的服务或令牌,使其他模块在引入当前模块后可使用这些内容。若模块无需对外提供服务,则可省略该属性。
2.1.2 全局模块
通常情况下,模块的服务仅能在当前模块及引入该模块的模块中使用。若需让某个模块的服务在整个应用中全局可用,可使用 @Global() 装饰器标记该模块(如 DatabaseModule):
import { Module, Global } from '@nestjs/common';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
@Global() // 标记为全局模块
@Module({
providers: [
{
provide: 'PG_CONNECTION',
useValue: new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432', 10),
})
}
],
exports: ['PG_CONNECTION'], // 导出令牌,供全局使用
})
export class DatabaseModule {}
全局模块只需在根模块中引入一次,即可在整个应用的任何模块中注入其导出的服务,无需重复引入,适合数据库连接、日志服务等全局通用的功能。
2.2 控制器(Controller)
控制器负责处理客户端发起的 HTTP 请求,定义路由规则,校验请求参数,调用服务层处理业务逻辑,并返回响应结果。控制器通过@Controller() 装饰器定义,支持设置路由前缀,使该控制器下的所有路由都带有该前缀。
2.2.1 控制器基础用法
结合提供的 AppController 代码,解析控制器的核心特性:
import { Controller, Get, Post, Body, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller() // 无路由前缀,控制器下的路由直接映射到根路径
export class AppController {
// 依赖注入:注入 PG_CONNECTION 令牌对应的对象和 AppService 实例
constructor(
@Inject('PG_CONNECTION') private readonly db: any,
private readonly appService: AppService
) {}
// @Get() 装饰器:处理 GET 请求,路由路径为根路径(/)
@Get()
getHello() {
console.log(this.db, '/////////')
return this.appService.getHello();
}
// 路由路径为 /welcome
@Get('welcome')
getWelcome() {
return this.appService.getWelcome();
}
// 处理 POST 请求,路由路径为 /login
@Post('login')
login(@Body() body: { username: string, password: string }) {
const { username, password } = body;
console.log(username, password);
// 参数校验逻辑
if(!username || !password) {
return {
code: 400,
message: '用户名或密码不能为空'
}
}
if(password.length < 6){
return {
code: 400,
message: '密码不能少于6位'
}
}
return this.appService.handleLogin(username, password);
}
}
2.2.2 路由装饰器
NestJS 提供了一系列 HTTP 方法装饰器,用于映射不同的请求动作,遵循 RESTful API 设计规范,核心装饰器如下:
@Get(path):处理 GET 请求,用于查询资源。@Post(path):处理 POST 请求,用于创建资源。@Put(path):处理 PUT 请求,用于全量更新资源(如上传/更新头像)。@Patch(path):处理 PATCH 请求,用于局部更新资源(如修改昵称、密码)。@Delete(path):处理 DELETE 请求,用于删除资源。
装饰器中的 path 参数为可选,若不设置则默认映射到控制器前缀(无前缀时为根路径)。例如,@Get('welcome') 对应的路由为 GET /welcome。
2.2.3 请求参数获取
控制器方法中可通过装饰器获取请求参数,常用参数装饰器如下:
@Body():获取 POST/PUT/PATCH 请求的请求体数据,支持指定字段名(如@Body('username'))。@Param():获取动态路由参数,如@Delete(':id')中的 id,可通过@Param('id')获取。@Query():获取 URL 查询参数(如/todos?page=1中的 page)。@Headers():获取请求头信息。@Request()/@Req():获取原生 Express 请求对象。@Response()/@Res():获取原生 Express 响应对象(使用时需注意关闭 Nest 自动响应机制)。
示例:在 TodosController 中通过 @Param('id', ParseIntPipe) 获取动态路由参数,并通过管道将其解析为整数:
import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common';
import { TodosService } from './todos.service';
@Controller('todos')
export class TodosController {
constructor(private readonly todosService: TodosService) {}
@Delete(':id')
deleteTodo(@Param('id', ParseIntPipe) id: number){
console.log(typeof id,'////'); // 输出 number
return this.todosService.deleteTodo(id);
}
}
2.3 服务(Service)
服务是 NestJS 中处理业务逻辑、数据操作的核心组件,通过依赖注入机制供控制器调用,实现“控制器负责请求处理,服务负责业务逻辑”的分离,符合 MVC 设计模式。服务通过 @Injectable() 装饰器定义,标记该类可被注入到其他组件中。
2.3.1 服务的基本用法
以 AppService 为例,解析服务的定义与使用:
import { Injectable } from "@nestjs/common";
@Injectable() // 标记为可注入服务
export class AppService {
// 简单的返回字符串方法
getHello(): string {
return '扣你吉瓦';
}
getWelcome(): string {
return '欢迎来到扣你吉瓦';
}
// 登录业务逻辑处理
handleLogin(username: string, password: string) {
if(username !== 'admin' || password !== '123456') {
return {
code: 400,
message: '用户名或密码错误'
}
}else{
return '登录成功';
}
}
}
服务类中的方法通常包含具体的业务逻辑,如数据校验、数据库操作、第三方接口调用等。控制器通过构造函数注入服务实例后,即可调用这些方法。
2.3.2 服务与控制器的关系
在 MVC 设计模式中,服务对应“模型(Model)”层的部分职责,负责数据处理和业务逻辑,控制器对应“控制器(Controller)”层,负责请求分发和响应处理。两者的关系如下:
- 客户端发起 HTTP 请求,路由匹配到对应的控制器方法。
- 控制器方法获取请求参数并进行初步校验。
- 控制器调用注入的服务实例的方法,传递参数并获取处理结果。
- 服务方法执行业务逻辑(如数据库查询、数据处理),返回结果给控制器。
- 控制器将结果整理为响应格式,返回给客户端。
这种分离模式使代码结构更清晰,便于单元测试(可单独测试服务的业务逻辑,无需依赖控制器)和功能复用(多个控制器可共用同一个服务)。
2.4 依赖注入(Dependency Injection, DI)
依赖注入是 NestJS 的核心设计模式,用于实现组件间的解耦。其核心思想是:当一个组件(如控制器)依赖另一个组件(如服务)时,无需手动创建依赖组件的实例,而是由 NestJS 容器自动创建并注入到依赖组件中。
2.4.1 依赖注入的实现方式
在 NestJS 中,依赖注入主要通过构造函数注入实现,常见形式有以下两种:
(1)服务实例注入
控制器注入服务实例时,直接在构造函数中声明服务类型,NestJS 会自动通过类型推断创建实例并注入:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
// 构造函数注入 AppService 实例
constructor(private readonly appService: AppService) {}
@Get()
getHello() {
return this.appService.getHello(); // 调用服务方法
}
}
其中,private readonly 是 TypeScript 语法,用于声明私有只读属性,确保服务实例仅能在控制器内部访问,且不可修改。
(2)令牌注入
当注入的对象不是类(如数据库连接池、配置对象)时,需通过令牌(Token)注入,使用 @Inject(Token) 装饰器指定令牌:
import { Controller, Get, Inject } from '@nestjs/common';
@Controller()
export class AppController {
// 通过令牌 PG_CONNECTION 注入数据库连接池
constructor(
@Inject('PG_CONNECTION') private readonly db: any,
private readonly appService: AppService
) {}
@Get('db-test')
async testConnection(){
try {
const res = await this.db.query('SELECT * from users');
return {
status: 'success',
data: res.rows,
}
}catch(err){
return {
status: 'error',
error: err.message,
}
}
}
}
令牌可以是字符串、Symbol 等,对应的对象需在模块的 providers 中注册,通过 provide 指定令牌,useValue/useFactory 等指定具体对象。
2.4.2 依赖注入的优势
- 解耦组件:依赖组件的创建和管理由容器负责,组件间无需硬编码依赖关系,便于替换和扩展。
- 便于测试:可通过模拟依赖对象(如模拟服务实例),单独测试目标组件的功能,无需依赖真实依赖。
- 代码复用:依赖对象可被多个组件共享,提升代码复用率。
- 集中管理:依赖对象的生命周期由 NestJS 容器统一管理,减少内存泄漏风险。
2.5 管道(Pipe)
管道是 NestJS 中的中间件组件,用于数据转换和数据校验。管道可在控制器方法执行前拦截请求数据,对数据进行转换(如将字符串 id 转换为整数)或校验,若数据不合法则抛出异常,阻止控制器方法执行。
NestJS 内置了多个常用管道,如 ParseIntPipe(将参数解析为整数)、ParseUUIDPipe(校验参数是否为 UUID)、ValidationPipe(基于 class-validator 进行数据校验)等。
示例:使用 ParseIntPipe 将动态路由参数 id 解析为整数:
import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common';
import { TodosService } from './todos.service';
@Controller('todos')
export class TodosController {
constructor(private readonly todosService: TodosService) {}
@Delete(':id')
deleteTodo(@Param('id', ParseIntPipe) id: number){
// 若 id 无法解析为整数,会自动返回 400 错误
return this.todosService.deleteTodo(id);
}
}
除了内置管道,还可自定义管道,实现复杂的数据校验或转换逻辑,满足业务需求。
三、RESTful API 设计与实现
3.1 RESTful API 核心规范
RESTful API 是一种基于 HTTP 协议的 API 设计风格,核心思想是“一切皆资源”,通过 HTTP 方法和 URL 路径的组合定义对资源的操作,具有语义化、无状态、可缓存等特点。NestJS 天然支持 RESTful API 设计,结合 HTTP 方法装饰器可快速实现规范的接口。
RESTful API 核心规范如下:
- 资源命名:URL 路径使用名词复数形式表示资源集合(如
/todos),单个资源使用/todos/:id表示。 - HTTP 方法语义:使用对应的 HTTP 方法表示对资源的操作,替代 URL 中的动词(如不用
/getTodos,而用GET /todos)。 - 状态码使用:合理使用 HTTP 状态码表示请求结果(如 200 表示成功、400 表示参数错误、404 表示资源不存在、500 表示服务器错误)。
- 响应格式统一:响应数据格式保持一致,通常包含状态码、消息、数据等字段(如
{ code: 200, message: 'success', data: [] })。
3.2 HTTP 请求动作实战
结合提供的 Todos 模块代码,实现一套完整的待办事项 RESTful API,涵盖增删改查操作。
3.2.1 定义 Todo 接口
在 TodosService 中定义 Todo 接口,规范数据结构:
import { Injectable } from '@nestjs/common';
// 定义 Todo 数据结构接口
export interface Todo {
id: number;
title: string;
completed: boolean;
}
@Injectable()
export class TodosService {
// 模拟数据库存储待办事项
private todos: Todo[] = [
{ id: 1, title: 'todo 1', completed: false },
{ id: 2, title: 'todo 2', completed: true },
{ id: 3, title: 'todo 3', completed: false },
];
}
3.2.2 实现服务层方法
在 TodosService 中实现对 Todo 资源的增删改查方法,处理业务逻辑:
@Injectable()
export class TodosService {
private todos: Todo[] = [
{ id: 1, title: 'todo 1', completed: false },
{ id: 2, title: 'todo 2', completed: true },
{ id: 3, title: 'todo 3', completed: false },
];
// 查询所有待办事项
findAll(): Todo[] {
return this.todos;
}
// 创建新的待办事项
addTodo(title: string): Todo {
const todo: Todo = {
id: +Date.now(), // 使用时间戳作为唯一 ID
title,
completed: false,
};
this.todos.push(todo);
return todo;
}
// 根据 ID 删除待办事项
deleteTodo(id: number) {
this.todos = this.todos.filter((todo) => todo.id !== id);
return {
message: 'delete success',
code: 200,
};
}
// 根据 ID 更新待办事项状态(扩展方法)
updateTodoStatus(id: number, completed: boolean): Todo | null {
const todo = this.todos.find(item => item.id === id);
if (!todo) return null;
todo.completed = completed;
return todo;
}
// 根据 ID 查询单个待办事项(扩展方法)
findOne(id: number): Todo | null {
return this.todos.find(item => item.id === id) || null;
}
}
3.2.3 实现控制器路由
在 TodosController 中定义路由,映射 HTTP 请求到服务层方法:
import {
Controller,
Get,
Post,
Body,
Delete,
Param,
ParseIntPipe,
Patch,
} from '@nestjs/common';
import { TodosService } from './todos.service';
import { Todo } from './todos.service';
@Controller('todos') // 路由前缀:/todos
export class TodosController {
constructor(private readonly todosService: TodosService) {}
// GET /todos:查询所有待办事项
@Get()
getTodos(): Todo[] {
return this.todosService.findAll();
}
// GET /todos/:id:查询单个待办事项
@Get(':id')
getTodoById(@Param('id', ParseIntPipe) id: number): Todo | null {
return this.todosService.findOne(id);
}
// POST /todos:创建待办事项
@Post()
addTodo(@Body('title') title: string): Todo {
return this.todosService.addTodo(title);
}
// PATCH /todos/:id/status:局部更新待办事项状态
@Patch(':id/status')
updateTodoStatus(
@Param('id', ParseIntPipe) id: number,
@Body('completed') completed: boolean
): Todo | null {
return this.todosService.updateTodoStatus(id, completed);
}
// DELETE /todos/:id:删除待办事项
@Delete(':id')
deleteTodo(@Param('id', ParseIntPipe) id: number) {
return this.todosService.deleteTodo(id);
}
}
3.2.4 接口测试
启动项目后,可通过 Postman、curl 等工具测试接口,验证功能是否正常:
- 查询所有待办事项:
GET http://localhost:3000/todos,返回预设的 3 条待办数据。 - 创建待办事项:
POST http://localhost:3000/todos,请求体为{ "title": "新待办" },返回创建的待办数据。 - 更新待办状态:
PATCH http://localhost:3000/todos/1/status,请求体为{ "completed": true },返回更新后的待办数据。 - 删除待办事项:
DELETE http://localhost:3000/todos/1,返回删除成功消息。
四、数据库集成实战
企业级应用中,数据库是核心组件之一。NestJS 支持多种数据库集成,本文以 PostgreSQL 为例,结合提供的 DatabaseModule 代码,实现数据库连接与操作。
4.1 环境准备
4.1.1 安装依赖
安装 PostgreSQL 驱动和 dotenv(环境变量管理):
npm install pg dotenv
npm install -D @types/pg
4.1.2 配置环境变量
在项目根目录创建 .env 文件,配置数据库连接信息:
DB_USER=postgres
DB_HOST=localhost
DB_NAME=nest_test
DB_PASSWORD=123456
DB_PORT=5432
4.1.3 创建数据库
通过 PostgreSQL 客户端(如 pgAdmin)创建数据库 nest_test,并创建 users 表用于测试:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
4.2 数据库模块实现
DatabaseModule 负责创建数据库连接池,并将其作为全局服务导出,供其他模块注入使用。代码解析如下:
import { Module, Global } from '@nestjs/common';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config(); // 加载 .env 环境变量
@Global()
@Module({
providers: [
{
provide: 'PG_CONNECTION', // 注入令牌
// 使用 useValue 创建数据库连接池实例
useValue: new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432', 10),
})
}
],
exports: ['PG_CONNECTION'], // 导出令牌,供全局注入
})
export class DatabaseModule {}
这里使用 PostgreSQL 的 Pool 类创建连接池,连接池可复用数据库连接,提升性能,避免频繁创建和关闭连接。
4.3 数据库操作实战
在 AppController 中注入数据库连接池,实现简单的查询操作,验证数据库连接是否正常:
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
@Inject('PG_CONNECTION') private readonly db: any, // 注入数据库连接池
private readonly appService: AppService
) {}
// 测试数据库连接与查询
@Get('db-test')
async testConnection(){
try {
// 执行 SQL 查询
const res = await this.db.query('SELECT * from users');
return {
status: 'success',
data: res.rows,
}
}catch(err){
return {
status: 'error',
error: err.message,
}
}
}
}
访问 http://localhost:3000/db-test,若返回 users 表中的数据,则说明数据库连接和查询正常。若出现错误,需检查环境变量配置、数据库服务是否启动以及表是否创建成功。
4.4 进阶:使用 TypeORM 操作数据库
上述示例使用原生 SQL 操作数据库,在复杂项目中效率较低且易出错。NestJS 推荐使用 TypeORM 等 ORM 工具,通过对象映射实现数据库操作,提升开发效率。
使用 TypeORM 集成 PostgreSQL 的步骤如下:
- 安装依赖:
npm install @nestjs/typeorm typeorm pg
npm install -D @types/node
- 修改 AppModule,配置 TypeORM:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './entities/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User], // 实体类
synchronize: true, // 开发环境下自动同步表结构(生产环境禁用)
logging: true, // 打印 SQL 日志
}),
TypeOrmModule.forFeature([User]), // 注册实体仓库
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- 创建 User 实体类:
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('users') // 映射到 users 表
export class User {
@PrimaryGeneratedColumn() // 自增主键
id: number;
@Column({ length: 50, unique: true }) // 用户名,唯一
username: string;
@Column({ length: 100 }) // 密码
password: string;
@CreateDateColumn({ name: 'created_at' }) // 创建时间
createdAt: Date;
}
- 在服务中注入实体仓库,实现数据库操作:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
// 创建用户
async create(username: string, password: string): Promise<User> {
const user = this.userRepository.create({ username, password });
return this.userRepository.save(user);
}
// 查询所有用户
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
}
通过 TypeORM,可避免编写原生 SQL,通过对象方法实现数据库操作,同时支持事务、关联查询等复杂功能,更适合企业级项目开发。
五、NestJS 核心设计模式应用
NestJS 融合了多种设计模式,这些模式贯穿于框架的核心组件中,理解并掌握这些模式有助于更好地设计和开发应用。
5.1 装饰器模式
装饰器模式是 NestJS 最核心的设计模式之一,用于在不修改类或方法源码的情况下,为其添加额外功能。NestJS 中的 @Module()、@Controller()、@Injectable()、@Get() 等均为装饰器。
例如,@Controller() 装饰器为普通类添加了路由处理功能,使其成为控制器;@Get() 装饰器为类方法添加了 HTTP GET 请求处理功能,映射到指定路由。
装饰器本质上是 TypeScript/JavaScript 的函数,通过反射机制获取类或方法的元数据,进而实现功能扩展。
5.2 工厂模式
工厂模式用于创建对象,隐藏对象创建的细节,使代码更具灵活性。在 NestJS 中,工厂模式主要通过 useFactory 实现,用于动态创建依赖对象。
示例:通过工厂函数创建数据库连接池,支持动态配置:
import { Module, Global } from '@nestjs/common';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
@Global()
@Module({
providers: [
{
provide: 'PG_CONNECTION',
// 工厂函数,动态创建连接池实例
useFactory: () => {
return new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432', 10),
});
},
}
],
exports: ['PG_CONNECTION'],
})
export class DatabaseModule {}
工厂函数还可注入其他依赖,实现更复杂的对象创建逻辑,例如根据环境变量切换不同的数据库配置。
5.3 MVC 设计模式
MVC(Model-View-Controller)是一种经典的软件架构模式,用于分离应用的不同职责。NestJS 中的 MVC 实现如下:
- Model(模型) :对应服务(Service)和数据实体,负责业务逻辑和数据处理。
- View(视图) :NestJS 主要用于后端 API 开发,视图层通常由前端负责,后端仅返回 JSON 数据。
- Controller(控制器) :负责接收请求、分发请求到服务层、返回响应结果。
这种模式使代码职责清晰,便于维护和扩展,尤其适合大型应用开发。
六、项目优化与最佳实践
6.1 代码规范
- 文件命名规范:采用“功能.类型.ts”的命名方式,如
app.controller.ts、todos.service.ts,清晰区分文件类型。 - 类命名规范:控制器类命名以
Controller结尾,服务类命名以Service结尾,模块类命名以Module结尾,如AppController、TodosService。 - 代码风格:使用 ESLint 和 Prettier 统一代码风格,避免语法错误和格式混乱。
6.2 错误处理
企业级应用中,统一的错误处理机制至关重要。NestJS 提供了全局异常过滤器,可捕获应用中的所有异常,统一返回错误响应:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { 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 status = exception.getStatus();
response
.status(status)
.json({
code: status,
message: exception.getResponse(),
timestamp: new Date().toISOString(),
});
}
}
在 main.ts 中注册全局异常过滤器:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter()); // 注册全局异常过滤器
await app.listen(3000);
}
bootstrap();
6.3 环境变量管理
使用 dotenv 管理环境变量,区分开发、测试、生产环境的配置,避免硬编码。同时,创建 .env.example 文件,记录环境变量的键名和说明,便于团队协作。
6.4 单元测试
NestJS 内置了 Jest 测试框架,支持单元测试和集成测试。以测试 AppService 为例:
import { Test, TestingModule } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let service: AppService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = module.get<AppService>(AppService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('getHello should return "扣你吉瓦"', () => {
expect(service.getHello()).toBe('扣你吉瓦');
});
it('handleLogin should return "登录成功" when username and password are correct', () => {
expect(service.handleLogin('admin', '123456')).toBe('登录成功');
});
});
运行 npm run test 执行测试,确保代码功能正常。
七、总结与展望
本文基于实操代码,系统梳理了 NestJS 的核心概念、使用方法和最佳实践,涵盖框架基础、模块、控制器、服务、依赖注入、RESTful API 设计、数据库集成等关键内容。NestJS 以其强类型支持、模块化架构和丰富的生态,成为企业级 Node.js 后端开发的首选框架之一。
后续学习中,可进一步探索 NestJS 的高级特性,如:
- 身份认证与授权(JWT、OAuth2.0)。
- 微服务架构(基于 gRPC、消息队列)。
- GraphQL 集成。
- 缓存策略(Redis)。
- API 文档生成(Swagger)。
通过不断实践和深入学习,可充分发挥 NestJS 的优势,构建高效、可扩展、易维护的企业级后端应用。