文章首先本人公众号:Java小成
大家好,我是汪小成。
最近在常用NestJS,刚好有个做一个在线工具箱的想法。工具箱需要一个简单的后台服务,于是就使用NestJS开发了一个后台服务。借着这个简单的项目学习下NestJS。我会记录下使用NestJS创建后台服务的每一个过程,感兴趣的朋友可以关注下我的公众号Java小成。
目前已经完成的工具如下:
✅JSON格式化 ------ 快速美化/压缩JSON数据
✅Base64编码/解码 ------ 轻松处理加密数据
✅时间戳转换 ------ 秒级时间戳 ↔ 可读日期
✅文本对比 ------ 高亮显示差异,排查代码/配置更轻松
✅二维码生成器 ------高度可定制 ,支持Logo、颜色、尺寸调整
✅URL编码/解码 ------ 一键处理特殊字符
✅UUID生成器 ------ 快速生成唯一标识符
✅JWT解析器 ------ 直接解码JWT Token,查看Payload
体检地址:https://ddcherry.cn/toolbox/
。
1、前言
在前面一章,使用Cursor创建了一个NestJS后台服务项目。接下来详细地拆解下项目。
2、技术栈
- NestJS: v11.0.1
- TypeScript: v5.7.3
- MySQL8
- Prisma: v6.5.0
- JWT
- bcrypt: v5.1.1
- Jest: v29.7.0
- supertest: v7.0.0
- ESLint: v9.18.0
- Prettier: v3.4.2
- pnpm:v10.0.0
- Redis
3、目录结构
标准、清晰的目录结构方便项目的后期维护。
ruby
.
├── prisma/ # Prisma相关目录
│ ├── migration/ # 数据库迁移脚本
│ │ └── init.sql # 初始化数据库脚本
│ ├── schema.prisma # Prisma模型定义
│ └── prisma.service.ts # Prisma服务
├── src/ # 源代码目录
│ ├── config/ # 配置文件目录
│ │ ├── database.config.ts # 数据库配置
│ │ ├── redis.config.ts # Redis配置
│ │ └── jwt.config.ts # JWT配置
│ ├── module/ # 功能模块目录
│ │ ├── auth/ # 认证模块
│ │ │ ├── dto/ # 数据传输对象
│ │ │ └── entity/ # 实体
│ │ ├── user/ # 用户模块
│ │ │ ├── dto/
│ │ │ ├── entity/
│ │ │ ├── user.controller.ts
│ │ │ ├── user.service.ts
│ │ │ └── user.module.ts
│ │ └── tool/ # 工具模块
│ │ ├── dto/
│ │ ├── entity/
│ │ ├── tool.controller.ts
│ │ ├── tool.service.ts
│ │ └── tool.module.ts
│ ├── common/ # 通用功能目录
│ │ ├── decorator/ # 自定义装饰器
│ │ ├── filter/ # 异常过滤器
│ │ ├── guard/ # 守卫
│ │ ├── interceptor/ # 拦截器
│ │ ├── middleware/ # 中间件
│ │ └── pipe/ # 管道
│ ├── interface/ # 接口定义目录
│ │ └── index.ts # 接口导出文件
│ ├── util/ # 工具函数目录
│ │ └── index.ts # 工具函数导出文件
│ ├── app.module.ts # 主模块
│ └── main.ts # 应用入口
├── test/ # 测试目录
│ ├── app.e2e-spec.ts # 端到端测试
│ └── jest-e2e.json # Jest端到端测试配置
├── .gitignore # Git忽略文件配置
├── .prettierrc # Prettier格式化配置
├── eslint.config.mjs # ESLint配置
├── nest-cli.json # NestJS CLI配置
├── package.json # 项目依赖和脚本配置
├── tsconfig.json # TypeScript配置
└── tsconfig.build.json # TypeScript构建配置
主要目录解释:
-
prisma:包含数据库相关的配置和迁移文件,使用Prisma作为ORM工具。
-
src:项目核心代码目录
- config:存放各种配置文件。
- module:按照功能划分的模块,采用NestJS的模块化设计
- common:存放通用的功能组件,如装饰器、过滤器等。
- interface和util:存放接口定义和工具函数。
-
test:包含端到端测试文件和Jest配置。
-
根目录下的配置文件:包含项目构建、格式化、版本控制等配置。
4、环境配置
项目使用根目录下.env
文件管理敏感配置。
配置文件中主要包括:
- 数据库连接字符串(DATABASE_URL)
- JWT密钥和过期时间(JWT_SECRET, JWT_EXPIRES_IN)
- Redis连接配置
5、Prisma数据库配置
在schema.prisma
文件中主要配置了如下几部分:
- Client生成器配置
- 数据源配置
- 数据模型定义
- 枚举类型定义
- 数据库表名映射
5.1、Client生成器配置
ini
generator client {
provider = "prisma-client-js"
}
5.2、数据源配置
ini
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
-provider = "mysql"
:指定使用MySQL作为数据库。 -url = env("DATABASE_URL")
:指定数据库连接信息,从环境变量中读取。
5.3、数据模型定义
项目中定义了User
、Tool
两个模型。这里以User
模型为例。
kotlin
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
role Role @default(USER)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("sys_user")
}
- 主键
id
自增。 -email
字段唯一。 -role
角色使用枚举类型,默认值为USER
。 -@@map("sys_user")
将模型映射到sys_user
数据表。
5.4、枚举类型定义
arduino
enum Role {
USER
ADMIN
}
6、用户模块
通过用户模块了解一下一个简单的业务模块包含哪些组成部分。
UserModule] -->|注册依赖| A[用户控制器
UserController] G -->|注册依赖| B[用户服务
UserService] G -->|注册依赖| D[数据库服务
PrismaService] G -.->|服务导出| E[用户服务
UserService] %% 增强样式定义 classDef module fill:#4CAF50,stroke:#388E3C,color:white,stroke-width:2px classDef controller fill:#2196F3,stroke:#0b7dda,color:white,stroke-width:2px classDef service fill:#FF9800,stroke:#e68a00,color:white classDef service1 fill:white,stroke:#e68a00,color:#FF9800 classDef database fill:#9C27B0,stroke:#7b1fa2,color:white,stroke-width:2px %% 应用样式 class G module class A controller class B service class D database class E service1
6.1、模块定义
typescript
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { PrismaService } from '../../prisma/prisma.service';
@Module({
controllers: [UserController],
providers: [UserService, PrismaService],
exports: [UserService],
})
export class UserModule {}
在 NestJS 中,模块(Module)是组织应用程序的核心单元,用于封装相关的控制器、服务和其他提供者。
模块通过@Module()
装饰器定义,并指定其元数据(如providers
、controllers
、imports
和exports
)。
@Module()
装饰器接受一个对象,包含以下关键属性:
属性 | 说明 |
---|---|
controllers |
注册该模块中使用的控制器(Controller)。 |
providers |
注册该模块中可注入的提供者(如 Service、Repository、Helper 等)。 |
imports |
导入其他模块。 |
exports |
导出该模块的提供者,供其他模块复用。 |
6.2、控制器
UserController] A -->|使用| B[用户服务
UserService] A -->|使用| D[DTO
CreateUserDto] %% 增强样式定义 classDef controller fill:#2196F3,stroke:#0b7dda,color:white,stroke-width:2px classDef service fill:#FF9800,stroke:#e68a00,color:white classDef dto fill:#9C27B0,stroke:#7b1fa2,color:white,stroke-width:2px %% 应用样式 class A controller class B service class D dto
typescript
import { Controller, Get, Post, Body, Param, Delete } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
在 NestJS 中,控制器(Controller) 负责处理传入的 HTTP 请求并返回响应。它通过装饰器(如@Controller()
、@Get()
、@Post()
等)定义路由和请求处理逻辑。
NestJS 提供了一系列装饰器来处理不同的 HTTP 方法:
装饰器 | HTTP 方法 | 示例用法 |
---|---|---|
@Get() |
GET | @Get('profile') →GET /user/profile |
@Post() |
POST | @Post() →POST /user |
@Put() |
PUT | @Put(':id') →PUT /user/1 |
@Delete() |
DELETE | @Delete(':id') →DELETE /user/1 |
@Patch() |
PATCH | @Patch(':id') →PATCH /user/1 |
@Options() |
OPTIONS | @Options() →OPTIONS /user |
@Head() |
HEAD | @Head() →HEAD /user |
@All() |
所有方法 | @All() → 匹配所有 HTTP 方法 |
路由参数与请求数据
路径参数(Path Parameters)
使用@Param()
获取路由参数:
typescript
@Get(':id') // 匹配 GET /user/1
findOne(@Param('id') id: string): string {
return`This action returns user #${id}`;
}
查询参数(Query Parameters)
使用@Query()
获取查询参数:
typescript
@Get() // 匹配 GET /user?limit=10
findAll(@Query('limit') limit: number) {
return`Returning ${limit} users`;
}
请求体(Request Body)
使用@Body()
获取请求体数据(需配合 DTO 类):
typescript
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
6.3、Service
typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from '@prisma/client';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
return this.prisma.user.create({
data: {
...createUserDto,
password: hashedPassword,
},
});
}
async findAll(): Promise<User[]> {
return this.prisma.user.findMany();
}
async findOne(id: number): Promise<User | null> {
return this.prisma.user.findUnique({
where: { id },
});
}
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({
where: { email },
});
}
async remove(id: number): Promise<User> {
return this.prisma.user.delete({
where: { id },
});
}
}
- 使用
@Injectable()
装饰器标记一个类为可注入的服务。 - 构造方法注入
PrismaService
进行数据库操作。
6.4、实体类
typescript
import { Role } from '@prisma/client';
export class User {
id: number;
email: string;
password: string;
name?: string;
role: Role;
createdAt: Date;
updatedAt: Date;
}
作用:导入枚举类型Role
,定义实体类User
。
总结
本篇文章中介绍了在NestJS项目中怎样开发一个基础的业务模块、怎样定义一个控制器类、怎样定义一个Service、使用Prisma进行简单地增删改查操作、怎样定义一个实体类、怎样定义一个DTO类。