从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 项目根目录下执行:
npm install prisma @prisma/client
prisma
:命令行工具,用于初始化、迁移、生成客户端@prisma/client
:运行时客户端,供服务层调用
✅ 第二步:初始化 Prisma 项目
执行以下命令,生成 Prisma 配置文件:
npx prisma init
该命令会在项目中创建两个关键文件:
1. .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
数据模型定义文件
generator client {provider = "prisma-client-js"
}datasource db {provider = "sqlite"url = "file:./dev.db"
}model User {id Int @id @default(autoincrement())email String @uniquename String?password StringcreatedAt DateTime @default(now())updatedAt DateTime @updatedAt
}
✅
@id
:主键
✅@unique
:唯一索引
✅@updatedAt
:自动更新时间戳
✅@default(now())
:默认当前时间
✅ 第三步:执行数据库迁移与生成客户端
1. 创建并应用迁移
npx prisma migrate dev --name init
- 自动创建
dev.db
文件(SQLite 数据库) - 在
prisma/migrations
目录下生成迁移历史文件 - 将
User
表结构写入数据库
2. 生成 Prisma Client
npx prisma generate
- 自动生成
node_modules/.prisma/client/
目录 - 生成 完全类型安全 的
PrismaClient
实例,支持智能提示
✅ 第四步:创建 Prisma 模块与服务
1. 生成 Prisma 模块和服务
nest generate module prisma
nest generate service prisma
2. 编写 PrismaService
(src/prisma/prisma.service.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
,实现OnModuleInit
和OnModuleDestroy
,确保连接在模块初始化时打开,销毁时关闭。
3. 定义 PrismaModule
(src/prisma/prisma.module.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
export class CreateUserDto {email: string;name: string;password: string;
}
✅ DTO 用于定义接口接收的参数结构,提升类型安全与接口文档清晰度。
2. 编写 Users 服务 src/users/users.service.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
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
:
### 创建用户
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
中添加分页方法
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. 在控制器中暴露接口
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
:自动转换字符串为数字,防止类型错误
✅ 测试分页接口
### 分页查询(带搜索)
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. 创建拦截器目录与文件
mkdir src/interceptors
touch src/interceptors/transform.interceptor.ts
2. 编写 TransformInterceptor
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:ssprivate 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
中全局注册拦截器
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] → [全局注册] → [统一时间格式输出]
🚀 推荐资源
- Nest.js 官网:https://docs.nestjs.com
- Prisma 官网:https://www.prisma.io
- VS Code REST Client 插件:https://marketplace.visualstudio.com/items?itemName=humao.rest-client
💬 结语
别再用 Express 写 SQL 了!
用 Nest.js + Prisma,你写的不是“后端接口”,而是类型安全、结构清晰、可维护的工业级服务。
从今天起,告别拼接字符串、类型错误、时间格式混乱,
拥抱现代 TypeScript 全栈开发的优雅与高效!