MATIN MOLKARA
SERIES: آموزش Next js — PART 10 OF 28

قسمت دهم - اتصال Next.js به PostgreSQL با Prisma

وقتی قرار است Next.js را به دیتابیس وصل کنیم، فقط به یک اتصال خام نیاز نداریم؛ به ابزاری نیاز داریم که کار با داده را قابل‌فهم، type-safe و قابل نگهداری کند.

وقتی قرار است Next.js را به دیتابیس وصل کنیم، فقط به یک اتصال خام نیاز نداریم؛
به ابزاری نیاز داریم که کار با داده را قابل‌فهم، type-safe و قابل نگهداری کند.

وقتی از یک پرتفولیوی ساده عبور می‌کنیم و وارد ساخت یک پروژه واقعی می‌شویم، خیلی زود به یک نیاز مشترک می‌رسیم:
باید داده‌ها جایی خارج از کد ذخیره شوند.

تا وقتی پروژه‌ها، پست‌ها، اطلاعات About یا محتوای CMS را با آرایه‌های دستی داخل فایل‌ها نگه می‌داریم، توسعه سریع است؛
اما این مدل فقط برای شروع مناسب است.
در یک پروژه واقعی، باید بتوانیم داده را ذخیره کنیم، ویرایش کنیم، منتشر کنیم و بعد در صفحه‌های مختلف نمایش دهیم.

در این مرحله، ترکیب PostgreSQL و Prisma یکی از انتخاب‌های استاندارد و عملی برای پروژه‌های Next.js است.

در این نوشته، قدم‌به‌قدم این مسیر را می‌سازیم:

  • راه‌اندازی Prisma
  • اتصال به PostgreSQL
  • طراحی مدل‌های داده
  • اجرای migration
  • ساخت Prisma Client
  • و در نهایت، ساخت یک data layer ساده برای استفاده در پروژه

چرا Prisma؟

وقتی قرار است Next.js را به دیتابیس وصل کنیم، فقط به یک اتصال خام نیاز نداریم؛
به ابزاری نیاز داریم که کار با داده را شفاف، type-safe و قابل نگهداری کند.

Prisma دقیقاً همین‌جا ارزشش را نشان می‌دهد.

چند دلیل مهم برای انتخاب Prisma:

  • تعریف شفاف مدل‌ها: ساختار داده را در schema.prisma مشخص می‌کنیم
  • Type safety: کوئری‌ها و داده‌های برگشتی با TypeScript هماهنگ هستند
  • Migration workflow: تغییرات مدل را می‌توانیم به‌صورت کنترل‌شده به دیتابیس اعمال کنیم
  • Prisma Client: برای query گرفتن نیازی به نوشتن SQL خام در همه‌جا نداریم
  • خوانایی بهتر کد: منطق کار با داده مرتب‌تر از queryهای پراکنده می‌شود

برای پروژه‌ای مثل پرتفولیو، بلاگ، داشبورد و CMS، این مزیت‌ها مهم‌اند؛
چون قرار نیست فقط یک جدول ساده داشته باشیم. معمولاً به‌مرور با relationها، slugها، وضعیت draft/published و پنل مدیریت محتوا روبه‌رو می‌شویم.


راه‌اندازی Prisma

در این پروژه از npm استفاده می‌کنیم.

برای شروع، پکیج‌های لازم را نصب می‌کنیم:

npm install prisma @prisma/client
npm install @prisma/adapter-pg pg
npm install -D tsx

در این سری آموزشی، چون از PostgreSQL استفاده می‌کنیم و می‌خواهیم با الگوی به‌روز Prisma جلو برویم، از @prisma/adapter-pg هم استفاده می‌کنیم.

نقش هرکدام:

  • prisma: ابزار CLI برای init، migrate و generate
  • @prisma/client: کلاینتی که در کد پروژه با آن query می‌گیریم
  • pg: درایور PostgreSQL
  • @prisma/adapter-pg: آداپتر برای setup جدید Prisma با PostgreSQL
  • tsx: برای اجرای فایل‌های TypeScript در بعضی workflowها

بعد از نصب، Prisma را initialize می‌کنیم:

npx prisma init

این دستور معمولاً فایل‌ها و ساختار اولیه زیر را می‌سازد:

prisma/schema.prisma
.env

در بعضی setupها ممکن است فایل‌های دیگری هم ببینی، اما برای شروع، همین دو فایل مهم‌ترین بخش هستند.


اتصال به PostgreSQL

بعد از initialize شدن Prisma، مهم‌ترین بخش اتصال، تنظیم DATABASE_URL است.

داخل فایل .env معمولاً چیزی شبیه این قرار می‌گیرد:

DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"

فرقی نمی‌کند دیتابیس روی localhost باشد یا روی سرویسی مثل:

  • Neon
  • Supabase
  • Railway
  • Render
  • یا هر سرویس دیگری که PostgreSQL ارائه می‌دهد

در هر صورت، Prisma به یک connection string معتبر نیاز دارد.

مثال:

DATABASE_URL="postgresql://postgres:123456@localhost:5432/portfolio_db?schema=public"

در این مرحله مسئولیت ما روشن است:

  1. یک دیتابیس PostgreSQL بسازیم
  2. connection string را بگیریم
  3. آن را داخل .env در DATABASE_URL قرار بدهیم

نکته مهم

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


طراحی مدل‌های داده

بعد از اتصال، نوبت مهم‌ترین بخش معماری داده است: مدل‌سازی.

در Prisma، مدل‌ها داخل prisma/schema.prisma تعریف می‌شوند.
اینجا مشخص می‌کنیم چه entityهایی در پروژه داریم و هرکدام چه فیلدهایی دارند.

برای یک پروژه پرتفولیو، خیلی محتمل است که با مدل‌هایی مثل این سروکار داشته باشیم:

  • Project
  • Post
  • User
  • و شاید بعداً Tag، Category یا ContactMessage

اگر بخواهیم از یک مدل ساده برای پروژه‌ها شروع کنیم، می‌توانیم چیزی شبیه این داشته باشیم:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Project {
  id           String   @id @default(cuid())
  title        String
  slug         String   @unique
  description  String
  content      String?
  category     String?
  technologies String[]
  demoUrl      String?
  githubUrl    String?
  published    Boolean  @default(false)
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
}

اگر بخواهیم برای بلاگ هم آماده باشیم، می‌توانیم بعداً مدلی شبیه این اضافه کنیم:

model Post {
  id          String   @id @default(cuid())
  title       String
  slug        String   @unique
  excerpt     String?
  content     String
  published   Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

در طراحی مدل به چه چیزهایی فکر کنیم؟

برای پروژه واقعی، فقط داشتن چند فیلد کافی نیست. بهتر است از همان ابتدا به این سؤال‌ها فکر کنیم:

  • آیا این محتوا published دارد یا draft هم خواهیم داشت؟
  • آیا برای routeها به slug نیاز داریم؟
  • آیا فیلدی باید unique باشد؟
  • آیا بعداً search یا filtering خواهیم داشت؟
  • آیا بین مدل‌ها relation لازم است؟

مدل‌سازی خوب یعنی ساختاری بنویسیم که هم نیاز فعلی را پوشش دهد، هم با رشد پروژه نشکند.


migration

بعد از اینکه مدل‌ها را در schema.prisma تعریف کردیم، هنوز هیچ جدولی در دیتابیس ساخته نشده است.
برای اعمال این تغییرات باید migration اجرا کنیم.

دستور معمول:

npx prisma migrate dev --name init

این دستور چند کار مهم انجام می‌دهد:

  • schema فعلی را بررسی می‌کند
  • فایل migration می‌سازد
  • تغییرات را روی دیتابیس اعمال می‌کند
  • در بسیاری از workflowها Prisma Client را هم regenerate می‌کند

اگر بعداً مدل‌ها را تغییر بدهیم، دوباره migration جدید می‌سازیم. مثلاً:

npx prisma migrate dev --name add-post-model

نکته مهم این است که migration فقط یک دستور برای ساخت جدول نیست؛
در واقع تاریخچه تغییرات ساختار دیتابیس ما است.

برای همین، migrationها بخش مهمی از نگهداری پروژه هستند.

اگر بخواهیم فقط Prisma Client را دوباره تولید کنیم، از این دستور استفاده می‌کنیم:

npx prisma generate

Prisma Client

بعد از اینکه schema آماده شد و migration اجرا شد، وقت استفاده از Prisma Client است.
این همان ابزاری است که در کد Next.js با آن query می‌گیریم. setup ما می‌تواند شبیه این باشد:

import { PrismaClient } from '@prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import { Pool } from 'pg'

const globalForPrisma = globalThis as unknown as {
  db: PrismaClient | undefined
  pgPool: Pool | undefined
}

const connectionString = process.env.DATABASE_URL

if (!connectionString) {
  throw new Error('DATABASE_URL is not set.')
}

const pool =
  globalForPrisma.pgPool ??
  new Pool({
    connectionString,
  })

const adapter = new PrismaPg(pool)

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

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.pgPool = pool
  globalForPrisma.db = db
}

این فایل را می‌توانیم در مسیر زیر قرار بدهی:

lib/db.ts

چرا این الگو مهم است؟

چند دلیل دارد:

  • یک instance مرکزی برای کار با دیتابیس داریم
  • از setup جدید Prisma با adapter استفاده می‌کنیم
  • در development جلوی ساخت instanceهای غیرضروری گرفته می‌شود
  • در پست‌های بعدی، همین db را مستقیم داخل Server Actions استفاده خواهیم کرد

ساخت لایه دسترسی به داده

تا اینجا به دیتابیس وصل شدیم و Prisma Client هم آماده است.
اما یک نکته مهم معماری باقی مانده:

بهتر است queryها را مستقیم در همه pageها و componentها نریزیم.

اینجاست که data layer وارد می‌شود.

یعنی به‌جای این:

const projects = await db.project.findMany()

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

مثلاً:

// lib/data/projects.ts
import { db } from '@/lib/db'

export async function getProjects() {
  return db.project.findMany({
    where: {
      published: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  })
}

export async function getProjectBySlug(slug: string) {
  return db.project.findUnique({
    where: {
      slug,
    },
  })
}

این الگو چند مزیت جدی دارد:

  • منطق query از UI جدا می‌شود
  • reuse راحت‌تر می‌شود
  • تغییرات بعدی ساده‌تر می‌شوند
  • pageها و sectionها تمیزتر می‌مانند
  • تست‌پذیری بهتر می‌شود

برای مثال، در app/projects/page.tsx می‌توانیم فقط این را داشته باشیم:

import { getProjects } from '@/lib/data/projects'

export default async function ProjectsPage() {
  const projects = await getProjects()

  return (
    <div>
      {projects.map((project) => (
        <div key={project.id}>{project.title}</div>
      ))}
    </div>
  )
}

در این نقطه، دیگر page وظیفه ساخت query ندارد؛ فقط داده را می‌گیرد و نمایش می‌دهد.


در این مرحله تمرکز ما روی ساخت زیرساخت است:

  • دیتابیس
  • schema
  • migration
  • Prisma Client
  • data layer

یعنی فعلاً داریم خواندن و سازمان‌دهی داده را آماده می‌کنیم.

بخش ذخیره‌سازی، ویرایش و حذف داده را در پست‌های بعدی با استفاده از Server Actions انجام می‌دهیم؛
چون آنجا باید علاوه بر Prisma، به validation، auth، revalidatePath و redirect هم توجه کنیم.

جمع‌بندی

برای اینکه یک پروژه Next.js از مرحله mock data عبور کند و وارد فاز واقعی شود، باید یک data layer قابل اتکا داشته باشد.
ترکیب PostgreSQL و Prisma یکی از بهترین مسیرها برای رسیدن به این هدف است.

در این مسیر:

  • PostgreSQL داده‌های پروژه را نگه می‌دارد
  • Prisma مدل‌سازی و migration را مدیریت می‌کند
  • Prisma Client راهی type-safe برای query گرفتن می‌دهد
  • data layer کمک می‌کند منطق دسترسی به داده از UI جدا بماند

نتیجه این است که صفحه‌هایی مثل projects، blog، dashboard و CMS روی یک پایه واقعی و قابل نگهداری ساخته می‌شوند، نه روی آرایه‌های موقتی داخل فایل‌ها.

// 0 comments
#Next.js#connection#Database#Prisma

نظرات (0)

نظر خود را بنویسید