{T}

NestJS REPL模式调试与数据插入实战

NestJS REPL模式调试与数据插入实战

学习目标:掌握 NestJS REPL 模式的使用、通过 REPL 模式调用 Service 方法、使用 include 查询关联数据。


一、NestJS REPL 模式概述

1.1 什么是 REPL 模式

code
REPL 模式介绍:
│
├── REPL 定义
│   ├── Read-Eval-Print-Loop
│   ├── 交互式编程环境
│   └── 实时执行代码并查看结果
│
├── NestJS REPL 模式特点
│   ├── 无需启动完整服务器
│   ├── 直接调用 Service 方法
│   ├── 实时调试业务逻辑
│   └── 快速验证功能
│
└── 使用场景
    ├── 跳过 Controller 直接测试 Service
    ├── 快速插入测试数据
    ├── 调试复杂查询逻辑
    └── 验证关联关系

1.2 REPL 模式优势

维度传统方式REPL 模式
启动速度需要启动完整服务器快速启动
调试方式需要通过 HTTP 请求直接调用方法
数据准备需要创建 Controller直接插入数据
交互性需要使用 Postman命令行交互
效率较慢快速高效

二、启动 REPL 模式

2.1 启动命令

bash
# 启动 NestJS REPL 模式
pnpm start:repl

# 或使用 npx
npx nest start --repl

# 或使用 npm
npm run start:repl

2.2 REPL 模式界面

bash
# 启动后的界面
$ pnpm start:repl

> my-project@1.0.0 start:repl
> nest start --repl

[Nest] LOG [NestFactory] Starting Nest application...
[Nest] LOG [InstanceLoader] AppModule dependencies initialized
[Nest] LOG [InstanceLoader] CourseModule dependencies initialized
[Nest] LOG [NestApplication] Nest application successfully started

REPL server is running.
Type `help` for more information.

>

2.3 REPL 常用命令

code
REPL 常用命令:
│
├── help
│   └── 查看帮助信息
│
├── get <ServiceName>
│   └── 获取 Service 实例
│
├── await <expression>
│   └── 执行异步操作
│
├── .exit 或 Ctrl+D
│   └── 退出 REPL 模式
│
└── .clear
    └── 清空上下文

三、使用 REPL 模式插入数据

3.1 插入 Type 数据

步骤一:获取 CourseService 实例

javascript
// 在 REPL 中执行
await get(CourseService)

步骤二:调用 createType 方法插入数据

javascript
// 插入第一条 type 数据
await get(CourseService).createType({
  name: '推荐内容'
})

// 输出示例
{
  id: 9,
  name: '推荐内容',
  createdAt: 2024-01-01T00:00:00.000Z,
  updatedAt: 2024-01-01T00:00:00.000Z
}

// 插入更多 type 数据
await get(CourseService).createType({ name: '每日一个' })
await get(CourseService).createType({ name: '精品微课' })
await get(CourseService).createType({ name: '学习计划' })
await get(CourseService).createType({ name: '优质专栏' })

3.2 插入 Tag 数据并关联 Type

关联关系说明

code
Tag 和 Type 的关联关系:
│
├── Type(类型)
│   ├── id: 9, name: '推荐内容'
│   ├── id: 10, name: '每日一个'
│   ├── id: 11, name: '精品微课'
│   ├── id: 12, name: '学习计划'
│   └── id: 13, name: '优质专栏'
│
└── Tag(标签)
    ├── id: 1, name: 'Vue3 项目实战', typeId: 9
    ├── id: 2, name: 'React18 新特性', typeId: 9
    ├── id: 3, name: 'TypeScript 进阶', typeId: 9
    └── id: 4, name: 'Node.js 实战', typeId: 10

插入 Tag 数据

javascript
// 插入 tag 并关联 type(typeId = 9 表示推荐内容)
await get(CourseService).createTag({
  name: 'Vue3 项目实战',
  typeId: 9
})

// 输出示例
{
  id: 1,
  name: 'Vue3 项目实战',
  typeId: 9,
  createdAt: 2024-01-01T00:00:00.000Z,
  updatedAt: 2024-01-01T00:00:00.000Z
}

// 插入更多 tag 数据
await get(CourseService).createTag({ name: 'React18 新特性', typeId: 9 })
await get(CourseService).createTag({ name: 'TypeScript 进阶', typeId: 9 })
await get(CourseService).createTag({ name: 'Node.js 实战', typeId: 10 })
await get(CourseService).createTag({ name: 'Prisma 详解', typeId: 10 })

3.3 完整的数据插入流程

code
数据插入完整流程:
│
├── 第一步:启动 REPL 模式
│   └── pnpm start:repl
│
├── 第二步:获取 Service 实例
│   └── await get(CourseService)
│
├── 第三步:插入 Type 数据
│   ├── createType({ name: '推荐内容' })
│   ├── createType({ name: '每日一个' })
│   ├── createType({ name: '精品微课' })
│   ├── createType({ name: '学习计划' })
│   └── createType({ name: '优质专栏' })
│
├── 第四步:插入 Tag 数据并关联
│   ├── createTag({ name: 'Vue3 项目实战', typeId: 9 })
│   ├── createTag({ name: 'React18 新特性', typeId: 9 })
│   ├── createTag({ name: 'TypeScript 进阶', typeId: 9 })
│   └── createTag({ name: 'Node.js 实战', typeId: 10 })
│
└── 第五步:验证数据
    ├── 查询数据库
    └── 使用 Prisma Studio 查看

四、使用 REPL 模式查询数据

4.1 查询 Tag 数据(包含关联的 Type)

javascript
// 查询所有 tag,并包含关联的 type
await get(CourseService).getTags()

// 输出示例
[
  {
    id: 1,
    name: 'Vue3 项目实战',
    typeId: 9,
    type: {
      id: 9,
      name: '推荐内容'
    }
  },
  {
    id: 2,
    name: 'React18 新特性',
    typeId: 9,
    type: {
      id: 9,
      name: '推荐内容'
    }
  },
  {
    id: 3,
    name: 'TypeScript 进阶',
    typeId: 9,
    type: {
      id: 9,
      name: '推荐内容'
    }
  }
]

4.2 查询 Type 数据(包含关联的 Tags)

Service 方法实现

typescript
// src/modules/course/course.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';

@Injectable()
export class CourseService {
  constructor(private prisma: PrismaService) {}

  // 根据 ID 查询 type,并包含关联的 tags
  async getTypeById(id: number) {
    return this.prisma.courseTypes.findUnique({
      where: { id },
      include: {
        tags: true,  // 包含关联的 tags
      },
    });
  }

  // 查询所有 tags,并包含关联的 type
  async getTags() {
    return this.prisma.courseTags.findMany({
      include: {
        type: true,  // 包含关联的 type
      },
    });
  }
}

REPL 模式查询

javascript
// 查询 ID 为 9 的 type,并包含关联的 tags
await get(CourseService).getTypeById(9)

// 输出示例
{
  id: 9,
  name: '推荐内容',
  createdAt: 2024-01-01T00:00:00.000Z,
  updatedAt: 2024-01-01T00:00:00.000Z,
  tags: [
    {
      id: 1,
      name: 'Vue3 项目实战',
      typeId: 9
    },
    {
      id: 2,
      name: 'React18 新特性',
      typeId: 9
    },
    {
      id: 3,
      name: 'TypeScript 进阶',
      typeId: 9
    }
  ]
}

4.3 访问嵌套数据

javascript
// 查询 type 并获取第一条 tag
const result = await get(CourseService).getTypeById(9)

// 访问第一条 tag
result.tags[0]

// 输出
{
  id: 1,
  name: 'Vue3 项目实战',
  typeId: 9
}

// 访问 tag 的 name
result.tags[0].name
// 输出:'Vue3 项目实战'

五、Prisma include 详解

5.1 include 基本语法

typescript
// 基本语法
await prisma.model.findMany({
  include: {
    relationName: true,  // 包含关联数据
  },
});

// 查询 type 并包含 tags
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  include: {
    tags: true,
  },
});

5.2 include vs select

维度includeselect
作用包含关联数据选择特定字段
默认字段包含所有字段只包含选择的字段
关联数据可以包含可以包含
使用场景需要完整数据和关联只需要部分字段
typescript
// include:包含所有字段 + 关联数据
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  include: {
    tags: true,
  },
});
// 返回:{ id, name, createdAt, updatedAt, tags }

// select:只选择特定字段 + 关联数据
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  select: {
    id: true,
    name: true,
    tags: {
      select: {
        id: true,
        name: true,
      },
    },
  },
});
// 返回:{ id, name, tags: [{ id, name }] }

5.3 include 嵌套查询

typescript
// 多层嵌套 include
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  include: {
    tags: {
      include: {
        courses: true,  // 包含 tag 关联的课程
      },
    },
  },
});

5.4 include 过滤和排序

typescript
// 查询 type 并包含过滤后的 tags
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  include: {
    tags: {
      where: {
        name: {
          contains: 'Vue',  // 只包含名称包含 'Vue' 的 tag
        },
      },
      orderBy: {
        createdAt: 'desc',  // 按创建时间倒序
      },
      take: 5,  // 最多返回 5 条
    },
  },
});

六、REPL 模式实战场景

6.1 场景一:快速插入测试数据

javascript
// 批量插入 type 数据
const types = ['推荐内容', '每日一个', '精品微课', '学习计划', '优质专栏'];

for (const name of types) {
  await get(CourseService).createType({ name });
  console.log(`创建 type: ${name}`);
}

// 批量插入 tag 数据
const tags = [
  { name: 'Vue3 项目实战', typeId: 9 },
  { name: 'React18 新特性', typeId: 9 },
  { name: 'TypeScript 进阶', typeId: 9 },
  { name: 'Node.js 实战', typeId: 10 },
  { name: 'Prisma 详解', typeId: 10 },
];

for (const tag of tags) {
  await get(CourseService).createTag(tag);
  console.log(`创建 tag: ${tag.name}`);
}

6.2 场景二:调试复杂查询

javascript
// 调试分页查询
const page = 1;
const size = 10;

const result = await get(CourseService).findAll(page, size);
console.log('总数据:', result.total);
console.log('当前页数据:', result.data.length);

// 调试关联查询
const typeWithTags = await get(CourseService).getTypeById(9);
console.log('Type 名称:', typeWithTags.name);
console.log('Tags 数量:', typeWithTags.tags.length);

6.3 场景三:验证数据一致性

javascript
// 验证 type 和 tag 的关联关系
const type = await get(CourseService).getTypeById(9);

console.log('Type:', type.name);
console.log('关联的 Tags:');
type.tags.forEach((tag, index) => {
  console.log(`  ${index + 1}. ${tag.name} (ID: ${tag.id})`);
});

// 输出:
// Type: 推荐内容
// 关联的 Tags:
//   1. Vue3 项目实战 (ID: 1)
//   2. React18 新特性 (ID: 2)
//   3. TypeScript 进阶 (ID: 3)

6.4 场景四:数据清理

javascript
// 清理测试数据
await get(CourseService).deleteAllTags()
await get(CourseService).deleteAllTypes()

console.log('测试数据已清理');

七、完整实战示例

7.1 Service 层实现

typescript
// src/modules/course/course.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { CreateTypeDto } from './dto/create-type.dto';
import { CreateTagDto } from './dto/create-tag.dto';

@Injectable()
export class CourseService {
  constructor(private prisma: PrismaService) {}

  // 创建 type
  async createType(createTypeDto: CreateTypeDto) {
    return this.prisma.courseTypes.create({
      data: createTypeDto,
    });
  }

  // 创建 tag
  async createTag(createTagDto: CreateTagDto) {
    return this.prisma.courseTags.create({
      data: createTagDto,
    });
  }

  // 查询所有 tags(包含关联的 type)
  async getTags() {
    return this.prisma.courseTags.findMany({
      include: {
        type: true,
      },
    });
  }

  // 根据 ID 查询 type(包含关联的 tags)
  async getTypeById(id: number) {
    return this.prisma.courseTypes.findUnique({
      where: { id },
      include: {
        tags: true,
      },
    });
  }

  // 查询所有 types(包含关联的 tags)
  async getTypes() {
    return this.prisma.courseTypes.findMany({
      include: {
        tags: true,
      },
    });
  }

  // 删除所有 tags
  async deleteAllTags() {
    return this.prisma.courseTags.deleteMany();
  }

  // 删除所有 types
  async deleteAllTypes() {
    return this.prisma.courseTypes.deleteMany();
  }
}

7.2 DTO 定义

typescript
// src/modules/course/dto/create-type.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';

export class CreateTypeDto {
  @IsString()
  @IsNotEmpty()
  name: string;
}
typescript
// src/modules/course/dto/create-tag.dto.ts
import { IsString, IsNotEmpty, IsInt } from 'class-validator';

export class CreateTagDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsInt()
  typeId: number;
}

7.3 Prisma Schema

prisma
// prisma/schema.prisma

// 课程类型
model CourseTypes {
  id        Int         @id @default(autoincrement())
  name      String
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
  
  tags      CourseTags[]  // 一对多关系
  
  @@map("course_types")
}

// 课程标签
model CourseTags {
  id        Int         @id @default(autoincrement())
  name      String
  typeId    Int
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
  
  type      CourseTypes @relation(fields: [typeId], references: [id])
  
  @@map("course_tags")
}

7.4 REPL 操作完整流程

javascript
// ========== 1. 启动 REPL ==========
// $ pnpm start:repl

// ========== 2. 插入 Type 数据 ==========
await get(CourseService).createType({ name: '推荐内容' })
// 返回:{ id: 9, name: '推荐内容', ... }

await get(CourseService).createType({ name: '每日一个' })
// 返回:{ id: 10, name: '每日一个', ... }

await get(CourseService).createType({ name: '精品微课' })
// 返回:{ id: 11, name: '精品微课', ... }

await get(CourseService).createType({ name: '学习计划' })
// 返回:{ id: 12, name: '学习计划', ... }

await get(CourseService).createType({ name: '优质专栏' })
// 返回:{ id: 13, name: '优质专栏', ... }

// ========== 3. 插入 Tag 数据并关联 Type ==========
await get(CourseService).createTag({ name: 'Vue3 项目实战', typeId: 9 })
// 返回:{ id: 1, name: 'Vue3 项目实战', typeId: 9, ... }

await get(CourseService).createTag({ name: 'React18 新特性', typeId: 9 })
// 返回:{ id: 2, name: 'React18 新特性', typeId: 9, ... }

await get(CourseService).createTag({ name: 'TypeScript 进阶', typeId: 9 })
// 返回:{ id: 3, name: 'TypeScript 进阶', typeId: 9, ... }

await get(CourseService).createTag({ name: 'Node.js 实战', typeId: 10 })
// 返回:{ id: 4, name: 'Node.js 实战', typeId: 10, ... }

// ========== 4. 查询 Tag(包含关联的 Type) ==========
await get(CourseService).getTags()
// 返回:[{ id: 1, name: 'Vue3 项目实战', typeId: 9, type: { id: 9, name: '推荐内容' } }, ...]

// ========== 5. 查询 Type(包含关联的 Tags) ==========
await get(CourseService).getTypeById(9)
// 返回:{ id: 9, name: '推荐内容', tags: [{ id: 1, name: 'Vue3 项目实战' }, ...] }

// ========== 6. 访问嵌套数据 ==========
const result = await get(CourseService).getTypeById(9)
result.tags[0]
// 返回:{ id: 1, name: 'Vue3 项目实战', typeId: 9 }

result.tags[0].name
// 返回:'Vue3 项目实战'

八、REPL 模式最佳实践

8.1 使用技巧

code
REPL 模式使用技巧:
│
├── 1. 使用变量存储结果
│   ├── const result = await get(Service).method()
│   └── 方便后续操作和查看
│
├── 2. 使用 console.log 输出
│   ├── console.log(result)
│   └── 查看完整数据结构
│
├── 3. 分步调试
│   ├── 先测试简单方法
│   └── 逐步增加复杂度
│
├── 4. 保存常用命令
│   ├── 记录常用的查询命令
│   └── 提高调试效率
│
└── 5. 及时清理测试数据
    ├── 避免污染数据库
    └── 使用 deleteMany 清理

8.2 常见问题与解决方案

问题一:Service 未找到

javascript
// 错误:Service 未找到
Error: Cannot find module './course.service'

// 解决方案:确保 Service 正确导出
@Injectable()
export class CourseService {
  // ...
}

// 在 Module 中注册
@Module({
  providers: [CourseService],
  exports: [CourseService],  // 确保导出
})
export class CourseModule {}

问题二:异步操作未使用 await

javascript
// 错误:未使用 await
const result = get(CourseService).createType({ name: 'test' })
// 返回:Promise { <pending> }

// 正确:使用 await
const result = await get(CourseService).createType({ name: 'test' })
// 返回:{ id: 9, name: 'test', ... }

问题三:关联数据未返回

javascript
// 错误:未使用 include
await prisma.courseTypes.findUnique({ where: { id: 9 } })
// 返回:{ id: 9, name: '推荐内容' }  // 没有 tags

// 正确:使用 include
await prisma.courseTypes.findUnique({
  where: { id: 9 },
  include: { tags: true },
})
// 返回:{ id: 9, name: '推荐内容', tags: [...] }

九、REPL 模式 vs 传统调试方式对比

9.1 对比总结

维度REPL 模式Postman 调试单元测试
启动速度快速需要启动服务器需要配置测试环境
调试方式直接调用方法HTTP 请求编写测试用例
数据准备直接插入需要 API需要 fixture
适用场景快速验证、调试API 测试自动化测试
学习成本
可重复性手动执行手动执行自动执行

9.2 选择建议

code
调试方式选择建议:
│
├── REPL 模式
│   ├── 快速验证业务逻辑
│   ├── 调试复杂查询
│   ├── 插入测试数据
│   └── 学习和探索
│
├── Postman 调试
│   ├── API 接口测试
│   ├── 前后端联调
│   ├── 性能测试
│   └── 生产环境验证
│
└── 单元测试
    ├── 自动化测试
    ├── 回归测试
    ├── CI/CD 集成
    └── 团队协作

十、命令速查表

10.1 REPL 常用命令速查

命令说明示例
get(ServiceName)获取 Service 实例get(CourseService)
await get(Service).method()调用异步方法await get(CourseService).createType({ name: 'test' })
help查看帮助help
.exit退出 REPL.exit
Ctrl+D退出 REPLCtrl+D
Ctrl+C取消当前输入Ctrl+C

10.2 常用查询命令速查

操作命令
创建 Typeawait get(CourseService).createType({ name: '推荐内容' })
创建 Tagawait get(CourseService).createTag({ name: 'Vue3', typeId: 9 })
查询所有 Tagsawait get(CourseService).getTags()
查询 Type by IDawait get(CourseService).getTypeById(9)
删除所有 Tagsawait get(CourseService).deleteAllTags()
删除所有 Typesawait get(CourseService).deleteAllTypes()

十一、学习要点总结

11.1 核心概念总结

code
NestJS REPL 模式核心要点:
│
├── REPL 模式特点
│   ├── 交互式编程环境
│   ├── 直接调用 Service 方法
│   └── 快速验证业务逻辑
│
├── 启动方式
│   └── pnpm start:repl
│
├── 基本用法
│   ├── await get(ServiceName) 获取实例
│   └── await get(ServiceName).method() 调用方法
│
├── 适用场景
│   ├── 快速插入测试数据
│   ├── 调试复杂查询
│   ├── 验证关联关系
│   └── 跳过 Controller 测试
│
└── Prisma include
    ├── 包含关联数据
    ├── 支持嵌套查询
    └── 支持过滤排序

11.2 学习路径规划

code
学习路径规划:
│
├── 第一阶段:理解概念(半天)
│   ├── 理解 REPL 模式的作用
│   ├── 掌握启动和基本命令
│   └── 理解适用场景
│
├── 第二阶段:实践操作(1-2 天)
│   ├── 使用 REPL 插入数据
│   ├── 使用 REPL 查询数据
│   └── 调试复杂查询逻辑
│
└── 第三阶段:深入应用(持续)
    ├── 结合实际项目使用
    ├── 优化调试流程
    └── 提高开发效率

11.3 重要提示

重要提示:REPL 模式是 NestJS 提供的强大调试工具,特别适合快速验证业务逻辑、插入测试数据、调试复杂查询。相比传统的 Postman 调试,REPL 模式可以直接调用 Service 方法,无需创建 Controller,大大提高了开发效率!记住使用 await 处理异步操作,使用 include 查询关联数据!


十二、参考资料