NestJS 企业级后端开发学习笔记

5 阅读21分钟

在当前后端开发领域,随着业务复杂度的提升,对代码的可维护性、可扩展性和规范性要求越来越高。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)”层,负责请求分发和响应处理。两者的关系如下:

  1. 客户端发起 HTTP 请求,路由匹配到对应的控制器方法。
  2. 控制器方法获取请求参数并进行初步校验。
  3. 控制器调用注入的服务实例的方法,传递参数并获取处理结果。
  4. 服务方法执行业务逻辑(如数据库查询、数据处理),返回结果给控制器。
  5. 控制器将结果整理为响应格式,返回给客户端。

这种分离模式使代码结构更清晰,便于单元测试(可单独测试服务的业务逻辑,无需依赖控制器)和功能复用(多个控制器可共用同一个服务)。

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 等工具测试接口,验证功能是否正常:

  1. 查询所有待办事项:GET http://localhost:3000/todos,返回预设的 3 条待办数据。
  2. 创建待办事项:POST http://localhost:3000/todos,请求体为 { "title": "新待办" },返回创建的待办数据。
  3. 更新待办状态:PATCH http://localhost:3000/todos/1/status,请求体为 { "completed": true },返回更新后的待办数据。
  4. 删除待办事项: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 的步骤如下:

  1. 安装依赖:
npm install @nestjs/typeorm typeorm pg
npm install -D @types/node
  1. 修改 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 {}
  1. 创建 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;
}
  1. 在服务中注入实体仓库,实现数据库操作:
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.tstodos.service.ts,清晰区分文件类型。
  • 类命名规范:控制器类命名以 Controller 结尾,服务类命名以 Service 结尾,模块类命名以 Module 结尾,如 AppControllerTodosService
  • 代码风格:使用 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 的优势,构建高效、可扩展、易维护的企业级后端应用。