ساخت یک ابزار مدیریت پروژه با Prisma
من در حال ساخت یک ابزار مدیریت پروژه مشارکتی شبیه به Trello هستم.
من از React، Express.js، PostgreSQL و Socket.io استفاده میکنم. قبل از اینکه هرگونه مسیر (route) بکاند را بنویسم، باید طرحواره (schema) پایگاه داده را طراحی کنم.
طرحواره، زیربنا است. اگر طرحواره اشتباه باشد، کل اپلیکیشن با شکست مواجه میشود. در اینجا تحلیل من از طراحی طرحواره Prisma آمده است.
مدلها
• User: نامها، ایمیلها و رمزهای عبور را ذخیره میکند. من از cuid() برای شناسهها (IDs) استفاده میکنم. این کار رشتههای طولانی و منحصربهفردی ایجاد میکند. این روش از اعداد بهتر است زیرا تعداد کاربران شما را در URL لو نمیدهد.
• Project: نام و توضیحات پروژه را نگه میدارد. من توضیحات را با استفاده از یک علامت سوال اختیاری کردم.
• ProjectMember: این یک جدول واسط (junction table) است. کاربران را به پروژهها متصل میکند. از آنجایی که یک کاربر میتواند در پروژههای زیادی عضو شود و یک پروژه میتواند کاربران زیادی داشته باشد، برای مدیریت رابطه چند-به-چند (many-to-many) به این جدول میانی نیاز دارید. من یک محدودیت منحصربهفرد (unique constraint) اضافه کردم تا از عضویت دوباره همان کاربر در یک پروژه جلوگیری کنم.
• Board: وظایف (Tasks) درون بردها (boards) قرار دارند. بردها درون پروژهها قرار دارند. این سلسلهمراتب، قابلیت کشیدن و رها کردن (drag-and-drop) را آسان میکند. جابهجایی یک وظیفه بین ستونها تنها یک بهروزرسانی ساده در یک فیلد است.
• Task: این مدل اصلی است. این مدل دو رابطه متفاوت با مدل User دارد:
- یک کاربر تعیینشده (این اختیاری است).
- یک ایجادکننده (این الزامی است). من مجبور شدم این روابط را بهطور صریح نامگذاری کنم تا Prisma بداند هر کدام مربوط به چیست.
• Comment: کاربران میتوانند روی وظایف نظر بگذارند. من برای خواناتر شدن کد، نام رابطه را به جای "user" به "author" تغییر دادم.
• Notification: یک مدل ساده برای پیگیری پیامها برای کاربران.
درسهای فنی آموخته شده
من هنگام ساخت این پروژه با چندین خطا مواجه شدم. مراقب این موارد باشید:
- نامهای رابطه (Relation Names): اگر دو فیلد به یک مدل یکسان اشاره کنند، باید روابط را نامگذاری کنید. اگر این کار را انجام ندهید، Prisma خطا خواهد داد.
- فیلدهای قابل تهی (Nullable Fields): اگر یک رابطه اختیاری است، باید علامت سوال را هم در فیلد رابطه و هم در فیلد کلید خارجی (foreign key) قرار دهید.
- خطاهای سینتکس (Syntax Errors): Prisma برای مقادیر پیشفرض رشتهای (string defaults) به علامت نقلقول دوگانه (double quotes) نیاز دارد. استفاده از علامت نقلقول تک (single quotes) باعث خطا میشود.
- سینتکس رابطه (Relation Syntax): همیشه از سینتکس صحیح استفاده کنید:
@relation(fields: [localField], references: [remoteField]).
اکنون طرحواره مهاجرت (migrate) شده است. در مرحله بعد، بکاند Express و تنظیمات Socket.io را برای بهروزرسانیهای آنی (real-time) خواهم ساخت.
ساخت یک ابزار مدیریت پروژه از صفر، با شروع از Prisma schema
در این مجموعه، من یک ابزار مدیریت پروژه را از صفر خواهم ساخت. ما از یک پشته تکنولوژی (tech stack) مدرن شامل Next.js، TypeScript، Tailwind CSS و Prisma استفاده خواهیم کرد.
اولین قدم در ساخت هر اپلیکیشن، تعریف مدل دادهها است. از آنجایی که ما از Prisma استفاده میکنیم، کار را با تعریف اسکیما در schema.prisma شروع میکنیم.
چرا Prisma؟
Prisma یک ORM نسل جدید است که تعامل با پایگاه داده شما را آسان میکند. این ابزار امنیت نوع (type safety) و یک API شهودی را فراهم میکند.
طراحی اسکیما
یک ابزار مدیریت پروژه به چندین موجودیت کلیدی نیاز دارد:
- Users: افرادی که از ابزار استفاده میکنند.
- Projects: ظرفهایی برای وظایف (tasks).
- Tasks: کارهای اصلی که باید انجام شوند.
- Comments: بحثها درباره وظایف.
اسکیما در Prisma
در اینجا اسکیما اولیه آورده شده است:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
email String @unique
name String?
projects Project[]
tasks Task[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Project {
id String @id @default(cuid())
name String
description String?
ownerId String
owner User @relation(fields: [ownerId], references: [id])
tasks Task[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Task {
id String @id @default(cuid())
title String
description String?
status Status @default(TODO)
projectId String
project Project @relation(fields: [projectId], references: [id])
assigneeId String?
assignee User? @relation(fields: [assigneeId], references: [id])
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Comment {
id String @id @default(cuid())
content String
taskId String
task Task @relation(fields: [taskId], references: [id])
userId String
user User @relation(fields: [userId], references: [