从前端到全栈:NestJS 入门实战指南

6 阅读9分钟

🌟 NestJS 全栈入门实战:从前端思维到后端架构的无缝跃迁

写给正在尝试后端开发的你
如果你熟悉 React、Vue 或 Angular,那么 NestJS 就是你通往全栈世界的“舒适区延伸”。它用你熟悉的模块化、类、装饰器等概念,构建出结构清晰、类型安全、可维护性强的后端服务。本文将手把手带你从零搭建一个带数据库的待办事项(Todos)API,并深入剖析 NestJS 的核心思想——尤其是其赖以运转的两大基石:装饰器元编程依赖注入系统


为什么选择 NestJS?—— 前端开发者的“后端方言”

想象一下:你在前端写 Vue 组件时,会把逻辑拆成 script、模板拆成 template、样式拆成 style;在 React 中,你会用自定义 Hook 封装逻辑,用 Context 管理状态。这种“分而治之”的思想,正是 NestJS 的灵魂。

NestJS 并不是凭空造轮子,而是站在巨人的肩膀上:

  • 底层:基于 Express(或 Fastify),兼容所有中间件。
  • 语法:完全使用 TypeScript,享受强类型带来的安全感。
  • 架构:借鉴 Angular 的依赖注入与模块系统,让大型项目也能井井有条。

💡 比喻:如果说 Express 是“手工打造的木屋”,灵活但需要自己钉每一颗钉子;那么 NestJS 就是“预制装配式建筑”——你只需按图纸拼装模块,就能快速建成一栋结构稳固的大楼。

安装 NestJS


首先,确保你已安装 Node.js(建议 v18+)。然后全局安装 NestJS CLI:

npm install -g @nestjs/cli

创建新项目:

nest new your_project_name

初始项目结构如下:

src/
├── main.ts
├── app.module.ts
├── app.controller.ts
├── app.service.ts

启动项目

npm run start:dev

项目骨架解析:从入口到根模块

main.ts —— 应用的“启动按钮”

这是整个应用的起点,就像汽车的点火开关:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  // 创建一个基于 AppModule 的 Nest 应用实例
  const app = await NestFactory.create(AppModule);
  
  // 启动 HTTP 服务器,监听 3000 端口(或从环境变量读取)
  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();
  • NestFactory.create(AppModule):告诉 NestJS,“请以 AppModule 为蓝图,构建整个应用”。
  • 这里还可以添加全局中间件、异常过滤器、Swagger 文档等,但我们先保持简洁。

🔍 小知识?? 是 ES2020(ES11)引入的空值合并运算符,只有当左边是 nullundefined 时才使用右边的默认值,比 || 更安全(不会把 0''false 当作假值)。


app.module.ts —— 应用的“中央调度室”

如果说 main.ts 是点火开关,那 AppModule 就是整栋大楼的总配电箱,决定哪些功能模块通电运行。

// src/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 {}
  • imports:声明本模块依赖的其他模块(类似 Vue 的 components 或 React 的 import)。
  • controllers:对外暴露的 HTTP 接口入口。
  • providers:内部可被注入的服务(业务逻辑容器)。

🏗️ 比喻AppModule 就像一栋写字楼的物业中心。它不直接提供咖啡(业务),但它知道哪一层有咖啡厅(TodosModule),哪一层有健身房(未来可能的 UsersModule),并确保水电(数据库连接)畅通。


分层架构详解:Controller、Service、Module 三剑客

NestJS 的核心哲学是 “职责分离” 。我们以默认生成的 AppControllerAppService 为例,看看它们如何协作。

Controller —— “前台接待员”

Controller 负责接收请求、解析参数、调用服务、返回响应。它不处理任何业务逻辑,只做“传话筒”。

// src/app.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { AppService } from './app.service';

@Controller() // 路由前缀为空,即根路径 `/`
export class AppController {
  // 通过构造函数注入 AppService 实例
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    // 直接调用服务方法,不关心内部实现
    return this.appService.getHello();
  }

  @Post('login')
  login(@Body() body: { username: string; password: string }) {
    const { username, password } = body;
    
    // 简单参数校验(实际项目应使用 DTO + ValidationPipe)
    if (!username || !password) {
      return '用户名或密码不能为空';
    }

    // 将请求“转交”给 Service 处理
    return this.appService.handleLogin(username, password);
  }
}

👩‍💼 比喻:Controller 就像酒店前台。客人(客户端)说“我要登录”,前台不会自己去查房卡系统,而是打电话给后台(Service):“请帮这位客人验证身份”,然后把结果告诉客人。


Service —— “后厨大厨”

Service 是业务逻辑的核心。所有数据计算、规则判断、数据库操作都发生在这里。

// src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable() // 标记该类可被 Nest 容器管理(即可注入)
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }

  handleLogin(username: string, password: string): string {
    // 纯粹的业务规则:硬编码账号(仅演示!)
    if (username === 'admin' && password === '123456') {
      return '登录成功';
    }
    return '登录失败';
  }
}

👨‍🍳 比喻:Service 就像后厨大厨。他不管客人是谁、从哪来,只专注于“这道菜怎么做”。即使前台换人了(Controller 重构),只要菜单不变,后厨照常工作。


Module —— “功能集装箱”

Module 是组织单元,它把相关的 Controller 和 Service 打包在一起,形成一个可复用、可测试的“功能块”。

// src/app.module.ts(节选)
@Module({
  imports: [TodosModule, DatabaseModule],
  controllers: [AppController],
  providers: [AppService],
})
  • 没有被 @Module 声明的类,NestJS 根本“看不见”。
  • 模块之间通过 imports 显式声明依赖,避免隐式耦合。

📦 比喻:Module 就像乐高积木。每个积木(模块)内部结构完整(含 Controller + Service),你可以自由拼接(imports),但不能强行掰开(破坏封装)。


 NestJS 的两大核心机制:装饰器 & 依赖注入

作为前端开发者,你不需要成为框架作者,但你需要理解 NestJS 是如何“自动工作”的。关键就靠两个机制:装饰器 和 依赖注入。它们就像 NestJS 的“自动驾驶系统”——你只管写业务逻辑,剩下的交给框架。


装饰器(Decorators):告诉框架“这是什么”

在 NestJS 中,装饰器的作用只有一个:给代码打标签
这些标签告诉框架:“这个类是控制器”、“这个方法响应 GET 请求”、“这个参数来自请求体”。

常见装饰器及其用途:

表格

装饰器作用举个栗子 🌰
@Module()标记一个类是“模块”“这是一个功能包,包含哪些控制器和服务”
@Controller('todos')标记一个类是“控制器”“所有接口都挂到 /todos 下”
@Get()@Post()标记一个方法处理哪种 HTTP 请求@Get() → 响应 GET /todos
@Body()告诉框架:“这个参数从请求体里取”自动把 {title: "买牛奶"} 赋值给 body
@Injectable()标记一个类可以被“自动提供”“这个服务能被其他地方直接用”

✅ 你只需要记住
装饰器 = 声明式指令
你不用写 router.get('/todos', handler),只要在方法上写 @Get(),NestJS 就自动帮你注册路由。

💡 类比前端
就像你在 Vue 中写 @Component({ name: 'MyButton' }),或在 Angular 中写 @Component({...}) —— 你不是在“调用”功能,而是在“描述”这个类的角色。


依赖注入(DI):自动把需要的东西“送上门”

想象你要做一道菜(比如红烧肉),你需要:

  • 五花肉
  • 酱油
  • 冰糖

传统方式:你自己去超市买、去厨房拿。
NestJS 方式:你只说“我需要五花肉、酱油、冰糖”,厨房(框架)自动把它们摆在你面前。

在代码中怎么体现?
// todos.controller.ts
@Controller('todos')
export class TodosController {
  // 只要声明“我需要 TodosService”
  constructor(private readonly todosService: TodosService) {}
  
  @Get()
  getTodos() {
    return this.todosService.findAll(); // 直接用!
  }
}

✅ 你只需要记住

  • 在 @Module({ providers: [TodosService] }) 中注册服务。
  • 在需要的地方(如 Controller)声明类型
  • NestJS 会自动创建实例并传给你,无需 new TodosService()

🎯 它解决了什么问题?

  • 解耦:Controller 不关心 Service 怎么创建、有没有数据库连接。
  • 复用:多个 Controller 可以共享同一个 Service 实例。
  • 测试友好:写单元测试时,可以轻松替换成假的(mock)Service。

💡 类比前端
就像 React 中通过 useContext(AuthContext) 获取全局状态,你不用自己管理状态来源,React 自动给你。NestJS 的 DI 就是后端版的“自动上下文注入”。


总结一句话:

  • 装饰器 → 告诉 NestJS “这是什么”(角色声明)
  • 依赖注入 → 让 NestJS “自动给我用”(能力交付)

你专注写业务逻辑,NestJS 负责把一切串联起来。这就是现代框架的优雅之处。

实战:开发一个完整的 Todos 模块

现在,我们按照上述模式,从零实现一个待办事项功能。

步骤 1️⃣:创建 Service —— 定义业务逻辑

// src/todos/todos.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class TodosService {
  // 模拟内存存储(后续替换为数据库)
  private todos: { id: number; title: string; completed: boolean }[] = [];
  private nextId = 1;

  findAll() {
    return this.todos;
  }

  create(title: string) {
    const newTodo = {
      id: this.nextId++,
      title,
      completed: false,
    };
    this.todos.push(newTodo);
    return newTodo;
  }

  toggleCompleted(id: number) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
      return todo;
    }
    return null;
  }
}

步骤 2️⃣:创建 Controller —— 暴露 API 接口

// src/todos/todos.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  ParseIntPipe,
} from '@nestjs/common';
import { TodosService } from './todos.service';

@Controller('todos') // 所有路由以 /todos 开头
export class TodosController {
  constructor(private readonly todosService: TodosService) {}

  @Get()
  getAllTodos() {
    return this.todosService.findAll();
  }

  @Post()
  createTodo(@Body('title') title: string) {
    if (!title) {
      return { error: '标题不能为空' };
    }
    return this.todosService.create(title);
  }

  @Patch(':id/toggle')
  toggleTodo(@Param('id', ParseIntPipe) id: number) {
    const result = this.todosService.toggleCompleted(id);
    if (!result) {
      return { error: '待办事项不存在' };
    }
    return result;
  }
}

⚠️ 关键细节@Param('id', ParseIntPipe)
URL 参数 :id 默认是字符串(如 '5'),但我们需要数字 5ParseIntPipe 会自动转换并校验,若非数字则返回 400 错误。


步骤 3️⃣:创建 Module —— 组装功能单元

// src/todos/todos.module.ts
import { Module } from '@nestjs/common';
import { TodosController } from './todos.controller';
import { TodosService } from './todos.service';

@Module({
  controllers: [TodosController],
  providers: [TodosService],
})
export class TodosModule {}

这个模块现在是一个自包含的功能包,可以在任何地方复用。


步骤 4️⃣:注册到根模块

回到 app.module.ts,导入并注册:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodosModule } from './todos/todos.module'; // ← 导入

@Module({
  imports: [
    TodosModule, // ← 注册
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

现在,访问 GET /todos 就能获取待办列表!


总结:NestJS 给前端开发者的三大礼物

礼物说明价值
熟悉的模块化思维Component → Module, Hook → Service降低学习曲线
强类型安全保障TypeScript + 接口 + DTO减少 80% 的低级错误
清晰的架构约束Controller/Service/Module 严格分离团队协作无歧义

🌈 更深层的价值
NestJS 通过 装饰器 实现声明式编程,通过 依赖注入 实现松耦合架构。这两者共同构成了现代企业级后端开发的黄金标准。掌握它们,不仅是为了用 NestJS,更是为了理解“如何构建可维护的大型系统”。

NestJS 不是魔法,而是一套经过验证的工程方法论。它用约束换取清晰,用规范换取可维护性。作为前端开发者,你完全有能力驾驭它,成为真正的全栈工程师!