第六部分:第五节 - 数据持久化 (基础):管理厨房的原材料库存

一个真正的后端应用通常需要与数据库交互,存储和读取数据。数据库就像中央厨房的原材料仓库,所有的食材都整齐地存放在这里,并有专门的管理系统。

在 Node.js 生态中,有多种数据库可供选择(如 PostgreSQL, MySQL, MongoDB 等),也有多种库可以帮助我们与数据库交互。常用的方式是使用 ORM (Object-Relational Mapper) 处理关系型数据库(如 TypeORM, Sequelize)或 ODM (Object-Document Mapper) 处理 NoSQL 数据库(如 Mongoose 处理 MongoDB)。ORM/ODM 就像仓库管理员提供的标准化接口,让我们可以用面向对象的方式来操作数据库中的数据,而无需直接写复杂的 SQL 语句或处理底层数据库驱动。

NestJS 并没有内置强制使用某个特定的 ORM/ODM,但它提供了良好的集成能力。通常的做法是:

  1. 安装数据库驱动和 ORM/ODM 库。
  2. 配置数据库连接。 这通常在一个专门的数据库模块中完成。
  3. 定义数据模型(Entities/Schemas)。 描述数据在数据库中的结构。
  4. 在 Service 中使用 ORM/ODM 提供的 Repository 或 Model 来进行数据操作(CRUD)。 Service 作为业务逻辑层,负责协调数据访问。

Service 与数据交互的角色:

在 NestJS 中,Service (Provider) 是进行数据访问的理想位置。控制器负责处理 HTTP 请求和调用 Service,而 Service 则负责执行具体的业务逻辑,包括与数据库进行交互。这符合关注点分离的原则:控制器只关心如何接收请求和返回响应,Service 则关心如何实现某个功能(包括数据读写)。

想象一下,控制器(服务员)把订单交给服务(厨师长),厨师长根据食谱(业务逻辑)决定需要哪些原材料,然后通过仓库管理员(ORM/ODM)从仓库(数据库)获取原材料,最后完成菜品。

模拟数据存储(使用内存数组):

在这个基础教程阶段,为了避免引入复杂的数据库安装和配置,我们可以先在 Service 中使用一个简单的内存数组来模拟数据存储。这让我们能够专注于 NestJS 的核心架构,理解 Service 如何作为数据交互层。

小例子:修改 UsersService,使用内存数组存储用户数据

修改 src/users/users.service.ts

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

// 定义一个简单的用户接口,用于类型安全
interface User {
  id: number;
  name: string;
  age: number;
}

@Injectable()
export class UsersService {
  // 使用内存数组模拟用户数据存储
  private users: User[] = [
    { id: 1, name: 'Alice', age: 28 },
    { id: 2, name: 'Bob', age: 32 },
  ];

  // 获取所有用户 (Read)
  findAll(): User[] {
    return this.users;
  }

  // 获取单个用户 (Read)
  findOne(id: number): User | undefined {
    return this.users.find(user => user.id === id);
  }

  // 创建用户 (Create) - 简单实现
  create(userData: { name: string; age: number }): User {
    const newUser = {
      id: this.users.length > 0 ? this.users[this.users.length - 1].id + 1 : 1, // 简单生成 ID
      ...userData,
    };
    this.users.push(newUser);
    return newUser;
  }

  // 更新用户 (Update) - 简单实现
  update(id: number, updateData: Partial<User>): User | undefined {
    const userIndex = this.users.findIndex(user => user.id === id);
    if (userIndex === -1) {
      return undefined; // 找不到用户
    }
    this.users[userIndex] = { ...this.users[userIndex], ...updateData };
    return this.users[userIndex];
  }

  // 删除用户 (Delete) - 简单实现
  remove(id: number): boolean {
    const initialLength = this.users.length;
    this.users = this.users.filter(user => user.id !== id);
    return this.users.length < initialLength; // 判断是否删除了元素
  }
}

修改 src/users/users.controller.ts 以使用这些方法:

// src/users/users.controller.ts (修改)
import { Controller, Get, Post, Put, Delete, Param, Body, NotFoundException, HttpCode, HttpStatus } from '@nestjs/common';
import { UsersService } from './users.service';
import { ParseIntPipe } from '@nestjs/common'; // 导入 ParseIntPipe

// 定义一个简单的 DTO 用于创建用户 (后面会详细讲 DTO)
class CreateUserDto {
  name: string;
  age: number;
}

@Controller('api/users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get() // GET /api/users
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id') // GET /api/users/:id
  findOne(@Param('id', ParseIntPipe) id: number) {
    const user = this.usersService.findOne(id);
    if (!user) {
      throw new NotFoundException(`找不到 ID 为 ${id} 的用户`); // 抛出 HttpException
    }
    return user;
  }

  @Post() // POST /api/users
  @HttpCode(HttpStatus.CREATED) // 返回 201 Created 状态码
  create(@Body() createUserDto: CreateUserDto) {
    const newUser = this.usersService.create(createUserDto);
    return newUser;
  }

  @Put(':id') // PUT /api/users/:id
  update(@Param('id', ParseIntPipe) id: number, @Body() updateUserDto: Partial<CreateUserDto>) {
    const updatedUser = this.usersService.update(id, updateUserDto);
    if (!updatedUser) {
      throw new NotFoundException(`找不到 ID 为 ${id} 的用户`);
    }
    return updatedUser;
  }

  @Delete(':id') // DELETE /api/users/:id
  @HttpCode(HttpStatus.NO_CONTENT) // 返回 204 No Content 状态码
  remove(@Param('id', ParseIntPipe) id: number) {
    const removed = this.usersService.remove(id);
    if (!removed) {
       // 如果删除不成功(比如 ID 不存在),可以选择抛出 404
       throw new NotFoundException(`找不到 ID 为 ${id} 的用户,无法删除`);
    }
    // NestJS 会自动处理返回 204,因为方法没有返回值,且我们设置了 @HttpCode(HttpStatus.NO_CONTENT)
    // 如果不设置 @HttpCode,默认会返回 200
  }
}

小结: 数据库是后端应用存放数据的“原材料仓库”。ORM/ODM 提供了与数据库交互的便捷方式。在 NestJS 中,Service 负责与数据源(可以是内存数组、数据库、其他服务)进行交互,而控制器则通过调用 Service 来获取或修改数据。使用内存数组是学习阶段模拟数据持久化的简单方法。

练习:

  1. 在你的 my-backend 项目中,确认你已经创建了 products 模块和服务。
  2. 修改 products.service.ts,使用一个内存数组来存储产品数据(包含 ID、名称、价格等属性)。
  3. ProductsService 中实现以下方法,操作这个内存数组:
    • findAll(): 返回所有产品。
    • findOne(id: number): 根据 ID 查找单个产品。
    • create(productData: { name: string; price: number }): 添加新产品,并简单生成一个唯一 ID。
    • update(id: number, updateData: Partial<{ name: string; price: number }>): 根据 ID 更新产品。
    • remove(id: number): 根据 ID 删除产品。
  4. 修改 products.controller.ts,更新你的路由处理函数 (findAll, findOne, create, update, remove),使其调用 ProductsService 中对应的方法来处理数据。
  5. findOne, update, remove 方法中,如果通过 ID 找不到产品,则抛出 NotFoundException
  6. 运行应用,使用 Postman 等工具测试你的产品 API 的增删改查功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低代码布道师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值