下面是一个完整的 NestJS RESTful API 示例,实现一个订单管理系统(包括 CRUD 操作),涵盖最佳实践如 DTO 验证服务层逻辑异常处理Swagger 文档
在这里插入图片描述


在这里插入图片描述

项目结构

src/
├── order/
│   ├── dto/
│   │   ├── create-order.dto.ts    # 创建订单的验证规则
│   │   ├── update-order.dto.ts    # 更新订单的验证规则
│   │   └── order-response.dto.ts  # 统一响应格式
│   ├── entities/
│   │   └── order.entity.ts        # 订单实体类
│   ├── order.controller.ts        # 控制器(路由定义)
│   ├── order.service.ts           # 服务层(业务逻辑)
│   └── order.module.ts            # 模块定义
├── app.module.ts                  # 根模块
└── main.ts                        # 入口文件(启用Swagger)

在这里插入图片描述

代码实现

1. 订单实体 (order.entity.ts)
export class Order {
  id: number;
  productName: string;
  quantity: number;
  price: number;
  status: 'PENDING' | 'SHIPPED' | 'DELIVERED'; // 订单状态枚举
  createdAt: Date;
}
2. DTO 数据传输对象

创建订单 DTO (create-order.dto.ts):

import { IsString, IsInt, Min, IsEnum } from 'class-validator';

export class CreateOrderDto {
  @IsString()
  productName: string;

  @IsInt()
  @Min(1)
  quantity: number;

  @IsInt()
  @Min(0)
  price: number;

  @IsEnum(['PENDING', 'SHIPPED'])
  status: 'PENDING' | 'SHIPPED';
}

更新订单 DTO (update-order.dto.ts):

import { PartialType } from '@nestjs/mapped-types';
import { CreateOrderDto } from './create-order.dto';

// 继承CreateOrderDto并设为可选
export class UpdateOrderDto extends PartialType(CreateOrderDto) {}
3. 服务层 (order.service.ts)
import { Injectable, NotFoundException } from '@nestjs/common';
import { Order } from './entities/order.entity';
import { CreateOrderDto, UpdateOrderDto } from './dto';

@Injectable()
export class OrderService {
  private orders: Order[] = []; // 模拟数据库
  private nextId = 1;

  // 创建订单
  create(createOrderDto: CreateOrderDto): Order {
    const newOrder: Order = {
      id: this.nextId++,
      ...createOrderDto,
      status: createOrderDto.status || 'PENDING',
      createdAt: new Date(),
    };
    this.orders.push(newOrder);
    return newOrder;
  }

  // 查找所有订单
  findAll(): Order[] {
    return this.orders;
  }

  // 查找单个订单
  findOne(id: number): Order {
    const order = this.orders.find(o => o.id === id);
    if (!order) throw new NotFoundException(`Order #${id} not found`);
    return order;
  }

  // 更新订单
  update(id: number, updateOrderDto: UpdateOrderDto): Order {
    const order = this.findOne(id);
    Object.assign(order, updateOrderDto);
    return order;
  }

  // 删除订单
  remove(id: number): void {
    const index = this.orders.findIndex(o => o.id === id);
    if (index === -1) throw new NotFoundException(`Order #${id} not found`);
    this.orders.splice(index, 1);
  }
}
4. 控制器 (order.controller.ts)
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  HttpCode,
  HttpStatus,
} from '@nestjs/common';
import { OrderService } from './order.service';
import { CreateOrderDto, UpdateOrderDto } from './dto';
import { Order } from './entities/order.entity';

@Controller('orders') // 基础路径 /orders
export class OrderController {
  constructor(private readonly orderService: OrderService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createOrderDto: CreateOrderDto): Order {
    return this.orderService.create(createOrderDto);
  }

  @Get()
  findAll(): Order[] {
    return this.orderService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string): Order {
    return this.orderService.findOne(+id); // 转换为数字
  }

  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() updateOrderDto: UpdateOrderDto,
  ): Order {
    return this.orderService.update(+id, updateOrderDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id') id: string): void {
    this.orderService.remove(+id);
  }
}
5. 模块定义 (order.module.ts)
import { Module } from '@nestjs/common';
import { OrderController } from './order.controller';
import { OrderService } from './order.service';

@Module({
  controllers: [OrderController],
  providers: [OrderService],
})
export class OrderModule {}
6. 启用 Swagger 文档 (main.ts)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 配置Swagger
  const config = new DocumentBuilder()
    .setTitle('Order Management API')
    .setDescription('The NestJS Order API description')
    .setVersion('1.0')
    .addTag('orders')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document); // 访问 /api 查看文档

  await app.listen(3000);
}
bootstrap();

在这里插入图片描述

测试 API(使用 curl)

创建订单
curl -X POST http://localhost:3000/orders \
  -H "Content-Type: application/json" \
  -d '{
    "productName": "Laptop",
    "quantity": 2,
    "price": 1200,
    "status": "PENDING"
  }'
响应
{
  "id": 1,
  "productName": "Laptop",
  "quantity": 2,
  "price": 1200,
  "status": "PENDING",
  "createdAt": "2023-10-05T08:30:00.000Z"
}
获取所有订单
curl http://localhost:3000/orders
更新订单
curl -X PUT http://localhost:3000/orders/1 \
  -H "Content-Type: application/json" \
  -d '{"status": "SHIPPED"}'
删除订单
curl -X DELETE http://localhost:3000/orders/1

关键特性演示

  1. DTO 验证
    如果请求缺少必填字段(如 price),自动返回 400 Bad Request

    {
      "statusCode": 400,
      "message": ["price must be a positive number"],
      "error": "Bad Request"
    }
    
  2. 异常处理
    访问不存在的订单 ID 时返回 404 Not Found

    {
      "statusCode": 404,
      "message": "Order #99 not found",
      "error": "Not Found"
    }
    
  3. Swagger 文档
    访问 http://localhost:3000/api 查看交互式文档:
    !https://miro.medium.com/v2/resize:fit:1400/1*5J6zWlVvYqQfXqj_9y6x3g.png


进阶优化建议

  1. 数据库集成
    替换内存存储为 TypeORM/Prisma:

    // 使用TypeORM实体
    @Entity()
    export class OrderEntity {
      @PrimaryGeneratedColumn()
      id: number;
      
      @Column()
      productName: string;
      // ...
    }
    
  2. 分页查询
    在服务层添加分页参数:

    findAll(page = 1, limit = 10) {
      return this.orders.slice((page - 1) * limit, page * limit);
    }
    
  3. 身份验证
    添加 JWT 守卫保护敏感操作:

    @UseGuards(JwtAuthGuard)
    @Delete(':id')
    remove(@Param('id') id: string) { ... }
    

这个示例展示了 NestJS 的核心优势:清晰的代码结构强类型验证可扩展性开发者友好的工具链。实际项目中可根据需求扩展功能(如数据库、缓存、消息队列)。


RESTful API

RESTful API 是一种基于 REST(Representational State Transfer,表述性状态转移)架构风格 设计的 API,核心思想是通过 资源(Resource) 为中心,利用 HTTP 协议的标准方法(GET/POST/PUT/DELETE 等)实现对资源的操作,具有简洁、可扩展、松耦合的特点。以下是其基础知识体系:

在这里插入图片描述

一、REST 的核心约束(6大原则)

REST 架构定义了 6 条约束(其中第 6 条为可选),满足这些约束的 API 才能称为“RESTful”:

约束含义
1. 客户端-服务器分离客户端(如浏览器、App)负责用户界面,服务器负责数据存储和业务逻辑,两者独立演化。
2. 无状态(Stateless)服务器不存储客户端上下文(如会话信息),每次请求必须包含所有必要信息(如认证令牌),简化服务器设计。
3. 可缓存(Cacheable)响应需明确是否可缓存(通过 Cache-Control 头),减少重复请求,提升性能。
4. 统一接口(Uniform Interface)客户端与服务器通过标准化的接口通信,核心包括:
- 资源通过 URI 唯一标识;
- 通过 HTTP 方法操作资源;
- 资源表述自包含(如 JSON/XML);
- HATEOAS(超媒体驱动,见下文)。
5. 分层系统(Layered System)架构可分为多层(如负载均衡层、应用层、数据层),每层仅与相邻层交互,隐藏内部实现。
6. 按需代码(Code on Demand,可选)服务器可向客户端临时发送可执行代码(如 JavaScript),扩展客户端功能(较少使用)。

二、RESTful API 的关键特征

1. 资源导向(Resource-Oriented)
  • 资源:API 操作的核心对象(如用户、订单、商品),用 URI(统一资源标识符) 唯一标识。

  • URI 设计原则

    • 名词复数 表示资源集合(如 /users 表示用户列表,/orders 表示订单列表);
    • 嵌套 URI 表示资源关系(如 /users/123/orders 表示 ID=123 用户的订单);
    • 避免动词(如不用 /getUsers,而用 GET /users);
    • 小写字母+连字符分隔(如 /user-profiles,不用驼峰)。

    ✅ 正确示例:GET /orders/456(获取 ID=456 的订单)、POST /products(创建商品)。
    ❌ 错误示例:GET /getOrder?id=456(含动词)、POST /createProduct(冗余动词)。

2. HTTP 方法映射 CRUD 操作

RESTful API 严格遵循 HTTP 方法的语义,对应资源的 增删改查(CRUD)

HTTP 方法语义操作示例 URI响应状态码
GET获取资源(幂等)查询单个/多个资源GET /orders(所有订单)
GET /orders/1(ID=1 的订单)
200 OK(成功)
404 Not Found(资源不存在)
POST创建资源(非幂等)新增资源(服务器生成 ID)POST /orders(创建订单)201 Created(成功创建)
400 Bad Request(参数错误)
PUT全量更新资源(幂等)替换整个资源(需提供完整字段)PUT /orders/1(更新 ID=1 的订单,全量替换)200 OK(成功)
404 Not Found(资源不存在)
PATCH部分更新资源(非幂等)仅修改部分字段(如只改状态)PATCH /orders/1(更新 ID=1 的订单状态)200 OK(成功)
DELETE删除资源(幂等)删除指定资源DELETE /orders/1(删除 ID=1 的订单)204 No Content(成功删除,无响应体)
  • 幂等性:多次执行同一请求,结果相同(如 GET/PUT/DELETE 是幂等的,POST 不是)。
3. 状态码(Status Code)

用 HTTP 标准状态码表示请求结果,避免自定义错误码:

类别状态码含义示例场景
2xx 成功200 OK请求成功(GET/PUT/PATCH)获取订单列表、更新订单成功
201 Created资源创建成功(POST)创建订单后返回新订单 ID
204 No Content成功但无响应体(DELETE)删除订单后返回空响应
4xx 客户端错误400 Bad Request请求参数错误(如格式不对)创建订单时 price 为负数
401 Unauthorized未认证(如 token 无效)未登录用户访问需认证的接口
403 Forbidden已认证但无权限普通用户尝试删除他人订单
404 Not Found资源不存在访问 /orders/999(ID=999 的订单不存在)
409 Conflict资源冲突(如重复创建)创建已存在的用户名
5xx 服务器错误500 Internal Server Error服务器内部错误(如代码 bug)数据库连接失败
4. 数据格式与表述
  • 表述(Representation):资源的具体形式(如 JSON、XML),最常用 JSON(轻量、易读)。
  • 请求/响应头
    • Content-Type: application/json:表示请求体/响应体是 JSON 格式;
    • Accept: application/json:客户端希望接收 JSON 格式响应。
  • 示例响应体(JSON)
    {
      "id": 1,
      "productName": "Laptop",
      "quantity": 2,
      "price": 1200,
      "status": "PENDING",
      "createdAt": "2023-10-05T08:30:00Z",
      "_links": {  // HATEOAS 链接(可选)
        "self": { "href": "/orders/1" },
        "update": { "href": "/orders/1", "method": "PUT" }
      }
    }
    
5. HATEOAS(超媒体作为应用状态的引擎)

REST 的高级特性:响应中不仅包含数据,还包含 超媒体链接,引导客户端下一步操作(类似网页的“超链接”)。

  • 例如,获取订单列表时,响应中可包含“创建订单”“查看订单详情”的链接,客户端无需硬编码 URI。
  • 目前多数 API 未完全实现,但体现了 REST 的“自描述”思想。
6. 版本控制

API 迭代时需保证旧版本兼容,常见版本控制方式:

  • URI 版本/v1/orders/v2/orders(直观,最常用);
  • 请求头版本Accept: application/vnd.company.v1+json(通过 Accept 头指定版本);
  • 查询参数版本/orders?version=1(不推荐,破坏 URI 纯净性)。

三、RESTful API 最佳实践

  1. 使用 HTTPS:加密传输数据,避免明文暴露。
  2. 合理分页/过滤:对大数据集用 ?page=1&limit=10 分页,?status=PENDING 过滤。
  3. 统一错误处理:用标准 JSON 格式返回错误(如 { "code": "ORDER_NOT_FOUND", "message": "订单不存在" })。
  4. 认证与授权:用 JWT/OAuth2 等机制,通过 Authorization: Bearer <token> 头传递凭证。
  5. 避免过度设计:小型 API 无需严格遵循所有约束(如 HATEOAS),优先保证简洁可用。

四、RESTful API vs 其他 API 风格

对比项RESTful APIGraphQLSOAP
核心思想资源导向,HTTP 方法语义按需获取数据(单一端点)XML 协议,强类型契约(WSDL)
灵活性固定端点,多请求获取关联数据单端点,客户端自定义查询字段严格契约,灵活性低
性能可能产生冗余数据(Over-fetching)精确获取数据(Under-fetching)较重(XML 冗余),性能较差
适用场景简单 CRUD、通用后端 API复杂前端需求(如动态表单)企业级系统集成(银行、政府)

五、总结

RESTful API 的核心是 “资源为中心 + HTTP 语义标准化”,通过简洁的 URI、明确的 HTTP 方法和状态码,实现客户端与服务器的松耦合通信。其优势在于 易理解、易扩展、兼容性好,是现代 Web 开发中最主流的 API 设计风格(如 GitHub API、Twitter API 均采用 RESTful 设计)。

结合前文 NestJS 示例,其控制器(@Controller('/orders'))和 HTTP 方法装饰器(@Get/@Post 等)正是 RESTful 思想的落地实现,通过模块化分层进一步强化了 API 的可维护性。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐