NestJS

技术栈
后端框架
nestjsnodejstypescript企业级装饰器依赖注入

概览

NestJS 技术栈概览

NestJS 是一个渐进式 Node.js 后端框架,由 Kamil Myśliwiec 于 2017 年创建。它深受 Angular 架构启发,原生支持 TypeScript,内置依赖注入、模块化、装饰器等企业级设计模式,是构建大型可维护后端应用的理想选择。

解决什么问题

  • 后端架构规范:Controller → Service → Repository 分层清晰,告别 Express 随意堆代码
  • 企业级需求:开箱即用的 GraphQL、WebSocket、微服务、OpenAPI 文档
  • 类型安全:TypeScript 原生支持,编译期发现错误
  • 毕设展示专业度:装饰器风格代码整洁,面试加分

关键特性

  • 依赖注入容器(DI),利于测试和解耦
  • 模块化架构(Module/Controller/Provider)
  • 装饰器驱动的路由、守卫、管道、拦截器
  • 内置支持 Prisma/TypeORM/Mongoose 等 ORM
  • 与 Express/Fastify 兼容,可复用其生态

安装

环境准备

  • 操作系统:Windows 10+ / macOS 12+ / Ubuntu 20.04+
  • 运行时版本:Node.js 18 LTS 或 20 LTS(NestJS 10+ 要求 Node >= 16)
  • 依赖项:npm / yarn / pnpm(任选)

安装命令

使用 Nest CLI(推荐)

# 全局安装 Nest CLI
npm install -g @nestjs/cli

# 创建新项目
nest new my-nest-app
# 选择 npm / yarn / pnpm 后自动安装依赖

# 进入项目并启动
cd my-nest-app
npm run start:dev
# 访问 http://localhost:3000 看到 Hello World!

从零手动搭建

mkdir my-nest-app &;& cd my-nest-app
npm init -y
npm install @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
npm install -D @nestjs/cli typescript @types/node ts-node
# 配置 tsconfig.json 和 nest-cli.json

常用额外模块

# 数据库 ORM
npm install @nestjs/typeorm typeorm sqlite3

# 数据验证
npm install class-validator class-transformer

# Swagger 文档
npm install @nestjs/swagger swagger-ui-express

常见安装问题

问题 解决方案
nest: command not found 使用 npx @nestjs/cli new my-app 或确认 npm global bin 在 PATH
reflect-metadata 报错 main.ts 最顶部加 import 'reflect-metadata'
TypeScript 编译错误 确认 tsconfig.jsonexperimentalDecoratorsemitDecoratorMetadata 均为 true
端口 3000 冲突 main.tsawait app.listen(3001) 改为其他端口

示例

NestJS 入门 — 学生管理系统模块

目标

演示 NestJS 核心概念:模块化(Module)、控制器(Controller)、服务(Service)、依赖注入(DI)、DTO 验证。

1. 创建项目

nest new nest-demo
cd nest-demo

2. 生成学生模块

nest g resource students
# 选择 REST API, 生成 CRUD 入口点

3. 完整代码

src/students/dto/create-student.dto.ts

import { IsString, IsInt, Min, Max, IsEmail, IsOptional } from 'class-validator';

export class CreateStudentDto {
  @IsString()
  name: string;

  @IsInt()
  @Min(18)
  @Max(30)
  age: number;

  @IsEmail()
  email: string;

  @IsOptional()
  @IsString()
  major?: string;
}

src/students/students.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateStudentDto } from './dto/create-student.dto';
import { UpdateStudentDto } from './dto/update-student.dto';

@Injectable()
export class StudentsService {
  private students = [];

  create(dto: CreateStudentDto) {
    const student = { id: Date.now(), ...dto, createdAt: new Date() };
    this.students.push(student);
    return student;
  }

  findAll() {
    return { count: this.students.length, data: this.students };
  }

  findOne(id: number) {
    const student = this.students.find(s => s.id === id);
    if (!student) throw new NotFoundException(`学生 #${id} 不存在`);
    return student;
  }

  update(id: number, dto: UpdateStudentDto) {
    const student = this.findOne(id);
    Object.assign(student, dto);
    return student;
  }

  remove(id: number) {
    const index = this.students.findIndex(s => s.id === id);
    if (index === -1) throw new NotFoundException(`学生 #${id} 不存在`);
    this.students.splice(index, 1);
    return { message: '已删除' };
  }
}

src/students/students.controller.ts

import { Controller, Get, Post, Body, Param, Delete, Put, ParseIntPipe, Query } from '@nestjs/common';
import { StudentsService } from './students.service';
import { CreateStudentDto } from './dto/create-student.dto';
import { UpdateStudentDto } from './dto/update-student.dto';

@Controller('students')
export class StudentsController {
  constructor(private readonly studentsService: StudentsService) {}

  @Post()
  create(@Body() dto: CreateStudentDto) {
    return this.studentsService.create(dto);
  }

  @Get()
  findAll() {
    return this.studentsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.studentsService.findOne(id);
  }

  @Put(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateStudentDto) {
    return this.studentsService.update(id, dto);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.studentsService.remove(id);
  }
}

运行步骤

npm run start:dev
# 访问 http://localhost:3000/students

# 测试 POST
curl -X POST http://localhost:3000/students \
  -H "Content-Type: application/json" \
  -d '{"name":"张三","age":22,"email":"zhangsan@univ.edu.cn","major":"计算机科学"}'

预期输出

  • GET /students{"count":1,"data":[...]}
  • DTO 自动验证:age < 18 或 email 格式错误时返回 400 及详细错误信息
  • 访问不存在的学生返回 404:{"statusCode":404,"message":"学生 #999 不存在"}

NestJS TypeORM CRUD + Swagger 文档

目标

用 TypeORM 操作 SQLite 数据库,配合 Swagger 自动生成交互式 API 文档。

模块代码

src/posts/entities/post.entity.ts

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';

@Entity()
export class Post {
  @ApiProperty({ description: '文章ID', example: 1 })
  @PrimaryGeneratedColumn()
  id: number;

  @ApiProperty({ description: '标题', example: '毕设心得' })
  @Column({ length: 100 })
  title: string;

  @ApiProperty({ description: '内容', example: '这是正文...' })
  @Column('text')
  content: string;

  @ApiProperty({ description: '作者', example: '张三' })
  @Column({ length: 50 })
  author: string;

  @ApiProperty({ description: '创建时间' })
  @CreateDateColumn()
  createdAt: Date;
}

src/posts/dto/create-post.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength, MaxLength } from 'class-validator';

export class CreatePostDto {
  @ApiProperty({ description: '文章标题', minLength: 2, maxLength: 100 })
  @IsString()
  @MinLength(2)
  @MaxLength(100)
  title: string;

  @ApiProperty({ description: '文章内容', minLength: 10 })
  @IsString()
  @MinLength(10)
  content: string;

  @ApiProperty({ description: '作者名', maxLength: 50 })
  @IsString()
  @MaxLength(50)
  author: string;
}

src/posts/posts.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Post } from './entities/post.entity';
import { CreatePostDto } from './dto/create-post.dto';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private postsRepo: Repository<Post>,
  ) {}

  async create(dto: CreatePostDto): Promise<Post> {
    return this.postsRepo.save(this.postsRepo.create(dto));
  }

  async findAll(page = 1, limit = 10, keyword?: string) {
    const where = keyword
      ? [{ title: Like(`%${keyword}%`) }, { author: Like(`%${keyword}%`) }]
      : {};
    const [data, total] = await this.postsRepo.findAndCount({
      where,
      skip: (page - 1) * limit,
      take: limit,
      order: { createdAt: 'DESC' },
    });
    return { data, total, page, limit, totalPages: Math.ceil(total / limit) };
  }

  async findOne(id: number): Promise<Post> {
    const post = await this.postsRepo.findOne({ where: { id } });
    if (!post) throw new NotFoundException(`文章 #${id} 不存在`);
    return post;
  }

  async update(id: number, dto: Partial<CreatePostDto>) {
    const post = await this.findOne(id);
    Object.assign(post, dto);
    return this.postsRepo.save(post);
  }

  async remove(id: number) {
    const post = await this.findOne(id);
    return this.postsRepo.remove(post);
  }
}

src/posts/posts.controller.ts

import { Controller, Get, Post, Body, Param, Delete, Put, Query, ParseIntPipe } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';

@ApiTags('文章管理')
@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Post()
  @ApiOperation({ summary: '创建文章' })
  @ApiResponse({ status: 201, description: '创建成功' })
  create(@Body() dto: CreatePostDto) {
    return this.postsService.create(dto);
  }

  @Get()
  @ApiOperation({ summary: '获取文章列表(支持分页+搜索)' })
  @ApiQuery({ name: 'page', required: false, example: 1 })
  @ApiQuery({ name: 'limit', required: false, example: 10 })
  @ApiQuery({ name: 'q', required: false, description: '搜索关键词' })
  findAll(
    @Query('page') page = 1,
    @Query('limit') limit = 10,
    @Query('q') q?: string,
  ) {
    return this.postsService.findAll(+page, +limit, q);
  }

  @Get(':id')
  @ApiOperation({ summary: '获取单篇文章' })
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.postsService.findOne(id);
  }

  @Put(':id')
  @ApiOperation({ summary: '更新文章' })
  update(@Param('id', ParseIntPipe) id: number, @Body() dto: Partial<CreatePostDto>) {
    return this.postsService.update(id, dto);
  }

  @Delete(':id')
  @ApiOperation({ summary: '删除文章' })
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.postsService.remove(id);
  }
}

src/posts/posts.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { Post } from './entities/post.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Post])],
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

app.module.ts 数据库配置

TypeOrmModule.forRoot({
  type: 'sqlite',
  database: 'blog.db',
  entities: [__dirname + '/**/*.entity{.ts,.js}'],
  synchronize: true, // 开发环境自动建表
}),

运行

npm run start:dev
# Swagger 文档: http://localhost:3000/api-docs
# 可在 Swagger UI 上直接测试所有接口!

预期效果

  • Swagger UI 展示所有 API,可直接点 Try it out 测试
  • 分页查询支持 keyword 模糊搜索
  • SQLite 零配置,blog.db 文件随项目携带

教程

NestJS 毕设实战 — 企业级架构入门

前言

如果你想让毕设代码看起来「专业」,NestJS 是最佳选择。它的模块化、依赖注入、装饰器风格让代码整洁且可测试。面试时拿出 NestJS 项目,面试官会眼前一亮。

第一章:理解依赖注入(DI)

NestJS 的核心是 IoC 容器(控制反转)。你不需要手动 new 对象,容器自动帮你组装:

// ❌ 传统方式 — 紧耦合
class UserController {
  constructor() {
    this.userService = new UserService(); // 写死了!
  }
}

// ✅ NestJS 方式 — DI 注入
@Injectable()
class UserService { /* ... */ }

@Controller('users')
class UserController {
  constructor(private readonly userService: UserService) {}
  // Nest 自动将 UserService 实例注入进来
}

好处

  • 测试时可以用 Mock 替代真实 Service
  • 单例管理,不会重复创建
  • 模块间解耦

第二章:守卫(Guard)— 权限控制

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    if (!token) return false;
    try {
      request.user = this.jwtService.verify(token);
      return true;
    } catch {
      return false;
    }
  }
}

// 使用
@UseGuards(AuthGuard)
@Get('profile')
getProfile(@Req() req) {
  return req.user;
}

第三章:TypeORM 集成

npm install @nestjs/typeorm typeorm sqlite3
// user.entity.ts
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column({ select: false }) // 查询时默认隐藏密码
  password: string;

  @CreateDateColumn()
  createdAt: Date;
}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepo: Repository<User>,
  ) {}

  async findByUsername(username: string) {
    return this.usersRepo.findOne({ where: { username } });
  }
}

第四章:模块化组织

src/
├── auth/           # 认证模块
│   ├── auth.module.ts
│   ├── auth.controller.ts
│   ├── auth.service.ts
│   └── jwt.strategy.ts
├── users/          # 用户模块
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── entities/user.entity.ts
├── common/         # 公共模块
│   ├── guards/
│   ├── decorators/
│   └── filters/
└── app.module.ts   # 根模块

每个功能一个文件夹,模块间通过 imports: [UsersModule] 引用。

第五章:Swagger 文档 — 答辩加分项

npm install @nestjs/swagger swagger-ui-express
// main.ts
const config = new DocumentBuilder()
  .setTitle('毕设 API')
  .setDescription('学生管理系统接口文档')
  .setVersion('1.0')
  .addBearerAuth()
  .build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);

访问 http://localhost:3000/api-docs 即可看到自动生成的 Swagger UI,答辩时直接浏览器演示接口!

思考题

  1. NestJS 的 Module 和 Angular 的 NgModule 有什么异同?
  2. Provider scope(DEFAULT/REQUEST/TRANSIENT)有什么区别?
  3. 如何在 NestJS 中实现请求级别的日志链路追踪?