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

从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/clientprisma命令行工具用于初始化、迁移、生成客户端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_URLfile:./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 unique name String? password String createdAt 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 Clientnpx prisma generate自动生成 node_modules/.prisma/client/ 目录生成完全类型安全的 PrismaClient 实例支持智能提示✅ 第四步创建 Prisma 模块与服务1. 生成 Prisma 模块和服务nest generate module prisma nest generate service prisma2. 编写 PrismaServicesrc/prisma/prisma.service.tsimport { 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. 定义 PrismaModulesrc/prisma/prisma.module.tsimport { Global, Module } from nestjs/common; import { PrismaService } from ./prisma.service; Global() Module({ providers: [PrismaService], exports: [PrismaService], // 导出供其他模块注入使用 }) export class PrismaModule {}✅ 使用 Global() 使该模块在任何地方都可被导入无需重复导入。✅ 第五步在 Users 模块中使用 Prisma1. 创建 DTO数据传输对象src/users/dto/create-user.dto.tsexport class CreateUserDto { email: string; name: string; password: string; }✅ DTO 用于定义接口接收的参数结构提升类型安全与接口文档清晰度。2. 编写 Users 服务 src/users/users.service.tsimport { 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.tsimport { 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: er11example.com, name: hu, password: 123456 } ### 查询所有用户 GET http://localhost:3000/users✅ 选中请求块 → 按 CtrlAltRWindows或 CmdAltRMac直接发送请求✅ 支持高亮、历史记录、环境变量是调试 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?page1pageSize5namehu✅ 第八步解决时间格式问题 —— 创建时间格式化拦截器❌ 问题返回时间是 2025-08-09T05:54:14.508Z这不是我们想要的 2025-08-09 13:54:14 格式。✅ 解决方案使用 Nest.js 拦截器Interceptor1. 创建拦截器目录与文件mkdir src/interceptors touch src/interceptors/transform.interceptor.ts2. 编写 TransformInterceptorimport { 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): Observableany { 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; } // 格式化日期为 UTC8 时区的 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 类型 转换为本地时间UTC8 格式统一为 2025-08-09 13:54:143. 在 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.508Z2025-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 官网docs.nestjs.comPrisma 官网www.prisma.ioVS Code REST Client 插件marketplace.visualstudio.com/items?itemN… 结语别再用 Express 写 SQL 了用 Nest.js Prisma你写的不是“后端接口”而是类型安全、结构清晰、可维护的工业级服务。从今天起告别拼接字符串、类型错误、时间格式混乱拥抱现代 TypeScript 全栈开发的优雅与高效原文链接https://juejin.cn/post/7553087132672802858