大家好,我是汪小成!
上一篇,我们成功点亮了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
。