从0死磕全栈第十天:nest.js集成prisma完成CRUD

前言:为什么选择 Nest.js + Prisma?

在全栈开发中,后端选型直接影响开发效率与项目可维护性。

如果你是 TypeScript 开发者,又想摆脱"手写 SQL + 类型不安全"的噩梦,那么 Nest.js + Prisma 是你不可错过的技术组合:

  • Nest.js:基于 TypeScript 的企业级 Node.js 框架,借鉴 Spring 的模块化、装饰器、依赖注入,结构清晰,易于团队协作。
  • Prisma:现代 ORM,通过声明式 schema 自动生成类型安全的数据库客户端,告别拼接 SQL、类型错乱、手动映射。

本文将手把手带你从零搭建一个完整的 Nest.js 后端服务,包含:

  • ✅ Prisma 初始化与数据库迁移
  • ✅ 用户 CRUD 操作
  • ✅ 分页查询 + 条件搜索
  • ✅ 时间格式化拦截器(解决 2025-08-09T05:54:14.508Z 问题)

✅ 第一步:安装 Prisma 依赖

在你的 Nest.js 项目根目录下执行:

bash 复制代码
npm install prisma @prisma/client
  • prisma:命令行工具,用于初始化、迁移、生成客户端
  • @prisma/client:运行时客户端,供服务层调用

✅ 第二步:初始化 Prisma 项目

执行以下命令,生成 Prisma 配置文件:

bash 复制代码
npx prisma init

该命令会在项目中创建两个关键文件:

1. .env 环境配置文件

env 复制代码
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"

💡 为快速上手,我们使用轻量级的 SQLite 数据库。生产环境推荐 PostgreSQL 或 MySQL。

2. prisma/schema.prisma 数据模型定义文件

prisma 复制代码
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

@id:主键

@unique:唯一索引

@updatedAt:自动更新时间戳

@default(now()):默认当前时间


✅ 第三步:执行数据库迁移与生成客户端

1. 创建并应用迁移

bash 复制代码
npx prisma migrate dev --name init
  • 自动创建 dev.db 文件(SQLite 数据库)
  • prisma/migrations 目录下生成迁移历史文件
  • User 表结构写入数据库

2. 生成 Prisma Client

bash 复制代码
npx prisma generate
  • 自动生成 node_modules/.prisma/client/ 目录
  • 生成 完全类型安全PrismaClient 实例,支持智能提示

✅ 第四步:创建 Prisma 模块与服务

1. 生成 Prisma 模块和服务

bash 复制代码
nest generate module prisma
nest generate service prisma

2. 编写 PrismaServicesrc/prisma/prisma.service.ts

ts 复制代码
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super({
      log: ['query', 'info', 'warn', 'error'], // 开启查询日志,便于调试
    });
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

✅ 继承 PrismaClient,实现 OnModuleInitOnModuleDestroy,确保连接在模块初始化时打开,销毁时关闭。

3. 定义 PrismaModulesrc/prisma/prisma.module.ts

ts 复制代码
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService], // 导出供其他模块注入使用
})
export class PrismaModule {}

✅ 使用 @Global() 使该模块在任何地方都可被导入,无需重复导入。


✅ 第五步:在 Users 模块中使用 Prisma

1. 创建 DTO(数据传输对象)src/users/dto/create-user.dto.ts

ts 复制代码
export class CreateUserDto {
  email: string;
  name: string;
  password: string;
}

✅ DTO 用于定义接口接收的参数结构,提升类型安全与接口文档清晰度。

2. 编写 Users 服务 src/users/users.service.ts

ts 复制代码
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';

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

  async create(user: CreateUserDto) {
    console.log('create user');
    return this.prisma.user.create({ data: user });
  }

  async findAll() {
    return this.prisma.user.findMany();
  }
}

this.prisma.user.create():自动映射到数据库表,参数类型安全,IDE 有智能提示!

3. 编写控制器 src/users/users.controller.ts

ts 复制代码
import { Controller, Get, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

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

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

@Post() / @Get():Nest.js 的路由装饰器,语义清晰,类似 Spring 的 @RequestMapping


✅ 第六步:测试接口(推荐使用 VS Code 插件:REST Client)

安装 VS Code 插件:REST Client(作者:Huachao Mao)

创建文件 test.http

http 复制代码
### 创建用户
POST http://localhost:3000/users
Content-Type: application/json

{
  "email": "er11@example.com",
  "name": "hu",
  "password": "123456"
}

### 查询所有用户
GET http://localhost:3000/users

✅ 选中请求块 → 按 Ctrl+Alt+R(Windows)或 Cmd+Alt+R(Mac)直接发送请求

✅ 支持高亮、历史记录、环境变量,是调试 API 的神器!
⚠️ 目前尚无插件能自动识别 Nest.js 路由生成 .http 文件,需手动编写。


✅ 第七步:实现分页查询 + 条件搜索

1. 在 UsersService 中添加分页方法

ts 复制代码
async findByPageWithName(
  page: number = 1,
  pageSize: number = 10,
  name?: string
) {
  const skip = (page - 1) * pageSize;
  const take = pageSize;

  const where = name
    ? {
        OR: [
          { name: { contains: name } },
        ],
      }
    : {};

  const [users, total] = await Promise.all([
    this.prisma.user.findMany({
      skip,
      take,
      where,
    }),
    this.prisma.user.count({ where }),
  ]);

  return {
    users,
    total,
    page,
    pageSize,
    totalPages: Math.ceil(total / pageSize),
  };
}

Promise.all 并发查询数据和总数,提升性能

contains:模糊搜索(LIKE '%xxx%')

✅ 返回结构标准化,前端可直接渲染分页器

2. 在控制器中暴露接口

ts 复制代码
import { Query, ParseIntPipe, DefaultValuePipe } from '@nestjs/common';

// ... 省略其他代码

@Get('page')
async getByPage(
  @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
  @Query('pageSize', new DefaultValuePipe(10), ParseIntPipe) pageSize: number,
  @Query('name') name?: string,
) {
  return this.usersService.findByPageWithName(page, pageSize, name);
}

@Query():获取 URL 查询参数

DefaultValuePipe:设置默认值

ParseIntPipe:自动转换字符串为数字,防止类型错误

✅ 测试分页接口

http 复制代码
### 分页查询(带搜索)
GET http://localhost:3000/users/page?page=1&pageSize=5&name=hu

✅ 第八步:解决时间格式问题 ------ 创建时间格式化拦截器

❌ 问题:返回时间是 2025-08-09T05:54:14.508Z

这不是我们想要的 2025-08-09 13:54:14 格式。

✅ 解决方案:使用 Nest.js 拦截器(Interceptor)

1. 创建拦截器目录与文件
bash 复制代码
mkdir src/interceptors
touch src/interceptors/transform.interceptor.ts
2. 编写 TransformInterceptor
ts 复制代码
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => this.transformDates(data)),
    );
  }

  private transformDates(data: any): any {
    if (!data) return data;

    // 如果是数组,递归处理每个元素
    if (Array.isArray(data)) {
      return data.map((item) => this.transformDates(item));
    }

    // 如果是对象,处理每个属性
    if (typeof data === 'object') {
      for (const key in data) {
        if (data.hasOwnProperty(key)) {
          // 处理日期类型
          if (data[key] instanceof Date) {
            data[key] = this.formatDate(data[key]);
          }
          // 递归处理嵌套对象
          else if (typeof data[key] === 'object' && data[key] !== null) {
            data[key] = this.transformDates(data[key]);
          }
        }
      }
    }

    return data;
  }

  // 格式化日期为 UTC+8 时区的 yyyy-MM-dd HH:mm:ss
  private formatDate(date: Date): string {
    const utc8Date = new Date(date.getTime() + 8 * 60 * 60 * 1000);
    return utc8Date
      .toISOString()
      .replace(/T/, ' ')     // 替换 T 为空格
      .replace(/\..+/, '')   // 删除毫秒部分
      .slice(0, 19);         // 截取到秒
  }
}

✅ 支持:

  • 深度递归处理嵌套对象
  • 自动识别 Date 类型
  • 转换为本地时间(UTC+8)
  • 格式统一为 2025-08-09 13:54:14
3. 在 AppModule 中全局注册拦截器
ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformInterceptor } from './interceptors/transform.interceptor';

@Module({
  imports: [PrismaModule, UsersModule],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_INTERCEPTOR,
      useClass: TransformInterceptor,
    },
  ],
})
export class AppModule {}

APP_INTERCEPTOR 是 Nest.js 提供的全局拦截器 token,注册后所有接口响应都会经过此拦截器!


✅ 最终效果对比

时间格式 未使用拦截器 使用拦截器后
返回值 2025-08-09T05:54:14.508Z 2025-08-09 13:54:14

✅ 前端再也不用手动 new Date().toLocaleString(),后端统一输出,体验一致!


🧠 拦截器本质:装饰器的"元数据"魔法

Nest.js 的拦截器、装饰器(如 @Get()@Body())本质是 TypeScript 装饰器

📌 装饰器是一种特殊函数,它能给类、方法、参数添加"元数据",告诉框架:"这个类是控制器""这个方法处理 GET 请求""这个参数来自查询字符串"。

它不改变核心业务逻辑,却能扩展功能、统一处理、提升可维护性


✅ 总结:Nest.js + Prisma 开发流程图

复制代码
[定义模型] → [npx prisma init] → [npx prisma migrate dev] → [npx prisma generate]
      ↓
[创建 PrismaService] → [注册 PrismaModule] → [在 Service 中使用 this.prisma.user.xxx()]
      ↓
[定义 DTO] → [编写 Controller] → [测试接口(REST Client)] → [添加分页/搜索]
      ↓
[创建 TransformInterceptor] → [全局注册] → [统一时间格式输出]

🚀 推荐资源


💬 结语

别再用 Express 写 SQL 了!

用 Nest.js + Prisma,你写的不是"后端接口",而是类型安全、结构清晰、可维护的工业级服务

从今天起,告别拼接字符串、类型错误、时间格式混乱,

拥抱现代 TypeScript 全栈开发的优雅与高效!

相关推荐
thginWalker3 小时前
Java 热门面试题200道之JVM(7 题)
java·jvm
know__ledge3 小时前
Pytest+requests进行接口自动化测试5.0(5种assert断言的封装 + pymysql)
服务器·开发语言·python·测试用例·pytest
fengdongnan3 小时前
JVM 类加载器详解
java·jvm·类加载器
安然~~~3 小时前
常见的【垃圾收集算法】
java·jvm
低调小一3 小时前
理解 JVM 的 8 个原子操作与 `volatile` 的语义
java·jvm
七夜zippoe3 小时前
JVM 调优在分布式场景下的特殊策略:从集群 GC 分析到 OOM 排查实战(二)
jvm·分布式
Familyism3 小时前
Java虚拟机——JVM
java·开发语言·jvm
Biomamba生信基地3 小时前
挑战用R语言硬干一百万单细胞数据分析
开发语言·数据分析·r语言·生信·医药
GISer_Jing3 小时前
携程HR面(准备)
前端·javascript·面试