NestJS学习笔记-02-模块、控制器与服务,手把手构建你的第一个CRUD API!🚀

大家好,我是汪小成!

上一篇,我们成功点亮了NestJS的"Hello World!",感受到了它清晰的项目结构和开箱即用的便捷。是不是有点意犹未尽?

但是"Hello World"终究只是起点。我们真正的目标是构建功能强大、逻辑清晰的后端应用。那么问题来了:

  • 当功能增多,代码如何有效地组织 ?总不能所有东西都塞在app.controller.ts里吧?🤔
  • 处理HTTP请求的逻辑(比如解析参数、验证)和核心业务逻辑 应该放在哪里?如何解耦?🤝
  • NestJS一直强调的依赖注入(DI) 到底是什么?它如何让我们的代码更优雅?✨

别急,今天这篇,我们就来揭晓答案!我们将深入NestJS的三大核心基石:模块 (Module)、控制器 (Controller) 和服务 (Service)

更重要的是,我们将亲手实战 ,构建一个完整的、虽简但五脏俱全的用户管理CRUD API (创建、读取、更新、删除)!准备好迎接干货了吗?

🧩 三剑客登场:模块、控制器、服务

想象一下,你要盖一座房子:

  • 模块 (Module - @Module) : 就像房子的不同功能区域 (客厅、卧室、厨房)。它负责组织相关的代码单元,将控制器和服务打包在一起。模块化让应用结构清晰,便于管理和复用。
  • 控制器 (Controller - @Controller) : 就像房子的门卫/前台 。它负责接收外部请求 (HTTP请求),进行一些基础处理(比如检查门票、指引方向),然后将具体的事务委托给相应的服务部门。
  • 服务 (Service/Provider - @Injectable) : 就像房子里提供具体服务 的部门(厨师、管家)。它负责处理核心的业务逻辑,比如数据存储、计算、调用其他服务等。控制器不应该关心这些细节。

它们的关系大致是:模块 包含控制器服务控制器 接收请求并调用服务来处理业务逻辑。

而连接这一切的魔法胶水,就是依赖注入 (Dependency Injection - DI)!

🚀 实战启程:构建用户管理 CRUD API

目标: 实现对用户列表的增、删、改、查操作。暂时,我们先用内存数组来模拟数据库存储。

注意: 我们使用了uuid库来生成唯一的用户ID,需要先安装它:pnpm install uuid && pnpm install --save-dev @types/uuid

1. 创建user模块、控制器、服务

使用nest命令创建02.user-crud-api项目。

打开终端,进入02.user-crud-api项目目录,祭出我们的神器Nest CLI

bash 复制代码
# 1. 生成user模块
nest g module user

# 2. 生成user控制器 (g 是generate的缩写, --no-spec不生成测试文件)
nest g controller user --no-spec

# 3. 生成user服务
nest g service user --no-spec

CLI会自动帮你创建好src/user 目录,以及user.module.ts, user.controller.ts, user.service.ts文件,并自动在user.module.ts 中声明控制器和服务,同时将UserModule导入到根模块app.module.ts

检查一下src/app.module.ts,你会看到UserModule已经被添加到了imports数组中:

typescript 复制代码
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module'; // <-- 看这里!自动导入了

@Module({
  imports: [UserModule], // <-- 确保UserModule在这里
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

2. 定义用户数据结构与服务逻辑

首先,在src/user目录下创建一个user.model.ts来描述用户的结构:

typescript 复制代码
export interface User {
  id: string;
  username: string;
  password: string;
  status: UserStatus;
}

export enum UserStatus {
  NORMAL = 'NORMAL',
  LOCKED = 'LOCKED'
}

接下来,编辑src/user/user.service.ts,实现具体的业务逻辑:

typescript 复制代码
import { Injectable, NotFoundException } from '@nestjs/common';
import { User, UserStatus } from './user.model';
import { v4 as uuid } from 'uuid';

@Injectable()
export class UserService {
  // 使用私有数组模拟数据库
  private users: User[] = [];

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

  // 获取单个用户
  getUserById(id: string): User {
    const user= this.users.find((user) => user.id === id);
    if (!user) {
      throw new NotFoundException(`User with ID "${id}" not found`);
    }
    return user;
  }

  // 创建用户
  createUser(username: string, password: string): User {
    const user: User = {
      id: uuid(),
      username,
      password,
      status: UserStatus.NORMAL,
    };
    this.users.push(user);
    return user;
  }

  // 更新用户
  updateUser(id: string, status: UserStatus): User {
    const user= this.getUserById(id);
    user.status = status;
    return user;
  }

  // 删除用户
  deleteUser(id: string): void {
    this.users = this.users.filter((user) => user.id !== id);
  }

}

我们还利用了NestJS内置的NotFoundException来处理找不到用户的情况,这会返回一个标准的404错误。

3. 编写控制器处理 HTTP 请求

现在轮到控制器 (src/user/user.controller.ts) 了。它的职责是定义路由,接收HTTP请求参数,并调用UserService来完成工作。

typescript 复制代码
import { Controller, Get, Post, Body, Param, Delete, Patch } from '@nestjs/common';
import { UserService } from './user.service';
import { User, UserStatus } from './user.model';

@Controller('user')
export class UserController {
  // 依赖注入!Nest自动将UserService实例注入这里
  constructor(private userService: UserService) { }

  // 获取所有用户
  @Get()
  getAllUser(): User[] {
    return this.userService.getAllUser();
  }

  // 根据ID获取单个用户
  @Get(':id')
  getUserById(@Param('id') id: string): User {
    return this.userService.getUserById(id);
  }

  // 创建用户
  // @Body()装饰器用于从请求体中提取数据
  @Post()
  createUser(@Body("username") username: string, @Body("password") password: string): User {
    return this.userService.createUser(username, password);
  }

  // 根据ID更新用户状态
  @Patch(':id/status')
  updateUserStatus(@Param('id') id: string, @Body('status') status: UserStatus): User {
    return this.userService.updateUser(id, status);
  }

  // 根据ID删除用户
  @Delete(':id')
  deleteUser(@Param('id') id: string): void {
    this.userService.deleteUser(id);
  }
}

关键点解读:

  • @Controller('user'): 这个控制器的所有路由都将以/user作为前缀。
  • @Get(), @Post(), @Delete(), @Patch(): 这些HTTP方法装饰器将路由映射到控制器的方法。
  • @Param('id'): 路由参数装饰器 ,用于提取URL路径中的参数。/user/:id中的:id就是占位符。
  • @Body(): 请求体参数装饰器 ,用于提取POST/PATCH/PUT请求的body数据。@Body('username')则只提取body中的username字段。
  • 依赖注入 (DI) : 注意 constructor(private usersService: UserService)。我们只需要在构造函数中声明依赖的类型 (UserService),NestJS 的DI容器就会自动找到并创建/提供 UserService 的实例,并将其赋给 this.usersService。我们完全不需要this.usersService = new UserService()!这就是DI的魔力,它让控制器和服务的耦合度大大降低 ,也让单元测试变得更容易(可以轻松替换依赖)。

4. 测试你的 API

确保你的应用仍在运行 (pnpm start:dev)。现在,打开Postman测试工具,开始测试我们刚创建的接口吧!

1. 创建用户

方法 : POST URL : http://localhost:3000/user 请求体 (JSON):

json 复制代码
{
    "username": "ddcherry.cn",
    "password": "123456"
}

响应:

json 复制代码
{
    "id": "62b35359-4900-4c93-8c5c-14ef2aa03c8f",
    "username": "ddcherry.cn",
    "password": "123456",
    "status": "NORMAL"
}

2. 获取所有用户

方法 : GET URL : http://localhost:3000/user 响应:

json 复制代码
[
    {
        "id": "62b35359-4900-4c93-8c5c-14ef2aa03c8f",
        "username": "ddcherry.cn",
        "password": "123456",
        "status": "NORMAL"
    }
]

3. 获取单个用户

方法 : GET URL : http://localhost:3000/user/62b35359-4900-4c93-8c5c-14ef2aa03c8f 响应:

json 复制代码
{
    "id": "62b35359-4900-4c93-8c5c-14ef2aa03c8f",
    "username": "ddcherry.cn",
    "password": "123456",
    "status": "NORMAL"
}

4. 更新用户状态

方法 : PATCH URL : http://localhost:3000/user/7e7f1a88-ca86-488e-9cd6-19d9a6af02c7/status 请求体 (JSON):

json 复制代码
{"status": "LOCKED"}

响应:

json 复制代码
{
    "id": "7e7f1a88-ca86-488e-9cd6-19d9a6af02c7",
    "username": "ddcherry.cn",
    "password": "123456",
    "status": "LOCKED"
}

** 5. 删除用户**

方法 : DELETE URL : http://localhost:3000/user/7e7f1a88-ca86-488e-9cd6-19d9a6af02c7 响应:

  • 成功:返回200状态码
  • 验证:再次调用获取所有用户接口可确认该用户已删除

✨ 核心价值回顾

通过今天的实战,我们体会到了NestJS核心设计的精妙之处:

  • 模块化 (@Module): 让代码组织有序,职责分明。
  • 关注点分离 : 控制器 (@Controller) 处理HTTP交互,服务 (@Injectable) 处理业务逻辑,各司其职。
  • 依赖注入 (DI): 优雅地解耦组件,提高代码的可维护性和可测试性。
  • 装饰器 (@Get, @Param, @Body 等): 以声明式的方式定义路由和参数处理,代码简洁易读。
  • 内置工具 : 异常过滤器 (NotFoundException) 等提供了开箱即用的便利。

📢 互动与支持

保持关注,不要走开!

如果你觉得这篇文章对你有帮助,请不吝:点赞 👍在看 👀转发 ↗️ ,让更多朋友加入我们的NestJS探索之旅

💬 欢迎在评论区留言分享你的想法或疑问,我们一起交流进步!

🔔 关注我的公众号「Java小成」 ,第一时间获取 《NestJS学习笔记》系列文章更新

本文代码已同步至GitHub:https://github.com/wanggch/learn-nestjs/tree/main/02.user-crud-api

相关推荐
方块海绵1 分钟前
RabbitMQ总结
后端
星辰大海的精灵2 分钟前
Python 中利用算法优化性能的方法
后端·python
雷渊3 分钟前
深度分析Scroll API(滚动搜索)方案
后端
AronTing3 分钟前
11-Spring Cloud OpenFeign 深度解析:从基础概念到对比实战
后端·spring cloud·架构
yifuweigan4 分钟前
J2Cache 实现多级缓存
后端
洛神灬殇7 分钟前
【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(时间事件驱动执行控制)
redis·后端
Java中文社群9 分钟前
SpringAI版本更新:向量数据库不可用的解决方案!
java·人工智能·后端
日月星辰Ace10 分钟前
蓝绿部署
运维·后端
D龙源12 分钟前
VSCode-IoC和DI
后端·架构
陵易居士28 分钟前
Spring如何解决项目中的循环依赖问题?
java·后端·spring