بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج

SQL آمن الأنواع يبدو مثل TypeScript. Drizzle ORM هو الـ ORM الحديث والخفيف الذي يمنحك كامل قوة SQL بدون أي عبء إضافي أثناء التشغيل. في هذا الدليل، ستبني تطبيقاً كاملاً لإدارة المهام باستخدام Next.js 15 وServer Actions وPostgreSQL.
ما ستتعلمه
بنهاية هذا الدليل، ستكون قادراً على:
- إعداد Drizzle ORM مع PostgreSQL في مشروع Next.js 15
- تعريف مخطط قاعدة بيانات آمن الأنواع بلغة TypeScript الصرفة
- تشغيل الترحيلات باستخدام
drizzle-kit - بناء عمليات CRUD كاملة باستخدام Next.js Server Actions
- التعامل مع إرسال النماذج باستخدام
useActionStateوالتحقق بـ Zod - تنفيذ استعلامات علائقية مع منشئ استعلامات Drizzle
- نشر تطبيق جاهز للإنتاج مع أنماط قاعدة بيانات صحيحة
المتطلبات المسبقة
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبّت (
node --version) - خبرة في TypeScript (الأنواع، الأنواع المعممة، async/await)
- معرفة بـ Next.js (App Router، Server Components)
- PostgreSQL يعمل محلياً أو قاعدة بيانات سحابية (سنستخدم Neon)
- محرر أكواد — يُنصح بـ VS Code أو Cursor
لماذا Drizzle ORM؟
يضم نظام JavaScript/TypeScript عدة أدوات ORM — مثل Prisma وTypeORM وSequelize — فلماذا نختار Drizzle؟ إليك ما يميزه:
| الميزة | Drizzle | Prisma | TypeORM |
|---|---|---|---|
| حجم الحزمة | ~7.4 KB | ~280 KB | ~180 KB |
| لغة المخطط | TypeScript | DSL مخصص (.prisma) | TypeScript/مزخرفات |
| التحكم في SQL | واجهة شبيهة بـ SQL | استعلامات مجردة | استعلامات مجردة |
| جاهز للحوسبة بدون خادم | نعم (بدون تبعيات) | يتطلب ملف محرك ثنائي | غير محسّن |
| أمان الأنواع | استنتاج كامل | أنواع مولّدة | جزئي |
| منحنى التعلم | تعرف SQL = تعرف Drizzle | بناء جمل جديد | أنماط المزخرفات |
Drizzle يتبع نهجاً مختلفاً جذرياً: إذا كنت تعرف SQL، فأنت تعرف Drizzle بالفعل. لا توجد لغة استعلام مخصصة، ولا خطوة لتوليد الأكواد، ولا عبء أثناء التشغيل. مخططك هو TypeScript، واستعلاماتك تبدو مثل SQL، وكل نوع إرجاع يُستنتج تلقائياً.
الخطوة 1: إنشاء مشروع Next.js 15 جديد
ابدأ بإنشاء هيكل مشروع Next.js جديد مع TypeScript:
npx create-next-app@latest task-manager --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd task-managerاختر الإعدادات الافتراضية عند السؤال. هذا يمنحك مشروع Next.js 15 مع:
- App Router
- TypeScript
- Tailwind CSS
- بنية مجلد
src/
الخطوة 2: تثبيت Drizzle ORM والتبعيات
ثبّت Drizzle ORM ومشغل PostgreSQL وDrizzle Kit (أداة سطر الأوامر للترحيلات):
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kitنستخدم @neondatabase/serverless كمشغل PostgreSQL لأنه يعمل بسلاسة في بيئات الحوسبة بدون خادم والبيئات التقليدية. إذا كنت تستخدم PostgreSQL محلي، يمكنك استخدام postgres (postgres.js) أو pg بدلاً من ذلك:
# بديل: لـ PostgreSQL المحلي
npm install drizzle-orm postgres
npm install -D drizzle-kitالخطوة 3: إعداد اتصال قاعدة البيانات
أنشئ ملف .env.local مع سلسلة الاتصال بقاعدة البيانات:
DATABASE_URL="postgresql://username:password@hostname/database?sslmode=require"تستخدم Neon؟ سجّل في neon.tech، أنشئ مشروعاً، وانسخ سلسلة الاتصال من لوحة التحكم. الطبقة المجانية تمنحك 512 ميجابايت — أكثر من كافٍ لهذا الدليل.
الآن أنشئ ملف اتصال قاعدة البيانات:
// src/db/index.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle({ client: sql, schema });لـ PostgreSQL المحلي مع postgres (postgres.js)، الإعداد يبدو هكذا:
// src/db/index.ts (بديل لـ PostgreSQL المحلي)
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle({ client, schema });الخطوة 4: تعريف مخطط قاعدة البيانات
هنا يتألق Drizzle. مخططك هو TypeScript خالص — بدون DSL مخصص، بدون مزخرفات، فقط دوال وأنواع:
// src/db/schema.ts
import {
pgTable,
serial,
text,
boolean,
timestamp,
integer,
pgEnum,
} from "drizzle-orm/pg-core";
// تعريف تعداد لأولوية المهمة
export const priorityEnum = pgEnum("priority", ["low", "medium", "high", "urgent"]);
// جدول المستخدمين
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// جدول المشاريع
export const projects = pgTable("projects", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
description: text("description"),
userId: integer("user_id")
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// جدول المهام
export const tasks = pgTable("tasks", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
description: text("description"),
completed: boolean("completed").default(false).notNull(),
priority: priorityEnum("priority").default("medium").notNull(),
projectId: integer("project_id")
.references(() => projects.id, { onDelete: "cascade" })
.notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});لاحظ كيف يُقرأ هذا تقريباً مثل تعليمات SQL CREATE TABLE، لكن مع استنتاج أنواع TypeScript الكامل. كل نوع عمود وقيد وعلاقة محدد الأنواع بقوة.
تعريف العلاقات
يفصل Drizzle المعلومات العلائقية عن مخطط الجدول. هذا يبقي الأمور صريحة:
// src/db/schema.ts (تابع)
import { relations } from "drizzle-orm";
export const usersRelations = relations(users, ({ many }) => ({
projects: many(projects),
}));
export const projectsRelations = relations(projects, ({ one, many }) => ({
user: one(users, {
fields: [projects.userId],
references: [users.id],
}),
tasks: many(tasks),
}));
export const tasksRelations = relations(tasks, ({ one }) => ({
project: one(projects, {
fields: [tasks.projectId],
references: [projects.id],
}),
}));الخطوة 5: إعداد Drizzle Kit
أنشئ ملف إعداد Drizzle Kit في جذر المشروع:
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});أضف سكريبتات الترحيل إلى ملف package.json:
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}الخطوة 6: توليد وتشغيل الترحيلات
ولّد ملفات الترحيل من مخططك:
npm run db:generateهذا ينشئ ملفات ترحيل SQL في مجلد drizzle/. افحصها لترى بالضبط أي SQL سيُنفّذ — Drizzle لا يخفي SQL عنك أبداً.
طبّق الترحيلات على قاعدة بياناتك:
npm run db:migrateنصيحة للتطوير السريع: استخدم npm run db:push أثناء التطوير لدفع تغييرات المخطط مباشرة بدون توليد ملفات ترحيل. استخدم db:generate + db:migrate لنشر الإنتاج.
الخطوة 7: بناء Server Actions آمنة الأنواع
لنبنِ الآن عمليات CRUD الأساسية باستخدام Next.js Server Actions. هذه تعمل على الخادم ويمكن استدعاؤها مباشرة من مكونات React.
إجراء إنشاء مهمة
// src/app/actions/tasks.ts
"use server";
import { db } from "@/db";
import { tasks } from "@/db/schema";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
// مخطط التحقق
const createTaskSchema = z.object({
title: z.string().min(1, "العنوان مطلوب").max(200),
description: z.string().optional(),
priority: z.enum(["low", "medium", "high", "urgent"]),
projectId: z.coerce.number().positive(),
});
export type ActionState = {
message: string;
errors?: Record<string, string[]>;
success?: boolean;
};
export async function createTask(
prevState: ActionState,
formData: FormData
): Promise<ActionState> {
const validatedFields = createTaskSchema.safeParse({
title: formData.get("title"),
description: formData.get("description"),
priority: formData.get("priority"),
projectId: formData.get("projectId"),
});
if (!validatedFields.success) {
return {
message: "فشل التحقق",
errors: validatedFields.error.flatten().fieldErrors,
};
}
try {
await db.insert(tasks).values(validatedFields.data);
revalidatePath("/dashboard");
return { message: "تم إنشاء المهمة بنجاح", success: true };
} catch (error) {
return { message: "فشل إنشاء المهمة. يرجى المحاولة مرة أخرى." };
}
}تبديل حالة إكمال المهمة
// src/app/actions/tasks.ts (تابع)
export async function toggleTask(taskId: number) {
const [task] = await db
.select({ completed: tasks.completed })
.from(tasks)
.where(eq(tasks.id, taskId));
if (!task) return;
await db
.update(tasks)
.set({
completed: !task.completed,
updatedAt: new Date(),
})
.where(eq(tasks.id, taskId));
revalidatePath("/dashboard");
}حذف مهمة
// src/app/actions/tasks.ts (تابع)
export async function deleteTask(taskId: number) {
await db.delete(tasks).where(eq(tasks.id, taskId));
revalidatePath("/dashboard");
}الخطوة 8: بناء مكون نموذج المهام
أنشئ مكون نموذج يستخدم useActionState للتكامل السلس مع server action:
// src/components/TaskForm.tsx
"use client";
import { useActionState } from "react";
import { createTask, type ActionState } from "@/app/actions/tasks";
const initialState: ActionState = { message: "" };
export function TaskForm({ projectId }: { projectId: number }) {
const [state, formAction, pending] = useActionState(createTask, initialState);
return (
<form action={formAction} className="space-y-4">
<input type="hidden" name="projectId" value={projectId} />
<div>
<label htmlFor="title" className="block text-sm font-medium">
عنوان المهمة
</label>
<input
type="text"
id="title"
name="title"
required
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
placeholder="ما الذي يجب إنجازه؟"
/>
{state.errors?.title && (
<p className="mt-1 text-sm text-red-600">{state.errors.title[0]}</p>
)}
</div>
<div>
<label htmlFor="description" className="block text-sm font-medium">
الوصف (اختياري)
</label>
<textarea
id="description"
name="description"
rows={3}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
placeholder="أضف المزيد من التفاصيل..."
/>
</div>
<div>
<label htmlFor="priority" className="block text-sm font-medium">
الأولوية
</label>
<select
id="priority"
name="priority"
defaultValue="medium"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
>
<option value="low">منخفضة</option>
<option value="medium">متوسطة</option>
<option value="high">عالية</option>
<option value="urgent">عاجلة</option>
</select>
</div>
<button
type="submit"
disabled={pending}
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{pending ? "جارٍ الإنشاء..." : "إنشاء مهمة"}
</button>
{state.message && !state.errors && (
<p
className={`text-sm ${state.success ? "text-green-600" : "text-red-600"}`}
aria-live="polite"
>
{state.message}
</p>
)}
</form>
);
}الخطوة 9: بناء لوحة التحكم مع الاستعلامات العلائقية
لنجلب الآن البيانات باستخدام واجهة الاستعلامات العلائقية القوية في Drizzle:
// src/app/dashboard/page.tsx
import { db } from "@/db";
import { TaskForm } from "@/components/TaskForm";
import { TaskList } from "@/components/TaskList";
export default async function DashboardPage() {
// استعلام علائقي: جلب المشاريع مع مهامها
const projectsWithTasks = await db.query.projects.findMany({
with: {
tasks: {
orderBy: (tasks, { desc }) => [desc(tasks.createdAt)],
},
},
orderBy: (projects, { desc }) => [desc(projects.createdAt)],
});
return (
<main className="mx-auto max-w-4xl p-8">
<h1 className="mb-8 text-3xl font-bold">مدير المهام</h1>
{projectsWithTasks.map((project) => (
<section key={project.id} className="mb-8 rounded-lg border p-6">
<h2 className="mb-4 text-xl font-semibold">{project.name}</h2>
<p className="mb-4 text-gray-600">{project.description}</p>
<TaskList tasks={project.tasks} />
<TaskForm projectId={project.id} />
</section>
))}
</main>
);
}الخطوة 10: استعلامات متقدمة مع Drizzle
يدعم Drizzle عمليات SQL المعقدة مع أمان أنواع كامل. إليك بعض الأنماط التي ستستخدمها بشكل متكرر:
استعلامات مفلترة مع WHERE
import { eq, and, or, like, desc, asc, count, sql } from "drizzle-orm";
// جلب المهام غير المكتملة ذات الأولوية العالية
const urgentTasks = await db
.select()
.from(tasks)
.where(
and(
eq(tasks.completed, false),
or(eq(tasks.priority, "high"), eq(tasks.priority, "urgent"))
)
)
.orderBy(desc(tasks.createdAt));
// البحث في المهام بالعنوان
const searchResults = await db
.select()
.from(tasks)
.where(like(tasks.title, `%${searchTerm}%`));التجميعات
// حساب المهام لكل مشروع
const taskCounts = await db
.select({
projectId: tasks.projectId,
projectName: projects.name,
total: count(),
completed: count(sql`CASE WHEN ${tasks.completed} THEN 1 END`),
})
.from(tasks)
.innerJoin(projects, eq(tasks.projectId, projects.id))
.groupBy(tasks.projectId, projects.name);المعاملات (Transactions)
// إنشاء مشروع مع مهام أولية بشكل ذري
import { db } from "@/db";
await db.transaction(async (tx) => {
const [project] = await tx
.insert(projects)
.values({ name: "مشروع جديد", userId: 1 })
.returning();
await tx.insert(tasks).values([
{ title: "إعداد المستودع", projectId: project.id, priority: "high" },
{ title: "كتابة التوثيق", projectId: project.id, priority: "medium" },
{ title: "النشر في الإنتاج", projectId: project.id, priority: "low" },
]);
});الخطوة 11: الاستكشاف باستخدام Drizzle Studio
Drizzle Studio هو واجهة رسومية مدمجة لقاعدة البيانات تتيح لك تصفح البيانات وتحريرها بصرياً:
npm run db:studioهذا يفتح واجهة ويب محلية على https://local.drizzle.studio حيث يمكنك:
- تصفح جميع الجداول والبيانات
- تعديل السجلات مباشرة
- تشغيل استعلامات SQL خام
- عرض علاقات الجداول بصرياً
الخطوة 12: ملء قاعدة البيانات ببيانات أولية
أنشئ سكريبت لملء قاعدة بياناتك ببيانات نموذجية:
// src/db/seed.ts
import { db } from "./index";
import { users, projects, tasks } from "./schema";
async function seed() {
console.log("جارٍ ملء قاعدة البيانات...");
// إنشاء مستخدم
const [user] = await db
.insert(users)
.values({ name: "أحمد محمد", email: "ahmed@example.com" })
.returning();
// إنشاء مشاريع
const [project1] = await db
.insert(projects)
.values([
{ name: "إعادة تصميم الموقع", description: "تجديد موقع الشركة", userId: user.id },
{ name: "تطبيق الهاتف", description: "بناء تطبيق React Native", userId: user.id },
])
.returning();
// إنشاء مهام
await db.insert(tasks).values([
{ title: "تصميم النماذج الأولية", priority: "high", projectId: project1.id },
{ title: "تنفيذ الصفحة الرئيسية", priority: "medium", projectId: project1.id },
{ title: "إضافة الوضع المظلم", priority: "low", projectId: project1.id },
{ title: "كتابة الاختبارات", priority: "high", projectId: project1.id },
]);
console.log("اكتمل ملء قاعدة البيانات!");
}
seed().catch(console.error);شغّله:
npm run db:seedاختبار التطبيق
شغّل خادم التطوير وتحقق من عمل كل شيء:
npm run devافتح http://localhost:3000/dashboard واختبر:
- عرض المشاريع والمهام — البيانات تُحمّل من قاعدة البيانات
- إنشاء مهمة جديدة — النموذج يُرسل عبر Server Action، والصفحة تُعاد التحقق منها
- تبديل الإكمال — مربع الاختيار يُحدّث قاعدة البيانات والواجهة
- حذف مهمة — العنصر يُزال من قاعدة البيانات والقائمة
- فحص Drizzle Studio — شغّل
npm run db:studioجنباً إلى جنب لمشاهدة التغييرات بالوقت الفعلي
استكشاف الأخطاء وإصلاحها
"Cannot find module '@/db'"
تأكد أن ملف tsconfig.json لديه المسار البديل الصحيح:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}"relation does not exist"
تحتاج لتشغيل الترحيلات أولاً. شغّل npm run db:push (للتطوير) أو npm run db:generate && npm run db:migrate لإنشاء الجداول.
"Type error: Column type mismatch"
هذا يعني عادة أن مخطط TypeScript انحرف عن قاعدة البيانات الفعلية. شغّل npm run db:generate لإنشاء ترحيل جديد يوفّق بين الاختلافات.
الخطوات التالية
لديك الآن أساس قوي لبناء تطبيقات متكاملة آمنة الأنواع مع Drizzle ORM و Next.js 15. إليك ما يمكنك فعله بعد ذلك:
- إضافة المصادقة — ادمج Better Auth أو NextAuth.js مع Drizzle كمحول قاعدة البيانات
- إضافة التحديثات الفورية — استخدم Drizzle مع WebSockets أو server-sent events
- تنفيذ واجهة متفائلة — ادمج
useOptimisticمع Server Actions للاستجابة الفورية - إضافة البحث والتصفية — استخدم عوامل
likeوilikeوالبحث النصي الكامل في Drizzle - النشر على Vercel — يعمل فوراً مع Neon PostgreSQL
- استكشف توثيق Drizzle للميزات المتقدمة
الخلاصة
يمثل Drizzle ORM فلسفة جديدة في أدوات TypeScript ORM: SQL ليس شيئاً نختبئ منه — إنه شيء نتبناه مع أمان الأنواع. على عكس أدوات ORM التي تخترع لغات استعلام خاصة بها، يرتبط Drizzle مباشرة بمفاهيم SQL التي تعرفها بالفعل مع منحك كامل قوة نظام أنواع TypeScript.
في هذا الدليل، بنيت تطبيقاً كاملاً لإدارة المهام يوضح:
- المخطط ككود — بنية قاعدة بياناتك تعيش في TypeScript
- بدون عبء تشغيل — Drizzle يُترجم إلى SQL فعال
- أمان أنواع كامل — من المخطط إلى نتيجة الاستعلام، كل نوع مُستنتج
- أنماط حديثة — Server Actions و
useActionStateوالتحقق بـ Zod - تجربة مطور ممتازة — Drizzle Studio والترحيلات وواجهة SQL أولاً
مزيج Drizzle ORM + Next.js 15 + PostgreSQL يمنحك مكدساً جاهزاً للإنتاج، خفيفاً وآمن الأنواع وممتعاً للعمل معه. ابدأ البناء.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء تطبيق فوري مع Supabase و Next.js 15: الدليل الشامل
تعلّم كيفية بناء تطبيق full-stack فوري باستخدام Supabase و Next.js 15 App Router. يغطي هذا الدليل المصادقة وإعداد قاعدة البيانات و Row Level Security والاشتراكات الفورية.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

إضافة المصادقة لتطبيق Next.js 15 باستخدام Auth.js v5: البريد الإلكتروني وOAuth والتحكم بالأدوار
تعلم كيفية إضافة نظام مصادقة جاهز للإنتاج لتطبيق Next.js 15 باستخدام Auth.js v5. يغطي هذا الدليل الشامل تسجيل الدخول عبر Google OAuth وبيانات الاعتماد بالبريد الإلكتروني وكلمة المرور والمسارات المحمية والتحكم بالوصول حسب الأدوار.