Prisma ORM چیست؟ آموزش کامل Prisma با TypeScript، Node.js و PostgreSQL

در این آموزش Prisma ORM را با TypeScript، Node.js و PostgreSQL راه‌اندازی می‌کنیم و Schema، Migration، CRUD، Relation، Transaction، Pagination و ساخت دیتابیس یک برنامه هوش مصنوعی را به‌صورت عملی یاد می‌گیریم.

Share
Prisma ORM چیست؟ آموزش کامل Prisma با TypeScript، Node.js و PostgreSQL

ساخت یک Backend واقعی فقط به دریافت Request و ارسال Response محدود نمی‌شود. تقریباً تمام برنامه‌های کاربردی باید اطلاعات کاربران، تنظیمات، سفارش‌ها، مکالمات، پیام‌ها یا گزارش‌های مصرف را در دیتابیس ذخیره کنند.

در پروژه‌های Node.js و TypeScript می‌توان Queryهای SQL را مستقیماً نوشت، اما با بزرگ‌تر شدن برنامه باید مشکلاتی مانند هماهنگ نگه داشتن Typeها با ساختار دیتابیس، مدیریت Migrationها، ارتباط میان جدول‌ها، تراکنش‌ها و Queryهای تکراری را نیز مدیریت کرد.

Prisma ORM ابزاری است که دسترسی به دیتابیس را برای توسعه‌دهندگان TypeScript ساختاریافته‌تر می‌کند. در Prisma مدل‌های دیتابیس در یک Schema تعریف می‌شوند و سپس یک Client اختصاصی و Type-Safe برای همان مدل‌ها تولید می‌شود.

در این مقاله Prisma ORM را با PostgreSQL و TypeScript راه‌اندازی می‌کنیم، عملیات CRUD و Relationها را می‌سازیم و در پایان یک ساختار واقعی برای ذخیره مکالمات یک برنامه هوش مصنوعی پیاده‌سازی می‌کنیم.

Prisma ORM چیست؟

Prisma ORM یک ابزار دسترسی به دیتابیس برای JavaScript و TypeScript است. توسعه‌دهنده مدل‌های داده را در Prisma Schema تعریف می‌کند و Prisma براساس همان مدل‌ها یک Client اختصاصی تولید می‌کند.

این Client امکانات زیر را فراهم می‌کند:

  • Queryهای Type-Safe
  • تکمیل خودکار کد در ویرایشگر
  • عملیات CRUD
  • مدیریت ارتباط میان جدول‌ها
  • Transaction
  • Migration
  • Introspection دیتابیس موجود
  • ابزار گرافیکی Prisma Studio
  • پشتیبانی از دیتابیس‌هایی مانند PostgreSQL، MySQL، SQLite، SQL Server، CockroachDB و MongoDB

براساس مستندات رسمی Prisma ORM، Prisma Client از روی Schema پروژه تولید می‌شود و API آن با مدل‌ها، فیلدها و Relationهای همان پروژه هماهنگ است.

یک Query ساده در Prisma:

const users = await prisma.user.findMany({
  where: {
    active: true,
  },
  orderBy: {
    createdAt: "desc",
  },
});

در این Query، نام مدل user، فیلد active و مقدار createdAt توسط TypeScript بررسی می‌شوند. اگر فیلدی وجود نداشته باشد یا Type اشتباهی ارسال شود، ویرایشگر و TypeScript خطا را نشان می‌دهند.

ORM چیست؟

ORM مخفف Object-Relational Mapping است. ORM یک لایه میان کد برنامه و دیتابیس رابطه‌ای ایجاد می‌کند تا توسعه‌دهنده بتواند به‌جای نوشتن تمام Queryها به‌صورت SQL خام، با Objectها و API زبان برنامه‌نویسی کار کند.

یک Query SQL:

SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC;

نمونه مفهومی همان Query در Prisma:

const users = await prisma.user.findMany({
  where: {
    active: true,
  },
  select: {
    id: true,
    name: true,
    email: true,
  },
  orderBy: {
    createdAt: "desc",
  },
});

استفاده از ORM به معنی بی‌نیازی از SQL نیست. برای طراحی Schema، Index، Relation، Transaction و بهینه‌سازی Queryها همچنان باید مفاهیم دیتابیس را بدانید.

اجزای اصلی Prisma

Prisma ORM را می‌توان به چند بخش اصلی تقسیم کرد.

Prisma Schema

فایلی است که مدل‌های داده، Relationها، Enumها، Generator و نوع دیتابیس را تعریف می‌کند.

مسیر رایج:

prisma/schema.prisma

Prisma Client

Client تولیدشده‌ای است که برنامه با استفاده از آن Queryهای دیتابیس را اجرا می‌کند.

const user = await prisma.user.findUnique({
  where: {
    id: userId,
  },
});

Prisma Migrate

ابزار مدیریت تغییرات ساختار دیتابیس است. وقتی مدل جدیدی اضافه یا یک فیلد تغییر می‌کند، Prisma می‌تواند Migration متناظر را تولید و اجرا کند.

Prisma Studio

یک رابط گرافیکی برای مشاهده و ویرایش رکوردهای دیتابیس در محیط توسعه است.

npx prisma studio

Prisma CLI

فرمان‌هایی مانند موارد زیر را ارائه می‌کند:

npx prisma init
npx prisma generate
npx prisma migrate dev
npx prisma migrate deploy
npx prisma db pull
npx prisma db push
npx prisma studio

Prisma چه تفاوتی با SQL خام دارد؟

معیارPrisma ORMSQL خام
Type Safetyقوی در TypeScriptنیازمند ابزار یا Type دستی
Autocompleteداردمعمولاً محدود
سرعت توسعه CRUDبالانیازمند Query بیشتر
کنترل کامل Queryمحدودتربسیار بالا
Migrationابزار داخلینیازمند ابزار جداگانه یا Script
Queryهای پیچیده تحلیلیگاهی دشوارترانعطاف‌پذیرتر
وابستگی به ORMداردندارد
نیاز به دانش SQLهمچنان لازم استضروری است

در بسیاری از پروژه‌ها بهترین راهکار استفاده ترکیبی است: بیشتر عملیات عادی با Prisma انجام می‌شوند و برای Queryهای تحلیلی یا خاص از SQL کنترل‌شده استفاده می‌شود.

Prisma برای چه پروژه‌هایی مناسب است؟

Prisma انتخاب مناسبی برای بسیاری از پروژه‌های زیر است:

  • Backendهای TypeScript و Node.js
  • REST API و GraphQL API
  • پروژه‌های Express و NestJS
  • برنامه‌های Next.js
  • پنل‌های مدیریتی
  • نرم‌افزارهای SaaS
  • فروشگاه‌های اینترنتی
  • سیستم‌های مدیریت محتوا
  • چت‌بات‌های هوش مصنوعی
  • AI Agentها
  • سیستم‌های RAG
  • ذخیره تاریخچه مکالمات
  • ثبت مصرف API و گزارش‌های مالی

در پروژه‌هایی که Queryهای تحلیلی بسیار پیچیده، پردازش حجیم یا نیازهای کاملاً اختصاصی دیتابیس دارند، ممکن است ترکیب Prisma با SQL خام یا استفاده از ابزار دیگری مناسب‌تر باشد.

نسخه Prisma و تغییرات مهم Prisma 7

در زمان نگارش این مقاله، Prisma 7 نسخه پایدار عمومی Prisma ORM است و Prisma Next نیز به‌عنوان مسیر نسخه اصلی بعدی در حال توسعه است. برای پروژه Production بهتر است از نسخه پایدار استفاده کنید، مگر اینکه دلیل مشخصی برای آزمایش نسخه Early Access داشته باشید.

در Prisma 7 چند تغییر مهم وجود دارد:

  • Generator جدید prisma-client استفاده می‌شود.
  • مسیر خروجی Prisma Client باید مشخص شود.
  • اتصال دیتابیس با Driver Adapter انجام می‌شود.
  • URL دیتابیس معمولاً در prisma.config.ts تنظیم می‌شود.
  • اجرای prisma migrate dev دیگر لزوماً Prisma Client را تولید نمی‌کند؛ بنابراین prisma generate را جداگانه اجرا کنید.

جزئیات تغییرات در راهنمای رسمی ارتقا به Prisma 7 قرار دارد.

نمونه‌های این مقاله بر پایه Prisma ORM 7 نوشته شده‌اند.

پیش‌نیازهای آموزش

برای اجرای پروژه به موارد زیر نیاز دارید:

  • Node.js نسخه LTS یا جدیدتر
  • npm یا pnpm
  • TypeScript
  • یک دیتابیس PostgreSQL
  • آشنایی مقدماتی با Node.js
  • یک ویرایشگر مانند VS Code

نسخه‌ها را بررسی کنید:

node --version
npm --version

برای PostgreSQL می‌توانید از نصب محلی، Docker یا یک سرویس مدیریت‌شده استفاده کنید.

ساخت پروژه TypeScript

پوشه پروژه را بسازید:

mkdir prisma-ai-api
cd prisma-ai-api

پروژه npm را راه‌اندازی کنید:

npm init -y

TypeScript و ابزار اجرای آن را نصب کنید:

npm install --save-dev typescript tsx @types/node

فایل تنظیمات TypeScript را بسازید:

npx tsc --init

نمونه tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": [
    "src",
    "generated",
    "prisma.config.ts"
  ]
}

در package.json حالت ES Module را فعال کنید:

{
  "type": "module"
}

نصب Prisma برای PostgreSQL

بسته‌های مورد نیاز را نصب کنید:

npm install @prisma/client @prisma/adapter-pg pg dotenv
npm install --save-dev prisma @types/pg

Prisma را راه‌اندازی کنید:

npx prisma init --datasource-provider postgresql

این فرمان معمولاً فایل‌ها و پوشه‌های اصلی Prisma را می‌سازد:

prisma/
  schema.prisma
prisma.config.ts
.env

راهنمای رسمی راه‌اندازی PostgreSQL در Quickstart رسمی Prisma و PostgreSQL موجود است.

ساخت دیتابیس PostgreSQL با Docker

اگر PostgreSQL محلی ندارید، می‌توانید برای محیط توسعه از Docker استفاده کنید.

فایل compose.yaml:

services:
  postgres:
    image: postgres:17
    container_name: prisma_ai_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: change_this_password
      POSTGRES_DB: ai_app
    ports:
      - "5432:5432"
    volumes:
      - prisma_postgres_data:/var/lib/postgresql/data

volumes:
  prisma_postgres_data:

دیتابیس را اجرا کنید:

docker compose up -d

وضعیت Container:

docker compose ps

برای محیط Production نباید از رمز نمونه مقاله استفاده کنید. Secretها باید خارج از مخزن و از طریق محیط استقرار مدیریت شوند.

تنظیم DATABASE_URL

در فایل .env:

DATABASE_URL="postgresql://app_user:change_this_password@localhost:5432/ai_app?schema=public"

ساختار کلی Connection String:

postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA

فایل .env را به Git اضافه نکنید:

.env
.env.*
!.env.example

فایل .env.example:

DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DATABASE?schema=public"
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

اگر نام کاربری یا رمز عبور شامل کاراکترهای خاص است، آن‌ها را در Connection String به‌درستی URL Encode کنید.

تنظیم prisma.config.ts

در Prisma 7 می‌توان تنظیمات مسیر Schema، Migration و URL دیتابیس را در prisma.config.ts قرار داد:

import "dotenv/config";
import {
  defineConfig,
  env,
} from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
});

این فایل توسط Prisma CLI استفاده می‌شود.

ساخت Prisma Schema

فایل prisma/schema.prisma را باز کنید:

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

اکنون مدل‌های پروژه را اضافه می‌کنیم.

ساخت مدل User

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  active    Boolean  @default(true)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([createdAt])
}

مفهوم هر بخش:

  • @id: کلید اصلی
  • @default(uuid()): تولید UUID به‌صورت پیش‌فرض
  • @unique: جلوگیری از ثبت مقدار تکراری
  • String?: فیلد اختیاری یا Nullable
  • @default(true): مقدار پیش‌فرض
  • @default(now()): زمان ایجاد
  • @updatedAt: به‌روزرسانی خودکار زمان ویرایش
  • @@index: تعریف Index

ساخت اولین Migration

پس از تعریف مدل:

npx prisma migrate dev --name init

این فرمان در محیط توسعه:

  1. اختلاف Schema و دیتابیس را بررسی می‌کند.
  2. فایل Migration می‌سازد.
  3. Migration را روی دیتابیس Development اجرا می‌کند.

در Prisma 7، Prisma Client را جداگانه تولید کنید:

npx prisma generate

ساختار پروژه اکنون تقریباً چنین است:

prisma-ai-api/
  generated/
    prisma/
  prisma/
    migrations/
    schema.prisma
  src/
  .env
  prisma.config.ts
  package.json
  tsconfig.json

ساخت Prisma Client

فایل src/lib/prisma.ts:

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../../generated/prisma/client";

const connectionString =
  process.env.DATABASE_URL;

if (!connectionString) {
  throw new Error(
    "DATABASE_URL is not configured",
  );
}

const adapter = new PrismaPg({
  connectionString,
});

export const prisma = new PrismaClient({
  adapter,
});

در Prisma 7، Driver Adapter اتصال Prisma Client به Driver دیتابیس را برقرار می‌کند. برای PostgreSQL از @prisma/adapter-pg استفاده کرده‌ایم.

آزمایش اتصال به دیتابیس

فایل src/index.ts:

import { prisma } from "./lib/prisma.js";

async function main() {
  const userCount =
    await prisma.user.count();

  console.log({
    connected: true,
    userCount,
  });
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

اجرا:

npx tsx src/index.ts

خروجی نمونه:

{ connected: true, userCount: 0 }

در Scriptهای کوتاه می‌توان در finally اتصال را قطع کرد. در یک Server طولانی‌مدت نباید بعد از هر Request از $disconnect() استفاده کنید؛ زیرا Server باید Connectionها را برای درخواست‌های بعدی نگه دارد.

عملیات CRUD با Prisma

CRUD شامل Create، Read، Update و Delete است.

ایجاد رکورد با create

const user = await prisma.user.create({
  data: {
    email: "sara@example.com",
    name: "Sara Ahmadi",
  },
});

console.log(user);

خروجی شامل فیلدهای تولیدشده مانند id و createdAt خواهد بود.

دریافت تمام کاربران با findMany

const users = await prisma.user.findMany({
  orderBy: {
    createdAt: "desc",
  },
});

console.log(users);

دریافت کاربر با findUnique

findUnique برای فیلدهای Unique مانند id و email استفاده می‌شود:

const user = await prisma.user.findUnique({
  where: {
    email: "sara@example.com",
  },
});

اگر رکورد وجود نداشته باشد، خروجی null است.

if (!user) {
  console.log("User not found");
}

دریافت اولین رکورد مطابق شرط

const user = await prisma.user.findFirst({
  where: {
    active: true,
    name: {
      contains: "Sara",
      mode: "insensitive",
    },
  },
});

به‌روزرسانی با update

const updatedUser =
  await prisma.user.update({
    where: {
      email: "sara@example.com",
    },
    data: {
      name: "Sara A.",
      active: true,
    },
  });

اگر رکورد مورد نظر وجود نداشته باشد، update خطا ایجاد می‌کند.

ایجاد یا به‌روزرسانی با upsert

const user = await prisma.user.upsert({
  where: {
    email: "sara@example.com",
  },
  update: {
    active: true,
  },
  create: {
    email: "sara@example.com",
    name: "Sara Ahmadi",
  },
});

upsert برای مواردی مناسب است که یک رکورد باید در هر صورت پس از عملیات وجود داشته باشد.

حذف رکورد با delete

await prisma.user.delete({
  where: {
    email: "sara@example.com",
  },
});

حذف داده باید آگاهانه انجام شود. در سیستم‌های واقعی گاهی Soft Delete، Archive یا نگهداری تاریخچه به حذف فیزیکی ترجیح داده می‌شود.

حذف چند رکورد با deleteMany

const result =
  await prisma.user.deleteMany({
    where: {
      active: false,
    },
  });

console.log(result.count);

پیش از اجرای deleteMany در محیط واقعی، شرط where را با دقت بررسی و ابتدا Query مشابه را با findMany آزمایش کنید.

جزئیات تمام عملیات در مستندات رسمی CRUD در Prisma موجود است.

انتخاب فیلدها با select

اگر تمام فیلدهای مدل را نیاز ندارید، از select استفاده کنید:

const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    email: true,
  },
});

خروجی فقط شامل فیلدهای انتخاب‌شده است.

این روش مزایای مهمی دارد:

  • انتقال داده کمتر
  • خروجی مشخص‌تر
  • جلوگیری از بازگرداندن تصادفی فیلدهای غیرضروری
  • Type دقیق‌تر در TypeScript

در API عمومی بهتر است Response را آگاهانه بسازید و Object کامل دیتابیس را مستقیماً برنگردانید.

فیلتر کردن داده‌ها

Prisma امکانات متنوعی برای فیلتر دارد:

const users = await prisma.user.findMany({
  where: {
    active: true,
    email: {
      endsWith: "@example.com",
      mode: "insensitive",
    },
    createdAt: {
      gte: new Date("2026-01-01"),
    },
  },
});

ترکیب شرط‌ها با OR:

const users = await prisma.user.findMany({
  where: {
    OR: [
      {
        name: {
          contains: "Sara",
          mode: "insensitive",
        },
      },
      {
        email: {
          contains: "sara",
          mode: "insensitive",
        },
      },
    ],
  },
});

ترکیب با AND:

const users = await prisma.user.findMany({
  where: {
    AND: [
      {
        active: true,
      },
      {
        createdAt: {
          gte: new Date("2026-01-01"),
        },
      },
    ],
  },
});

مرتب‌سازی داده‌ها

const users = await prisma.user.findMany({
  orderBy: [
    {
      active: "desc",
    },
    {
      createdAt: "desc",
    },
  ],
});

صفحه‌بندی با Pagination

صفحه‌بندی با skip و take

const page = 2;
const limit = 20;

const users = await prisma.user.findMany({
  skip: (page - 1) * limit,
  take: limit,
  orderBy: {
    createdAt: "desc",
  },
});

برای دریافت تعداد کل:

const [items, total] =
  await prisma.$transaction([
    prisma.user.findMany({
      skip: (page - 1) * limit,
      take: limit,
      orderBy: {
        createdAt: "desc",
      },
    }),
    prisma.user.count(),
  ]);

Response:

const response = {
  page,
  limit,
  total,
  totalPages: Math.ceil(total / limit),
  items,
};

این روش برای تعداد رکوردهای معمولی مناسب است، اما skipهای بسیار بزرگ ممکن است کارایی کمتری داشته باشند.

Cursor Pagination

برای لیست‌های بزرگ یا Feedها می‌توان از Cursor استفاده کرد:

const users = await prisma.user.findMany({
  take: 20,
  cursor: lastUserId
    ? {
        id: lastUserId,
      }
    : undefined,
  skip: lastUserId ? 1 : 0,
  orderBy: {
    id: "asc",
  },
});

برای Cursor Pagination باید ترتیب پایدار و فیلد مناسب انتخاب شود.

تعریف ارتباط میان مدل‌ها

اکنون می‌خواهیم هر کاربر چند مکالمه داشته باشد و هر مکالمه شامل چند پیام باشد.

Schema کامل‌تر:

enum MessageRole {
  system
  user
  assistant
  tool
}

model User {
  id            String         @id @default(uuid())
  email         String         @unique
  name          String?
  active        Boolean        @default(true)
  createdAt     DateTime       @default(now())
  updatedAt     DateTime       @updatedAt
  conversations Conversation[]

  @@index([createdAt])
}

model Conversation {
  id        String    @id @default(uuid())
  title     String?
  modelId   String
  userId    String
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  user      User      @relation(
    fields: [userId],
    references: [id],
    onDelete: Cascade
  )
  messages  Message[]

  @@index([userId, updatedAt])
}

model Message {
  id             String       @id @default(uuid())
  role           MessageRole
  content        String
  inputTokens    Int?
  outputTokens   Int?
  conversationId String
  createdAt      DateTime     @default(now())

  conversation   Conversation @relation(
    fields: [conversationId],
    references: [id],
    onDelete: Cascade
  )

  @@index([conversationId, createdAt])
}

پس از تغییر Schema:

npx prisma migrate dev --name add_conversations
npx prisma generate

مفهوم Relation در Prisma

در مدل Conversation:

userId String

کلید خارجی را نگهداری می‌کند.

این بخش ارتباط را تعریف می‌کند:

user User @relation(
  fields: [userId],
  references: [id],
  onDelete: Cascade
)

یعنی:

  • هر Conversation به یک User متعلق است.
  • مقدار userId به User.id اشاره می‌کند.
  • با حذف User، Conversationهای مرتبط نیز طبق این طراحی حذف می‌شوند.

انتخاب onDelete: Cascade باید با نیاز محصول، نگهداری سوابق و سیاست داده هماهنگ باشد. در برخی سیستم‌ها Restrict، SetNull یا Soft Delete انتخاب مناسب‌تری است.

ایجاد Relation با connect

ابتدا کاربر:

const user = await prisma.user.create({
  data: {
    email: "user@example.com",
    name: "کاربر نمونه",
  },
});

سپس مکالمه:

const conversation =
  await prisma.conversation.create({
    data: {
      title: "گفت‌وگوی آزمایشی",
      modelId: "YOUR_MODEL_ID",
      user: {
        connect: {
          id: user.id,
        },
      },
    },
  });

Nested Write

می‌توان کاربر و Conversation را در یک عملیات ساخت:

const user = await prisma.user.create({
  data: {
    email: "sara@example.com",
    name: "Sara",
    conversations: {
      create: {
        title: "اولین گفت‌وگو",
        modelId: "YOUR_MODEL_ID",
      },
    },
  },
  include: {
    conversations: true,
  },
});

Nested Writeهای Prisma به شکل Transactional اجرا می‌شوند؛ یعنی اگر بخشی از عملیات شکست بخورد، تغییرات مرتبط نیز برگشت داده می‌شوند.

خواندن Relationها با include

const conversation =
  await prisma.conversation.findUnique({
    where: {
      id: conversationId,
    },
    include: {
      user: {
        select: {
          id: true,
          name: true,
          email: true,
        },
      },
      messages: {
        orderBy: {
          createdAt: "asc",
        },
      },
    },
  });

include مدل‌های مرتبط را به خروجی اضافه می‌کند.

اگر فقط بعضی فیلدها لازم‌اند، از select استفاده کنید:

const conversations =
  await prisma.conversation.findMany({
    select: {
      id: true,
      title: true,
      modelId: true,
      updatedAt: true,
      _count: {
        select: {
          messages: true,
        },
      },
    },
  });

راهنمای کامل در مستندات Relation Queries در Prisma قرار دارد.

فیلتر کردن براساس Relation

تمام کاربرانی که حداقل یک Conversation دارند:

const users = await prisma.user.findMany({
  where: {
    conversations: {
      some: {},
    },
  },
});

تمام مکالمات یک کاربر:

const conversations =
  await prisma.conversation.findMany({
    where: {
      userId,
    },
    orderBy: {
      updatedAt: "desc",
    },
  });

مکالماتی که دارای پیام Assistant هستند:

const conversations =
  await prisma.conversation.findMany({
    where: {
      messages: {
        some: {
          role: "assistant",
        },
      },
    },
  });

Transaction در Prisma

Transaction مجموعه‌ای از عملیات است که باید یا همگی موفق شوند یا هیچ‌کدام اعمال نشوند.

فرض کنید می‌خواهیم هم پیام کاربر و هم پیام Assistant را ذخیره کنیم:

const result = await prisma.$transaction(
  async (tx) => {
    const userMessage =
      await tx.message.create({
        data: {
          conversationId,
          role: "user",
          content: userContent,
        },
      });

    const assistantMessage =
      await tx.message.create({
        data: {
          conversationId,
          role: "assistant",
          content: assistantContent,
          inputTokens,
          outputTokens,
        },
      });

    await tx.conversation.update({
      where: {
        id: conversationId,
      },
      data: {
        updatedAt: new Date(),
      },
    });

    return {
      userMessage,
      assistantMessage,
    };
  },
);

اگر ساخت پیام Assistant شکست بخورد، پیام User نیز در دیتابیس باقی نمی‌ماند.

Prisma از Transactionهای آرایه‌ای نیز پشتیبانی می‌کند:

const [conversation, messageCount] =
  await prisma.$transaction([
    prisma.conversation.findUnique({
      where: {
        id: conversationId,
      },
    }),
    prisma.message.count({
      where: {
        conversationId,
      },
    }),
  ]);

مستندات رسمی انواع Transaction را در راهنمای Transactions and Batch Queries توضیح داده است.

آیا فراخوانی API هوش مصنوعی را داخل Transaction قرار دهیم؟

معمولاً بهتر است Transaction دیتابیس را هنگام انتظار برای یک درخواست شبکه طولانی باز نگه ندارید.

این الگو مناسب نیست:

await prisma.$transaction(async (tx) => {
  await tx.message.create({
    data: userMessage,
  });

  const aiResponse =
    await callExternalAiApi();

  await tx.message.create({
    data: aiResponse,
  });
});

درخواست مدل هوش مصنوعی ممکن است چند ثانیه طول بکشد. باز ماندن Transaction در این مدت می‌تواند Connection دیتابیس را اشغال کند و احتمال Timeout یا رقابت هم‌زمان را افزایش دهد.

الگوی بهتر:

  1. پیام User را ذخیره کنید.
  2. درخواست API هوش مصنوعی را خارج از Transaction اجرا کنید.
  3. پاسخ را اعتبارسنجی کنید.
  4. پیام Assistant و اطلاعات مصرف را در یک Transaction کوتاه ذخیره کنید.
  5. در صورت شکست API، وضعیت پیام یا درخواست را به failed تغییر دهید.

برای سیستم قابل‌اعتمادتر می‌توان مدل AiRequest یا GenerationJob با وضعیت‌های زیر ساخت:

enum GenerationStatus {
  pending
  processing
  completed
  failed
}

ساخت سرویس مکالمه هوش مصنوعی

فایل src/services/chat.service.ts:

import { prisma } from "../lib/prisma.js";

type CreateChatInput = {
  userId: string;
  conversationId: string;
  content: string;
};

export async function createChatMessage(
  input: CreateChatInput,
) {
  const conversation =
    await prisma.conversation.findFirst({
      where: {
        id: input.conversationId,
        userId: input.userId,
      },
      select: {
        id: true,
        modelId: true,
      },
    });

  if (!conversation) {
    throw new Error(
      "Conversation not found",
    );
  }

  const userMessage =
    await prisma.message.create({
      data: {
        conversationId: conversation.id,
        role: "user",
        content: input.content,
      },
    });

  const history =
    await prisma.message.findMany({
      where: {
        conversationId: conversation.id,
      },
      orderBy: {
        createdAt: "asc",
      },
      take: 30,
      select: {
        role: true,
        content: true,
      },
    });

  return {
    conversation,
    userMessage,
    history,
  };
}

در Query اول، userId نیز بررسی شده است تا صرف داشتن شناسه Conversation برای دسترسی کافی نباشد. این نوع شرط باید در تمام Queryهای چندکاربره براساس مدل مجوز پروژه رعایت شود.

اتصال سرویس به API درواره

فایل src/services/ai.service.ts:

type ChatMessage = {
  role: string;
  content: string;
};

type CreateCompletionInput = {
  modelId: string;
  messages: ChatMessage[];
};

export async function createCompletion(
  input: CreateCompletionInput,
) {
  const apiKey =
    process.env.DARVAREH_API_KEY;

  if (!apiKey) {
    throw new Error(
      "DARVAREH_API_KEY is missing",
    );
  }

  const response = await fetch(
    "https://api.darvareh.ir/v1/chat/completions",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: input.modelId,
        messages: input.messages,
      }),
    },
  );

  if (!response.ok) {
    throw new Error(
      `AI API request failed: ${response.status}`,
    );
  }

  return response.json();
}

کلید API باید فقط در Backend و متغیر محیطی نگهداری شود. قرار دادن کلید در کد React، JavaScript مرورگر یا مخزن عمومی باعث قابل‌استخراج شدن آن می‌شود.

برای دریافت API Key می‌توانید در درواره ثبت‌نام کنید. شناسه مدل‌ها و قیمت‌های به‌روز در صفحه مدل‌های درواره قرار دارند.

ذخیره پاسخ مدل هوش مصنوعی

import { prisma } from "../lib/prisma.js";
import { createCompletion } from "./ai.service.js";

export async function processChat(
  userId: string,
  conversationId: string,
  content: string,
) {
  const conversation =
    await prisma.conversation.findFirst({
      where: {
        id: conversationId,
        userId,
      },
      select: {
        id: true,
        modelId: true,
      },
    });

  if (!conversation) {
    throw new Error(
      "Conversation not found",
    );
  }

  await prisma.message.create({
    data: {
      conversationId,
      role: "user",
      content,
    },
  });

  const history =
    await prisma.message.findMany({
      where: {
        conversationId,
      },
      orderBy: {
        createdAt: "asc",
      },
      take: 30,
      select: {
        role: true,
        content: true,
      },
    });

  const completion =
    await createCompletion({
      modelId: conversation.modelId,
      messages: history,
    });

  const assistantContent =
    completion.choices?.[0]?.message
      ?.content;

  if (
    typeof assistantContent !== "string"
  ) {
    throw new Error(
      "Invalid AI response",
    );
  }

  const assistantMessage =
    await prisma.$transaction(
      async (tx) => {
        const message =
          await tx.message.create({
            data: {
              conversationId,
              role: "assistant",
              content: assistantContent,
            },
          });

        await tx.conversation.update({
          where: {
            id: conversationId,
          },
          data: {
            updatedAt: new Date(),
          },
        });

        return message;
      },
    );

  return assistantMessage;
}

در پروژه واقعی بهتر است پاسخ API با Schema Runtime مانند Zod اعتبارسنجی شود و وضعیت Request، خطا، تعداد Tokenها و شناسه درخواست نیز ثبت شوند.

جلوگیری از ساخت چند Prisma Client

در یک Backend عادی معمولاً باید یک Prisma Client مشترک برای فرایند برنامه داشته باشید.

فایل Singleton:

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../../generated/prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma?: PrismaClient;
};

const adapter = new PrismaPg({
  connectionString:
    process.env.DATABASE_URL!,
});

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    adapter,
  });

if (
  process.env.NODE_ENV !== "production"
) {
  globalForPrisma.prisma = prisma;
}

این الگو در محیط Development و ابزارهایی که Hot Reload دارند، از ساخته شدن Clientهای متعدد جلوگیری می‌کند.

برای Runtimeهای Serverless باید Connection Pooling، تعداد Instanceها و محدودیت Connection دیتابیس را نیز بررسی کنید. راهنمای رسمی Prisma درباره محیط‌های Serverless و فرایندهای طولانی در Database Connections منتشر شده است.

Prisma Studio

برای مشاهده داده‌ها:

npx prisma studio

مرورگر باز می‌شود و می‌توانید:

  • جدول‌ها را ببینید.
  • رکوردها را فیلتر کنید.
  • داده Development را ویرایش کنید.
  • Relationها را مشاهده کنید.
  • رکورد آزمایشی بسازید.

Prisma Studio یک ابزار مدیریتی عمومی برای کاربران نهایی نیست. آن را بدون کنترل دسترسی روی اینترنت عمومی منتشر نکنید و دسترسی Production را محدود نگه دارید.

db push چه تفاوتی با migrate dev دارد؟

prisma db push

npx prisma db push

ساختار Schema را بدون تولید تاریخچه Migration روی دیتابیس اعمال می‌کند.

مناسب برای:

  • Prototype
  • آزمایش سریع
  • پروژه موقت
  • محیط Development اولیه

prisma migrate dev

npx prisma migrate dev --name add_messages

Migration ایجاد می‌کند و آن را در Development اعمال می‌کند.

مناسب برای:

  • پروژه واقعی
  • کار تیمی
  • نگهداری تاریخچه تغییرات
  • استقرار در چند محیط

برای پروژه Production بهتر است Migrationها داخل Git ثبت شوند و تغییرات Schema به شکل قابل‌ردیابی مدیریت شوند.

اجرای Migration در Production

در محیط Production از این فرمان استفاده کنید:

npx prisma migrate deploy

این فرمان Migrationهای موجود و اجرا‌نشده را اعمال می‌کند و Migration جدید نمی‌سازد.

فرایند پیشنهادی:

  1. Schema در Development تغییر می‌کند.
  2. migrate dev فایل Migration را می‌سازد.
  3. فایل Migration بررسی و وارد Git می‌شود.
  4. تست‌ها اجرا می‌شوند.
  5. در فرایند Deployment از migrate deploy استفاده می‌شود.

در Production نباید migrate dev اجرا شود؛ زیرا این فرمان برای Workflow توسعه طراحی شده است.

تولید Prisma Client در Build

اسکریپت‌های package.json:

{
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "prisma generate && tsc",
    "start": "node dist/server.js",
    "db:generate": "prisma generate",
    "db:migrate": "prisma migrate dev",
    "db:deploy": "prisma migrate deploy",
    "db:studio": "prisma studio"
  }
}

در محیط CI یا Build مطمئن شوید prisma generate اجرا می‌شود.

پس از هر تغییر Schema:

npx prisma generate

اگر Client دوباره تولید نشود، Typeهای برنامه با Schema جدید هماهنگ نخواهند بود.

Introspection دیتابیس موجود

اگر از قبل دیتابیس دارید، لازم نیست تمام مدل‌ها را دستی تعریف کنید.

ابتدا DATABASE_URL را تنظیم کنید و سپس اجرا کنید:

npx prisma db pull

این فرمان ساختار دیتابیس را بررسی و مدل‌های Prisma را ایجاد یا به‌روزرسانی می‌کند.

سپس:

npx prisma generate

پس از Introspection موارد زیر را بررسی کنید:

  • نام مدل‌ها و فیلدها
  • کلیدهای اصلی
  • Relationها
  • نوع ستون‌ها
  • Indexها
  • Viewها و قابلیت‌های خاص دیتابیس
  • نام‌گذاری با @map و @@map

فایل تولیدشده را بدون بازبینی وارد Production نکنید.

استفاده از @map و @@map

می‌توان نام مدل در کد را از نام جدول دیتابیس جدا کرد.

model UserProfile {
  id        String @id @default(uuid())
  firstName String @map("first_name")
  lastName  String @map("last_name")

  @@map("user_profiles")
}

در TypeScript:

await prisma.userProfile.findMany({
  select: {
    firstName: true,
    lastName: true,
  },
});

در دیتابیس:

user_profiles
first_name
last_name

این قابلیت هنگام اتصال Prisma به دیتابیس قدیمی یا دارای نام‌گذاری Snake Case بسیار مفید است.

تعریف Index در Prisma

Index روی فیلدهایی قرار می‌گیرد که در فیلتر، مرتب‌سازی، Join یا جست‌وجو زیاد استفاده می‌شوند.

model Message {
  id             String      @id @default(uuid())
  conversationId String
  role           MessageRole
  content        String
  createdAt      DateTime    @default(now())

  conversation Conversation @relation(
    fields: [conversationId],
    references: [id],
    onDelete: Cascade
  )

  @@index([conversationId, createdAt])
}

این Index برای Query زیر مفید است:

await prisma.message.findMany({
  where: {
    conversationId,
  },
  orderBy: {
    createdAt: "asc",
  },
});

افزودن Index به تمام فیلدها روش مناسبی نیست. هر Index فضای ذخیره‌سازی مصرف می‌کند و هزینه عملیات Write را افزایش می‌دهد. Index باید براساس Queryهای واقعی و Execution Plan انتخاب شود.

Unique Constraint ترکیبی

فرض کنید هر کاربر فقط یک تنظیم برای هر کلید داشته باشد:

model UserSetting {
  id        String @id @default(uuid())
  userId    String
  key       String
  value     String

  @@unique([userId, key])
}

اکنون می‌توان با کلید ترکیبی Query زد:

const setting =
  await prisma.userSetting.findUnique({
    where: {
      userId_key: {
        userId,
        key: "language",
      },
    },
  });

Aggregate و Group By

میانگین Token خروجی:

const result =
  await prisma.message.aggregate({
    where: {
      role: "assistant",
      outputTokens: {
        not: null,
      },
    },
    _avg: {
      outputTokens: true,
    },
    _sum: {
      outputTokens: true,
    },
    _count: {
      id: true,
    },
  });

گروه‌بندی براساس Role:

const messages =
  await prisma.message.groupBy({
    by: ["role"],
    _count: {
      id: true,
    },
    _sum: {
      inputTokens: true,
      outputTokens: true,
    },
  });

برای گزارش‌های تحلیلی پیچیده ممکن است SQL خام، View یا سیستم تحلیلی جداگانه مناسب‌تر باشد.

استفاده از SQL خام

Prisma امکان اجرای SQL خام را نیز فراهم می‌کند:

const result =
  await prisma.$queryRaw`
    SELECT role, COUNT(*)::int AS count
    FROM "Message"
    GROUP BY role
  `;

Template Tag مربوط به Prisma پارامترها را مدیریت می‌کند. از ساخت Query با String Concatenation خودداری کنید:

const query =
  `SELECT * FROM users WHERE email = '${email}'`;

این الگو می‌تواند Query را آسیب‌پذیر و مدیریت آن را دشوار کند.

SQL خام را فقط زمانی استفاده کنید که Query Builder معمول Prisma نیاز را پوشش نمی‌دهد و ورودی‌ها به شکل پارامتری مدیریت می‌شوند.

Seed کردن دیتابیس

فایل prisma/seed.ts:

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../generated/prisma/client";

const adapter = new PrismaPg({
  connectionString:
    process.env.DATABASE_URL!,
});

const prisma = new PrismaClient({
  adapter,
});

async function main() {
  const user = await prisma.user.upsert({
    where: {
      email: "demo@example.com",
    },
    update: {},
    create: {
      email: "demo@example.com",
      name: "Demo User",
      conversations: {
        create: {
          title: "مکالمه آزمایشی",
          modelId: "YOUR_MODEL_ID",
        },
      },
    },
  });

  console.log(user);
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

اسکریپت:

{
  "scripts": {
    "db:seed": "tsx prisma/seed.ts"
  }
}

اجرا:

npm run db:seed

استفاده از upsert باعث می‌شود Seed تا حد ممکن تکرارپذیر باشد.

مدیریت خطاهای Prisma

تمام خطاهای دیتابیس نباید با یک پیام عمومی و Status Code یکسان پاسخ داده شوند.

نمونه مدیریت خطای Unique Constraint:

import {
  Prisma,
} from "../../generated/prisma/client";

try {
  const user = await prisma.user.create({
    data: {
      email,
      name,
    },
  });

  return user;
} catch (error) {
  if (
    error instanceof
      Prisma.PrismaClientKnownRequestError &&
    error.code === "P2002"
  ) {
    throw new Error(
      "EMAIL_ALREADY_EXISTS",
    );
  }

  throw error;
}

در لایه HTTP می‌توان این خطا را به Response مناسب تبدیل کرد:

{
  "error": "EMAIL_ALREADY_EXISTS",
  "message": "کاربری با این ایمیل وجود دارد."
}

Stack Trace، Connection String، SQL داخلی و جزئیات دیتابیس نباید مستقیماً به کاربر نمایش داده شوند.

اعتبارسنجی ورودی پیش از Prisma

Type-Safe بودن Prisma به معنی معتبر بودن Request کاربر نیست. ورودی API باید پیش از رسیدن به Query اعتبارسنجی شود.

نمونه با Zod:

import { z } from "zod";

const CreateConversationSchema = z.object({
  title: z
    .string()
    .trim()
    .min(1)
    .max(150)
    .optional(),
  modelId: z.string().min(1),
});

سپس:

const result =
  CreateConversationSchema.safeParse(
    req.body,
  );

if (!result.success) {
  return res.status(400).json({
    error: "VALIDATION_ERROR",
  });
}

const conversation =
  await prisma.conversation.create({
    data: {
      ...result.data,
      userId: req.user.id,
    },
  });

Prisma نوع Query دیتابیس را بررسی می‌کند و Zod داده Runtime را اعتبارسنجی می‌کند. این دو ابزار مکمل یکدیگرند.

بهینه‌سازی Queryهای Prisma

فقط فیلدهای مورد نیاز را انتخاب کنید

const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
  },
});

از دریافت Relationهای غیرضروری خودداری کنید

این Query ممکن است حجم زیادی داده برگرداند:

await prisma.user.findMany({
  include: {
    conversations: {
      include: {
        messages: true,
      },
    },
  },
});

بهتر است Relationهای حجیم را صفحه‌بندی یا جداگانه دریافت کنید.

Queryهای داخل Loop را بررسی کنید

الگوی N+1:

for (const user of users) {
  const conversations =
    await prisma.conversation.findMany({
      where: {
        userId: user.id,
      },
    });
}

بهتر:

const users = await prisma.user.findMany({
  include: {
    conversations: {
      take: 5,
      orderBy: {
        updatedAt: "desc",
      },
    },
  },
});

Index متناسب بسازید

فیلدهای پرتکرار در where، orderBy و Relationها را بررسی کنید.

Queryها را اندازه‌گیری کنید

کند بودن برنامه را فقط به ORM نسبت ندهید. Execution Plan، حجم داده، Index، تعداد Connection و Queryهای N+1 را بررسی کنید.

Logging در Prisma Client

برای محیط Development می‌توان Log را فعال کرد:

export const prisma =
  new PrismaClient({
    adapter,
    log: [
      "query",
      "info",
      "warn",
      "error",
    ],
  });

در Production ثبت تمام Queryها می‌تواند حجم Log را زیاد کند و اطلاعات حساس را در معرض ثبت ناخواسته قرار دهد. سطح Log باید براساس نیاز عملیاتی تنظیم شود.

Connection Management

در برنامه‌های طولانی‌مدت مانند Express:

  • یک Prisma Client مشترک بسازید.
  • در هر Request Client جدید ایجاد نکنید.
  • بعد از هر Query از $disconnect() استفاده نکنید.
  • هنگام خاموش شدن برنامه اتصال را مدیریت کنید.
async function shutdown() {
  await prisma.$disconnect();
  process.exit(0);
}

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

در محیط‌های Serverless:

  • تعداد Instanceهای هم‌زمان را در نظر بگیرید.
  • از Connection Pooling متناسب استفاده کنید.
  • محدودیت Connection دیتابیس را بررسی کنید.
  • Client را تا حد امکان خارج Handler بسازید.
  • رفتار Runtime و Platform را با تست Load اندازه‌گیری کنید.

استفاده از Prisma در Next.js

در Next.js به دلیل Hot Reload محیط Development، استفاده از Singleton اهمیت بیشتری دارد:

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    adapter,
  });

if (
  process.env.NODE_ENV !== "production"
) {
  globalForPrisma.prisma = prisma;
}

همچنین Prisma Client و Connection String فقط باید در Server Component، Route Handler، Server Action یا Backend استفاده شوند. آن‌ها را وارد Client Component نکنید.

راهنمای رسمی Prisma برای Next.js در مستندات Prisma و Next.js در دسترس است.

تست Prisma

برای تست‌های Integration بهتر است یک دیتابیس مجزا داشته باشید:

DATABASE_URL="postgresql://test_user:test_password@localhost:5433/ai_app_test?schema=public"

پیش از تست:

npx prisma migrate deploy

اصول مهم:

  • تست‌ها نباید به دیتابیس Production متصل شوند.
  • داده تست باید قابل پاک‌سازی یا بازسازی باشد.
  • Migrationها باید در CI بررسی شوند.
  • برای عملیات Transactional تست شکست میانی بنویسید.
  • Unique Constraint و Relation Constraint را آزمایش کنید.
  • Queryهای مجوز دسترسی چندکاربره را تست کنید.

استفاده از Mock برای Unit Test ممکن است مناسب باشد، اما Queryها و Constraintهای واقعی دیتابیس فقط در Integration Test به‌درستی بررسی می‌شوند.

Prisma Migrate و تغییرات حساس Schema

بعضی تغییرات ساده و بعضی پرریسک‌ترند.

تغییرات نیازمند دقت بیشتر:

  • حذف ستون
  • تغییر Type ستون
  • اجباری کردن ستون دارای مقدار Null
  • افزودن Unique Constraint روی داده موجود
  • تغییر Relation
  • تغییر رفتار Cascade
  • تغییر نام جدول یا فیلد
  • Migration روی جدول بسیار بزرگ

برای اجباری کردن یک فیلد جدید بهتر است از فرایند چندمرحله‌ای استفاده کنید:

  1. فیلد Nullable اضافه شود.
  2. برنامه جدید مقدار آن را برای رکوردهای تازه بنویسد.
  3. داده‌های قبلی Backfill شوند.
  4. نبود مقدار بررسی شود.
  5. فیلد در Migration بعدی Required شود.

فایل SQL تولیدشده توسط Migration را پیش از Production بررسی کنید.

Backup قبل از Migration

Migration جایگزین Backup نیست. پیش از تغییرات مهم در Production:

  • Backup معتبر بگیرید.
  • امکان Restore را آزمایش کنید.
  • Migration را روی نسخه مشابه داده آزمایش کنید.
  • زمان اجرای Migration را تخمین بزنید.
  • Rollback یا Forward Fix را برنامه‌ریزی کنید.
  • تغییرات ساختاری و انتشار کد را هماهنگ کنید.

اشتباهات رایج در Prisma

اجرای migrate dev در Production

فرمان درست برای Production:

npx prisma migrate deploy

فراموش کردن prisma generate

بعد از تغییر Schema:

npx prisma generate

ساخت Prisma Client در هر Request

این کار می‌تواند Connectionهای زیادی ایجاد کند. یک Client مشترک بسازید.

بازگرداندن مستقیم مدل دیتابیس

Response API را با select یا DTO مشخص کنید.

اعتماد به TypeScript برای ورودی Runtime

ورودی Request باید با Zod یا ابزار مشابه اعتبارسنجی شود.

دریافت تمام Relationها

استفاده بی‌محدودیت از include ممکن است Response بزرگ و Query سنگین ایجاد کند.

نبود Index

Query Type-Safe همچنان می‌تواند کند باشد. Index براساس الگوی Query لازم است.

قرار دادن درخواست شبکه داخل Transaction

Transaction را کوتاه نگه دارید و عملیات طولانی شبکه را خارج آن اجرا کنید.

استفاده از db push در Workflow Production

برای پروژه تیمی و Production، Migrationهای نسخه‌بندی‌شده انتخاب مناسب‌تری هستند.

ذخیره Connection String در Git

فایل .env و Secretهای محیط Production نباید وارد مخزن شوند.

حذف داده بدون بررسی Relationها

پیش از delete و deleteMany اثر Cascade و وابستگی رکوردها را بررسی کنید.

چک‌لیست Prisma برای Production

  • نسخه Prisma و Driver Adapter مشخص و Pin شده‌اند.
  • DATABASE_URL از Secret Manager یا متغیر محیطی دریافت می‌شود.
  • Prisma Client در Build تولید می‌شود.
  • Migrationها داخل Git ثبت شده‌اند.
  • migrate deploy در فرایند Deployment اجرا می‌شود.
  • پیش از Migrationهای حساس Backup گرفته می‌شود.
  • یک Prisma Client مشترک استفاده می‌شود.
  • Connection Pooling متناسب با Runtime تنظیم شده است.
  • ورودی‌های API پیش از Query اعتبارسنجی می‌شوند.
  • خروجی API با select محدود شده است.
  • Relationهای حجیم صفحه‌بندی می‌شوند.
  • Queryهای پرتکرار Index مناسب دارند.
  • Transactionها کوتاه هستند.
  • APIهای خارجی داخل Transaction طولانی اجرا نمی‌شوند.
  • Queryهای مجوز دسترسی، userId یا Tenant را بررسی می‌کنند.
  • دیتابیس Test از Production جدا است.
  • Logها Connection String و داده حساس را ذخیره نمی‌کنند.
  • Prisma Studio به اینترنت عمومی باز نیست.
  • Monitoring برای خطا، زمان Query و مصرف Connection وجود دارد.

پرسش‌های متداول

Prisma ORM چیست؟

Prisma ORM ابزاری برای مدل‌سازی داده، تولید Prisma Client Type-Safe، اجرای Query، مدیریت Relationها و ساخت Migration در پروژه‌های JavaScript و TypeScript است.

آیا Prisma جایگزین PostgreSQL است؟

خیر. PostgreSQL دیتابیس است و Prisma لایه دسترسی برنامه به آن محسوب می‌شود.

آیا برای استفاده از Prisma باید SQL بلد باشیم؟

برای شروع می‌توان بدون نوشتن SQL زیاد پیش رفت، اما برای طراحی دیتابیس، Index، بهینه‌سازی، Transaction و رفع مشکلات Production باید SQL و مفاهیم دیتابیس را بدانید.

Prisma از چه دیتابیس‌هایی پشتیبانی می‌کند؟

Prisma ORM از چند دیتابیس رایج مانند PostgreSQL، MySQL، SQLite، SQL Server، CockroachDB و MongoDB پشتیبانی می‌کند. قابلیت‌ها و محدودیت‌ها ممکن است میان Connectorها متفاوت باشند.

تفاوت prisma generate و migrate چیست؟

prisma generate کد Prisma Client را تولید می‌کند. migrate ساختار دیتابیس را تغییر می‌دهد. این دو عملیات هدف متفاوتی دارند.

تفاوت prisma db push و migrate dev چیست؟

db push Schema را بدون ساخت تاریخچه Migration اعمال می‌کند. migrate dev Migration نسخه‌بندی‌شده تولید می‌کند و برای Workflow پروژه واقعی مناسب‌تر است.

آیا Prisma برای پروژه هوش مصنوعی مناسب است؟

بله. Prisma برای ذخیره کاربران، مکالمات، پیام‌ها، Jobها، حافظه Agent، گزارش مصرف و Metadata مناسب است. برای Vector Search ممکن است نیاز به قابلیت‌های PostgreSQL یا Queryهای اختصاصی نیز داشته باشید.

آیا می‌توان با Prisma SQL خام اجرا کرد؟

بله، Prisma از $queryRaw و $executeRaw پشتیبانی می‌کند. Query باید پارامتری و با دقت اجرا شود.

آیا Prisma Client را در هر Request بسازیم؟

خیر. در بیشتر Backendها باید یک Client مشترک برای فرایند برنامه ساخته شود.

آیا migrate dev را روی سرور Production اجرا کنیم؟

خیر. برای اعمال Migrationهای موجود در Production از prisma migrate deploy استفاده کنید.

آیا Prisma Validation ورودی را انجام می‌دهد؟

Prisma Type و ساختار Query را بررسی می‌کند، اما جایگزین Runtime Validation ورودی کاربران نیست. برای req.body، Query String و پاسخ API از Zod یا ابزار مشابه استفاده کنید.

آیا Prisma برای Serverless مناسب است؟

قابل استفاده است، اما باید Connection Pooling، تعداد Instanceها، محدودیت Connection دیتابیس و الگوی ساخت Prisma Client متناسب با Platform بررسی شوند.

Prisma Studio چیست؟

یک رابط گرافیکی برای مشاهده و ویرایش داده‌های دیتابیس است که بیشتر در محیط Development استفاده می‌شود.

جمع‌بندی

Prisma ORM یکی از ابزارهای کاربردی اکوسیستم TypeScript برای ارتباط با دیتابیس است. این ابزار با تبدیل Prisma Schema به یک Client اختصاصی، Queryهای Type-Safe، Autocomplete، Relation، Migration و Transaction را در اختیار توسعه‌دهنده قرار می‌دهد.

برای راه‌اندازی درست Prisma با PostgreSQL باید میان چند بخش تفاوت قائل شوید:

  • Prisma Schema ساختار مدل‌ها را تعریف می‌کند.
  • Prisma Client Queryهای Type-Safe را اجرا می‌کند.
  • Prisma Migrate تغییرات دیتابیس را نسخه‌بندی می‌کند.
  • Driver Adapter اتصال Prisma 7 به PostgreSQL را فراهم می‌کند.
  • Prisma Studio مشاهده داده‌های Development را آسان‌تر می‌کند.

در یک پروژه واقعی، Type Safety به‌تنهایی کافی نیست. ورودی API را اعتبارسنجی کنید، Queryها را با select محدود کنید، Indexها را براساس الگوی استفاده بسازید، Transactionها را کوتاه نگه دارید و Migrationهای Production را با Backup و برنامه استقرار کنترل‌شده اجرا کنید.

برای برنامه‌های هوش مصنوعی نیز Prisma می‌تواند لایه مناسبی برای نگهداری کاربران، مکالمات، پیام‌ها، وضعیت درخواست‌ها و گزارش مصرف باشد. اتصال به مدل‌های هوش مصنوعی از طریق API درواره انجام می‌شود و Prisma داده‌های برنامه را در PostgreSQL مدیریت می‌کند. این جداسازی، معماری پروژه را شفاف‌تر و قابل نگهداری‌تر می‌سازد.

منابع تکمیلی

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.