You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让NestJS Prisma与Next.js Drizzle安全共享PostgreSQL数据库

问题背景

现有一个基于NestJS + Prisma ORM的服务已稳定运行,通过定时任务和外部API操作PostgreSQL数据库中的Plant、Device、DeviceInfo三张表。现在需要让一个使用Drizzle ORM的Next.js项目直接访问同一数据库,但执行Drizzle的迁移命令后,数据库表结构被意外修改,需找到安全共享数据库的方案,避免生产环境出现问题。

现有环境配置

NestJS(Prisma侧)命令

{
  "prisma:init": "npx prisma migrate dev --name init", // 仅初始执行一次
  "prisma:generate": "npx prisma generate",
  "prisma:migrate": "npx prisma migrate deploy",
  "prisma": "npm run prisma:generate && npm run prisma:migrate" // 启动服务前必执行
}

Prisma模型定义

model Plant {
  id          Int      @id @default(autoincrement())
  vendor      String
  uuid        String
  data        Json
  vendorUuid  String   @unique
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([vendorUuid])
}

model Device {
  id          Int      @id @default(autoincrement())
  vendor      String
  uuid        String
  data        Json
  vendorUuid  String   @unique
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  @@index([vendorUuid])
}

model DeviceInfo {
  id          Int      @id @default(autoincrement())
  vendor      String
  uuid        String
  data        Json
  vendorUuid  String   @unique
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([vendorUuid])
}

Next.js(Drizzle侧)命令

npx drizzle-kit generate
npx drizzle-kit push

Drizzle Schema定义

// 对齐Prisma的时间字段行为
const timestampColumns = {
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').$onUpdate(() => new Date()).notNull()
};

// Plants table
export const plantTable = pgTable(
  'plants',
  {
    id: serial('id').primaryKey(),
    vendor: varchar('vendor', { length: 255 }).notNull(),
    uuid: varchar('uuid', { length: 255 }).notNull(),
    data: jsonb('data').notNull(),
    vendorUuid: varchar('vendor_uuid', { length: 255 }).notNull().unique(),
    ...timestampColumns,
  },
  (t) => [index('plant_vendor_uuid_idx').on(t.vendorUuid)]
);
export type Plant = typeof plantTable.$inferSelect;
export type PlantCreateParams = typeof plantTable.$inferInsert;

// Devices table
export const deviceTable = pgTable(
  'devices',
  {
    id: serial('id').primaryKey(),
    vendor: varchar('vendor', { length: 255 }).notNull(),
    uuid: varchar('uuid', { length: 255 }).notNull(),
    data: jsonb('data').notNull(),
    vendorUuid: varchar('vendor_uuid', { length: 255 }).notNull().unique(),
    ...timestampColumns,
  },
  (t) => [index('device_vendor_uuid_idx').on(t.vendorUuid)]
);
export type Device = typeof deviceTable.$inferSelect;
export type DeviceCreateParams = typeof deviceTable.$inferInsert;

// Device Info table
export const deviceInfoTable = pgTable(
  'device_info',
  {
    id: serial('id').primaryKey(),
    vendor: varchar('vendor', { length: 255 }).notNull(),
    uuid: varchar('uuid', { length: 255 }).notNull(),
    data: jsonb('data').notNull(),
    vendorUuid: varchar('vendor_uuid', { length: 255 }).notNull().unique(),
    ...timestampColumns,
  },
  (t) => [index('device_info_vendor_uuid_idx').on(t.vendorUuid)]
);
export type DeviceInfo = typeof deviceInfoTable.$inferSelect;
export type DeviceInfoCreateParams = typeof deviceInfoTable.$inferInsert;

Drizzle生成的迁移脚本

CREATE TABLE "device_info" (
    "id" serial PRIMARY KEY NOT NULL,
    "vendor" varchar(255) NOT NULL,
    "uuid" varchar(255) NOT NULL,
    "data" jsonb NOT NULL,
    "vendor_uuid" varchar(255) NOT NULL,
    "created_at" timestamp DEFAULT now() NOT NULL,
    "updated_at" timestamp,
    CONSTRAINT "device_info_vendor_uuid_unique" UNIQUE("vendor_uuid")
);
--> statement-breakpoint
CREATE TABLE "devices" (
    "id" serial PRIMARY KEY NOT NULL,
    "vendor" varchar(255) NOT NULL,
    "uuid" varchar(255) NOT NULL,
    "data" jsonb NOT NULL,
    "vendor_uuid" varchar(255) NOT NULL,
    "created_at" timestamp DEFAULT now() NOT NULL,
    "updated_at" timestamp,
    CONSTRAINT "devices_vendor_uuid_unique" UNIQUE("vendor_uuid")
);
--> statement-breakpoint
CREATE TABLE "plants" (
    "id" serial PRIMARY KEY NOT NULL,
    "vendor" varchar(255) NOT NULL,
    "uuid" varchar(255) NOT NULL,
    "data" jsonb NOT NULL,
    "vendor_uuid" varchar(255) NOT NULL,
    "created_at" timestamp DEFAULT now() NOT NULL,
    "updated_at" timestamp,
    CONSTRAINT "plants_vendor_uuid_unique" UNIQUE("vendor_uuid")
);
--> statement-breakpoint
CREATE INDEX "device_info_vendor_uuid_idx" ON "device_info" USING btree ("vendor_uuid");--> statement-breakpoint
CREATE INDEX "device_vendor_uuid_idx" ON "devices" USING btree ("vendor_uuid");--> statement-breakpoint
CREATE INDEX "plant_vendor_uuid_idx" ON "plants" USING btree ("vendor_uuid");
安全共享数据库的解决方案

1. 统一迁移源,禁用Drizzle的结构变更能力

因NestJS服务已稳定运行,将Prisma作为唯一的数据库迁移工具,所有表结构变更必须通过Prisma的migrate流程执行:

  • 删除Next.js项目中drizzle-kit push和drizzle-kit generate(生成迁移脚本)的命令,仅保留drizzle-kit generate:types(仅生成TypeScript类型定义)的命令,避免误操作修改数据库结构。
  • 表结构变更流程:修改NestJS项目的Prisma模型 → 生成Prisma迁移脚本 → 测试环境验证 → 生产环境执行prisma:migrate。

2. 严格对齐两个ORM的Schema定义

确保Prisma和Drizzle的Schema完全一致:

  • 表名/字段名:保持Prisma默认的驼峰转蛇形映射(如vendorUuid→vendor_uuid)与Drizzle配置一致。
  • 字段类型:Prisma的Json对应Drizzle的jsonb;Prisma的@updatedAt需对应Drizzle的$onUpdate(() => new Date()),确保时间字段自动更新行为一致。
  • 约束与索引:唯一约束、索引的名称和关联字段必须完全匹配,避免重复创建或遗漏。

3. 数据库权限隔离

为Next.js项目创建专用数据库用户,仅授予必要权限:

  • 若仅需读取数据,授予SELECT权限;若需写入,追加INSERT/UPDATE/DELETE权限,禁止授予CREATE/ALTER/DROP等结构修改权限。
  • 示例PostgreSQL权限配置:
    CREATE USER nextjs_user WITH PASSWORD 'your_secure_password';
    -- 授予读权限
    GRANT SELECT ON plants, devices, device_info TO nextjs_user;
    -- 若需写入,追加以下权限
    GRANT INSERT, UPDATE, DELETE ON plants, devices, device_info TO nextjs_user;
    -- 授予自增序列权限
    GRANT USAGE, SELECT ON SEQUENCE plants_id_seq, devices_id_seq, device_info_id_seq TO nextjs_user;
    

4. 规范迁移与发布流程

  • 所有表结构变更必须先在测试环境验证:同时启动NestJS和Next.js服务,测试数据读写、定时任务、API调用是否正常,确保变更不影响现有功能。
  • 生产环境迁移需在低峰期执行,执行前备份数据库,执行后验证两个项目功能正常。
  • 发布Next.js项目前,确认其Drizzle Schema与当前数据库结构完全一致,避免查询错误。

5. 并发与事务控制

  • 两个项目都会修改的数据操作必须用事务包裹,避免并发冲突。比如NestJS用Prisma事务,Next.js用Drizzle事务。
  • 避免长时间运行的事务,减少锁表时间,降低冲突概率。

6. 监控与告警

  • 监控数据库结构变更日志,发现非Prisma迁移的结构变更立即告警。
  • 监控两个项目的数据库操作错误日志,及时排查Schema不一致或权限问题。

内容的提问来源于stack exchange,提问作者Maifee Ul Asad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 00:17:33