量子纠缠般的恋爱现状:
秒回消息嫌粘人,已读不回说渣男;
主动觉得掉价,被动又怕错过
从0死磕全栈系列
从0死磕全栈第1天:从写一个React的hello world开始
从0 死磕全栈第3天:React Router (Vite + React + TS 版):构建小时站实战指南
从0死磕全栈第4天:使用React useState实现用户注册功能
从0死磕全栈第五天:React 使用zustand实现To-Do List项目
从0死磕全栈第6天:React useEffect副作用起了大作用之实现购物车功能
从0死磕全栈第八天:使用nest.js五分钟搭建后端开发环境
从0死磕全栈第九天:Trae AI IDE一把梭,使用react-query快速打通前后端接口调试
前面一节nest.js已经介绍了如何使用nest.js来定义简单的接口,下面继续来讲nest配合数据库实现后端对数据库的访问。
和其他的后端框架一样,要访问数据库都需要通过ORM框架来操作,比如Java里面有mybatis,jpa等多个ORM框架,go有gorm,那么node里面,基于ts的orm框架有prisma,typeorm。
这里以prisma为例来讲解如何快速搭建用户的CRUD功能。
prisma简介
Prisma 是一款为现代应用开发打造的强大 ORM(对象关系映射)工具,尤其适合 Node.js 和 TypeScript 项目。它通过直观的 schema 文件定义数据模型,自动生成类型安全的数据库客户端,彻底告别手写 SQL 的繁琐与类型错误。
与传统 ORM 不同,Prisma 采用声明式语法,让开发者只需关注数据结构而非底层实现。其自动迁移工具可轻松管理数据库变更,支持 PostgreSQL、MySQL、SQLite 等多种数据库。无论是简单的 CRUD 操作还是复杂查询,Prisma 都能提供简洁优雅的 API,大幅提升开发效率。
凭借类型安全、开发体验友好、性能出色等特性,Prisma 已成为全栈开发者的热门选择,尤其在 Next.js、NestJS 等现代框架生态中广泛应用。
第一步,安装依赖
npm install prisma @prisma/client
这里安装了prisma,prisma-client 2个框架, prisma-client就是我们开发者需要使用的工具来实现对数据库的crud。
第二步,生成prisma文件
npx prisma init
这个命令会在nest项目里面生成prisma文件夹存放数据库schema和迁移等文件,还有.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"
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
}
这里为了快速使用prisma,我们选择了轻量级数据库sqlite。3分钟就可以快速搭建好一个数据库。
接着执行迁移命令
npx prisma migrate dev --name init
它会自动创建dev.db数据库文件,并且把定义的表结构加进去。
npx prisma generate
这个命令会生成client。
第三步,创建prisma服务
使用如下2个命令创建prisma模块
nest generate module prisma
nest generate service prisma
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();
}
}
prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
第四步,使用prisma
现在可以在users模块里面使用prisma
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()
}
}
这里保存用户的时候创建了一个接受前端数据的dto
export class CreateUserDto {
email:string;
name:string;
password:string;
}
写完了之后,我们需要快速测试接口,这里我推荐安装一个vscode插件restclient
目前唯一的缺点是还没有插件可以实现自动识别nest接口生成对应的http文件,需要手动去写.http文件
下面是2个简单的例子
POST http://localhost:3000/users
Content-Type: application/json
{
"email":"er11",
"name":"hu",
"password":"rt"
}
###
GET http://localhost:3000/users
到此我们就学会了使用nest+prisma写CRUD了。
下面来实现一个分页查询的接口
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),
};
}
controller
// 分页查询
@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.userService.findByPageWithName(page, pageSize, name);
}
这里用到了@Query装饰器,如果了解Spring,可以类比为Spring里面的注解。
装饰器本质是一种特殊函数,它能给类、方法、参数添加“元数据”(额外信息),告诉NestJS如何处理这些代码。简单说,装饰器就像“标签”,让框架知道:“这个类是控制器”“这个方法处理GET请求”“这个参数来自查询字符串”。
装饰器本质是一种特殊函数,它能给类、方法、参数添加“元数据”(额外信息),告诉NestJS如何处理这些代码。简单说,装饰器就像“标签”,让框架知道:“这个类是控制器”“这个方法处理GET请求”“这个参数来自查询字符串”。
我们会发现返回的时间类似这样的2025-08-09T05:54:14.508Z,不是我们希望的 yyyy-MM-dd HH:mm:ss。这个时候就需要引入拦截器的概念。
拦截器简介
Nest拦截器是框架里的一种功能,能在请求处理前后执行额外逻辑(如日志、数据转换、异常处理),就像‘过滤器’,不改变核心业务,却能扩展功能,让代码更简洁易维护。
第一步,创建拦截器
在src下面创建一个目录 interceptors,定义拦截器的业务
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 {
// 设置时区为UTC+8
const utc8Date = new Date(date.getTime() + 8 * 60 * 60 * 1000);
return utc8Date.toISOString()
.replace(/T/, ' ') // 替换T为空格
.replace(/\..+/, '') // 删除小数点及之后的部分
.slice(0, 19); // 截取到秒
}
}
在app.module.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 {}
