返回博客列表
后端
2025年7月1日
14 分钟阅读

Nest.js 入门指南:构建现代 Node.js 应用

Nest.js 入门指南:构建现代 Node.js 应用

Nest.js 是一个用于构建高效、可扩展的 Node.js 服务器端应用的框架。它使用 TypeScript 构建,结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)的元素。本文将从基础概念开始,带你快速入门 Nest.js。

什么是 Nest.js?

核心特性

Nest.js 是一个渐进式的 Node.js 框架,具有以下特点:

  • 基于 TypeScript:提供类型安全和更好的开发体验
  • 模块化架构:借鉴 Angular 的模块化设计
  • 依赖注入:内置强大的依赖注入系统
  • 装饰器支持:大量使用装饰器简化代码
  • 开箱即用:内置对 TypeORM、MongoDB、GraphQL 等的支持

为什么选择 Nest.js?

相比 Express 等基础框架,Nest.js 提供了:

  • 结构化开发:强制性的项目结构和最佳实践
  • 可扩展性:模块化设计便于团队协作和项目扩展
  • 企业级特性:内置测试、验证、日志等功能
  • TypeScript 支持:完整的类型系统支持

项目初始化

安装 Nest.js CLI

# 全局安装 Nest.js CLI
npm i -g @nestjs/cli
 
# 创建新项目
nest new project-name
 
# 或使用 npm/yarn
npm i -g @nestjs/cli
yarn global add @nestjs/cli

项目结构

创建项目后,典型的目录结构如下:

src/
  main.ts           # 应用入口文件
  app.module.ts     # 根模块
  app.controller.ts # 根控制器
  app.service.ts    # 根服务

启动应用

# 开发模式(热重载)
npm run start:dev
 
# 生产模式
npm run build
npm run start:prod

应用默认运行在 http://localhost:3000

核心概念:模块(Module)

什么是模块?

模块是 Nest.js 应用的基本组织单元,使用 @Module() 装饰器定义:

// app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
 
@Module({
  imports: [], // 导入其他模块
  controllers: [AppController], // 控制器
  providers: [AppService], // 提供者(服务等)
  exports: [], // 导出供其他模块使用
})
export class AppModule {}

功能模块示例

创建用户模块:

// users/users.module.ts
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
 
@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService], // 导出供其他模块使用
})
export class UsersModule {}

模块导入

在根模块中导入功能模块:

// app.module.ts
import { Module } from "@nestjs/common";
import { UsersModule } from "./users/users.module";
 
@Module({
  imports: [UsersModule], // 导入用户模块
  controllers: [],
  providers: [],
})
export class AppModule {}

核心概念:控制器(Controller)

什么是控制器?

控制器负责处理 HTTP 请求并返回响应,使用 @Controller() 装饰器定义:

// users/users.controller.ts
import { Controller, Get, Post, Body, Param } from "@nestjs/common";
import { UsersService } from "./users.service";
 
@Controller("users") // 路由前缀 /users
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Get() // GET /users
  findAll() {
    return this.usersService.findAll();
  }
 
  @Get(":id") // GET /users/:id
  findOne(@Param("id") id: string) {
    return this.usersService.findOne(id);
  }
 
  @Post() // POST /users
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

常用装饰器

import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  Query,
  Headers,
  HttpCode,
  HttpStatus,
} from "@nestjs/common";
 
@Controller("products")
export class ProductsController {
  @Get()
  findAll(@Query("page") page: number) {
    // GET /products?page=1
    return { page };
  }
 
  @Post()
  @HttpCode(HttpStatus.CREATED) // 自定义状态码
  create(@Body() createProductDto: CreateProductDto) {
    return this.productsService.create(createProductDto);
  }
 
  @Get(":id")
  findOne(@Param("id") id: string) {
    // GET /products/:id
    return this.productsService.findOne(id);
  }
 
  @Put(":id")
  update(@Param("id") id: string, @Body() updateProductDto: UpdateProductDto) {
    return this.productsService.update(id, updateProductDto);
  }
 
  @Delete(":id")
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param("id") id: string) {
    return this.productsService.remove(id);
  }
}

路由参数

@Controller("blog")
export class BlogController {
  // 单个参数
  @Get(":id")
  findOne(@Param("id") id: string) {
    return { id };
  }
 
  // 多个参数
  @Get(":category/:slug")
  findByCategoryAndSlug(
    @Param("category") category: string,
    @Param("slug") slug: string
  ) {
    return { category, slug };
  }
 
  // 获取所有参数
  @Get(":id")
  findOneAllParams(@Param() params: { id: string }) {
    return params;
  }
}

核心概念:服务(Service)

什么是服务?

服务包含业务逻辑,使用 @Injectable() 装饰器定义,可以被注入到控制器或其他服务中:

// users/users.service.ts
import { Injectable } from "@nestjs/common";
 
@Injectable()
export class UsersService {
  private users = [
    { id: 1, name: "John", email: "john@example.com" },
    { id: 2, name: "Jane", email: "jane@example.com" },
  ];
 
  findAll() {
    return this.users;
  }
 
  findOne(id: string) {
    return this.users.find((user) => user.id === parseInt(id));
  }
 
  create(createUserDto: CreateUserDto) {
    const newUser = {
      id: this.users.length + 1,
      ...createUserDto,
    };
    this.users.push(newUser);
    return newUser;
  }
}

服务依赖注入

在控制器中注入服务:

// users/users.controller.ts
import { Controller, Get } from "@nestjs/common";
import { UsersService } from "./users.service";
 
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

注意:Nest.js 使用构造函数注入,通过 private readonly 语法简化代码。

DTO(Data Transfer Object)

什么是 DTO?

DTO 用于定义数据传输对象的结构和验证规则:

// users/dto/create-user.dto.ts
import { IsString, IsEmail, IsNotEmpty, MinLength } from "class-validator";
 
export class CreateUserDto {
  @IsNotEmpty()
  @IsString()
  name: string;
 
  @IsNotEmpty()
  @IsEmail()
  email: string;
 
  @IsNotEmpty()
  @MinLength(6)
  password: string;
}

使用 DTO

// users/users.controller.ts
import { Controller, Post, Body } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";
import { UsersService } from "./users.service";
 
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

完整的 CRUD 示例

创建资源模块

# 使用 CLI 创建完整资源
nest g resource products

这会自动生成:

  • products.module.ts - 模块文件
  • products.controller.ts - 控制器文件
  • products.service.ts - 服务文件
  • dto/ - DTO 目录

完整示例

// products/products.service.ts
import { Injectable, NotFoundException } from "@nestjs/common";
import { CreateProductDto } from "./dto/create-product.dto";
import { UpdateProductDto } from "./dto/update-product.dto";
 
@Injectable()
export class ProductsService {
  private products = [
    { id: 1, name: "Product 1", price: 100 },
    { id: 2, name: "Product 2", price: 200 },
  ];
 
  create(createProductDto: CreateProductDto) {
    const newProduct = {
      id: this.products.length + 1,
      ...createProductDto,
    };
    this.products.push(newProduct);
    return newProduct;
  }
 
  findAll() {
    return this.products;
  }
 
  findOne(id: number) {
    const product = this.products.find((p) => p.id === id);
    if (!product) {
      throw new NotFoundException(`Product with ID ${id} not found`);
    }
    return product;
  }
 
  update(id: number, updateProductDto: UpdateProductDto) {
    const product = this.findOne(id);
    Object.assign(product, updateProductDto);
    return product;
  }
 
  remove(id: number) {
    const index = this.products.findIndex((p) => p.id === id);
    if (index === -1) {
      throw new NotFoundException(`Product with ID ${id} not found`);
    }
    this.products.splice(index, 1);
    return { message: "Product deleted successfully" };
  }
}
// products/products.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  ParseIntPipe,
} from "@nestjs/common";
import { ProductsService } from "./products.service";
import { CreateProductDto } from "./dto/create-product.dto";
import { UpdateProductDto } from "./dto/update-product.dto";
 
@Controller("products")
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}
 
  @Post()
  create(@Body() createProductDto: CreateProductDto) {
    return this.productsService.create(createProductDto);
  }
 
  @Get()
  findAll() {
    return this.productsService.findAll();
  }
 
  @Get(":id")
  findOne(@Param("id", ParseIntPipe) id: number) {
    return this.productsService.findOne(id);
  }
 
  @Patch(":id")
  update(
    @Param("id", ParseIntPipe) id: number,
    @Body() updateProductDto: UpdateProductDto
  ) {
    return this.productsService.update(id, updateProductDto);
  }
 
  @Delete(":id")
  remove(@Param("id", ParseIntPipe) id: number) {
    return this.productsService.remove(id);
  }
}

管道(Pipe):数据验证和转换

内置管道

import { ParseIntPipe, ParseBoolPipe, DefaultValuePipe } from '@nestjs/common';
 
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  // id 自动转换为 number 类型
  return this.productsService.findOne(id);
}
 
@Get('active')
findActive(@Query('active', new DefaultValuePipe(true), ParseBoolPipe) active: boolean) {
  return this.productsService.findActive(active);
}

自定义验证管道

// common/pipes/validation.pipe.ts
import {
  PipeTransform,
  ArgumentMetadata,
  BadRequestException,
} from "@nestjs/common";
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";
 
export class ValidationPipe implements PipeTransform {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
 
    const object = plainToInstance(metatype, value);
    const errors = await validate(object);
 
    if (errors.length > 0) {
      throw new BadRequestException("Validation failed");
    }
 
    return value;
  }
 
  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

异常处理

内置异常

import {
  NotFoundException,
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  ConflictException,
} from '@nestjs/common';
 
@Get(':id')
findOne(@Param('id') id: string) {
  const product = this.productsService.findOne(id);
 
  if (!product) {
    throw new NotFoundException(`Product with ID ${id} not found`);
  }
 
  return product;
}

自定义异常过滤器

// common/filters/http-exception.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from "@nestjs/common";
import { Request, 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 request = ctx.getRequest<Request>();
    const status = exception.getStatus();
 
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: exception.message,
    });
  }
}

实际应用:构建 RESTful API

完整的用户管理模块

// users/users.module.ts
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
 
@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}
// users/dto/create-user.dto.ts
export class CreateUserDto {
  name: string;
  email: string;
  password: string;
}
 
// users/dto/update-user.dto.ts
export class UpdateUserDto {
  name?: string;
  email?: string;
}
// users/users.service.ts
import { Injectable } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
 
@Injectable()
export class UsersService {
  private users = [];
 
  create(createUserDto: CreateUserDto) {
    const user = { id: Date.now(), ...createUserDto };
    this.users.push(user);
    return user;
  }
 
  findAll() {
    return this.users;
  }
 
  findOne(id: number) {
    return this.users.find((user) => user.id === id);
  }
 
  update(id: number, updateUserDto: UpdateUserDto) {
    const user = this.findOne(id);
    if (user) {
      Object.assign(user, updateUserDto);
    }
    return user;
  }
 
  remove(id: number) {
    const index = this.users.findIndex((user) => user.id === id);
    if (index > -1) {
      this.users.splice(index, 1);
      return { message: "User deleted" };
    }
  }
}

最佳实践

  1. 模块化设计:每个功能创建一个独立模块
  2. 使用 DTO:定义清晰的数据传输对象
  3. 依赖注入:充分利用 Nest.js 的依赖注入系统
  4. 异常处理:使用适当的异常类型和异常过滤器
  5. 管道验证:使用管道进行数据验证和转换
  6. 单一职责:控制器处理请求,服务包含业务逻辑
  7. 使用 CLI:充分利用 Nest CLI 提高开发效率

通过本文的学习,你已经掌握了 Nest.js 的核心概念。接下来可以深入学习依赖注入、中间件、守卫等高级特性,构建更复杂的应用。