تطوير الويب

Bun + Drizzle ORM مع PostgreSQL: درس كامل 2026

4 min de lecture

Bun هو runtime JavaScript بديل لـ Node.js، أسرع 3-5× ومدمج مع package manager وbundler. Drizzle ORM هو TypeScript-first ORM يفوز بسرعته وحجمه الصغير على Prisma. دمجهما مع PostgreSQL ينتج stack حديث جداً للـ APIs والتطبيقات في 2026. هذا الدرس يبني نظاماً كاملاً في 8 خطوات.

المتطلبات

  • Linux/macOS (Bun لا يدعم Windows أصلياً، يحتاج WSL)
  • PostgreSQL متاح (محلي عبر Docker، أو Cloud)
  • أساسيات TypeScript
  • الوقت المقدر: ساعة ونصف

الخطوة 1 — تثبيت Bun

# Linux/macOS:
curl -fsSL https://bun.sh/install | bash

# تحقق:
bun --version  # → 1.1+

# WSL (Windows):
wsl curl -fsSL https://bun.sh/install | bash

Bun 1.1+ (مايو 2024) هو الحد الأدنى المستقر. النسخ الأحدث (1.2+ في 2026) تجلب ميزات Bun الجديدة: Bun Test، Bun:sqlite، Bun.serve محسّن. Bun يحلّ محل: Node + npm + pnpm + esbuild + ts-node، كل ذلك في binary واحد بـ 50 MB.

الخطوة 2 — مشروع جديد

mkdir my-api && cd my-api
bun init
# اختر TypeScript template

bun add drizzle-orm postgres
bun add -d drizzle-kit @types/pg
bun add hono   # web framework خفيف، يعمل بسلاسة مع Bun

Hono هو web framework صغير (12 KB) وسريع، صُمم للـ runtimes الحديثة (Bun، Deno، Cloudflare Workers). أسرع من Express بـ 2-3×. مع Bun، يعطي أحد أسرع HTTP servers على الإطلاق: 200K+ req/sec على VPS متوسط. للـ APIs الحرجة، هذا stack يلغي الحاجة لـ Go أو Rust.

الخطوة 3 — Schema بـ Drizzle

Drizzle يستخدم TypeScript للتعريف، ينتج SQL آلياً، يحافظ على type safety كاملة من قاعدة البيانات إلى الـ API.

// src/db/schema.ts
import { pgTable, serial, text, timestamp, boolean, bigint } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id:        serial("id").primaryKey(),
  email:     text("email").notNull().unique(),
  name:      text("name").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

export const posts = pgTable("posts", {
  id:         bigint("id", { mode: "number" }).generatedAlwaysAsIdentity().primaryKey(),
  authorId:   serial("author_id").references(() => users.id),
  title:      text("title").notNull(),
  content:    text("content"),
  published:  boolean("published").default(false),
  publishedAt: timestamp("published_at"),
});

الفائدة الكبرى: typeof users.$inferSelect يعطيك الـ TypeScript type تلقائياً. لا حاجة لـ generated code، كل شيء type-safe في الوقت الفعلي. تغيير العمود في schema.ts → الـ types في كل الـ codebase تحدّث فوراً.

الخطوة 4 — Migrations

Drizzle Kit يولّد ملفات SQL migration من تغييرات الـ schema. لا تكتب SQL يدوياً.

// drizzle.config.ts
import type { Config } from "drizzle-kit";
export default {
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: { url: process.env.DATABASE_URL! },
} satisfies Config;

# أوامر:
bun drizzle-kit generate    # ينشئ migration من تغيير الـ schema
bun drizzle-kit migrate     # يطبّق migrations على قاعدة البيانات
bun drizzle-kit studio      # GUI لتصفح القاعدة (drizzle studio)
bun drizzle-kit push        # للتطوير: يحدّث القاعدة بدون migration

Drizzle Studio (مثل Prisma Studio لكن أخف) يفتح GUI في المتصفح لتصفح وتعديل البيانات. مفيد جداً للتطوير. للإنتاج، استخدم migrations دائماً (لا push) — تركز التغييرات في git، تطبّق على staging قبل production.

الخطوة 5 — Queries

// src/db/index.ts
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 });

// queries مع type safety كاملة:
import { eq, and, gt } from "drizzle-orm";
import { db } from "./db";
import { users, posts } from "./db/schema";

// جلب مستخدم
const user = await db.select().from(users).where(eq(users.id, 1));
// user[0] type: { id, email, name, createdAt }

// JOIN مع where مركّب:
const publishedPosts = await db
  .select({ title: posts.title, authorName: users.name })
  .from(posts)
  .innerJoin(users, eq(posts.authorId, users.id))
  .where(and(
    eq(posts.published, true),
    gt(posts.publishedAt, new Date("2026-01-01"))
  ));

Drizzle queries تشبه SQL أكثر من ORM التقليدي. هذا قصد: من يعرف SQL، يكتب Drizzle بسرعة. الـ type inference يعمل في كل مكان: publishedPosts[0].title و publishedPosts[0].authorName موصوفان من الـ select. أخطاء الكتابة تُكشف في الـ compile time.

الخطوة 6 — API بـ Hono

// src/index.ts
import { Hono } from "hono";
import { db } from "./db";
import { users } from "./db/schema";
import { eq } from "drizzle-orm";

const app = new Hono();

app.get("/", (c) => c.json({ status: "ok" }));

app.get("/users/:id", async (c) => {
  const id = parseInt(c.req.param("id"));
  const user = await db.select().from(users).where(eq(users.id, id));
  if (!user.length) return c.json({ error: "not found" }, 404);
  return c.json(user[0]);
});

app.post("/users", async (c) => {
  const body = await c.req.json<{ email: string; name: string }>();
  const [created] = await db.insert(users).values(body).returning();
  return c.json(created, 201);
});

export default { port: 3000, fetch: app.fetch };

تشغيل: bun run src/index.ts. السيرفر يبدأ على :3000. Hono + Bun = سريع جداً (5-10× أسرع من Express + Node). للـ production، أضف middleware: CORS، rate limiting (Hono يدعم نشط)، logging، error handling. Hono Docs بـ 400+ مثال.

الخطوة 7 — Validation وZod

أمن أساسي: لا تثق ببيانات المستخدم. Zod مع Hono يفلتر الـ body قبل وصوله لقاعدة البيانات.

bun add zod @hono/zod-validator
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";

const userSchema = z.object({
  email: z.string().email(),
  name:  z.string().min(2).max(100),
});

app.post(
  "/users",
  zValidator("json", userSchema),
  async (c) => {
    const body = c.req.valid("json");  // type-safe الآن
    const [created] = await db.insert(users).values(body).returning();
    return c.json(created, 201);
  }
);

// طلب بإيميل غير صحيح → response 400 تلقائياً
// مع تفاصيل الأخطاء بصيغة JSON

Zod يحلّ مشكلة validation برشاقة: تعرّف الـ schema مرة، تحصل على validation + TypeScript types. الـ schema يصير « single source of truth » — تغيير حقل في الـ schema، الـ validation و الـ types يتحدّثان تلقائياً. هذا الـ pattern (DDD، single source) يقلّل bugs بشكل ملحوظ.

الخطوة 8 — النشر للإنتاج

المنصة السعر الميزة
VPS + Coolify 5-20 USD تحكم كامل
Fly.io 0-25 USD multi-region، scaling
Railway 5+ USD سهل، CD/CI مدمج
Cloudflare Workers 0-5 USD/M req edge compute عالمي
# Dockerfile لـ Bun:
FROM oven/bun:1.2-alpine AS base
WORKDIR /app

FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

FROM base AS builder
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN bun run build

FROM oven/bun:1.2-alpine AS runner
COPY --from=builder /app/dist ./dist
CMD ["bun", "run", "dist/index.js"]
EXPOSE 3000

image size بـ Bun ~ 100 MB، أصغر بـ 3× من Node. سرعة الإقلاع: 200 ms مقابل 500-800 ms لـ Node. هذا مهم في الـ serverless حيث cold starts كثيرة. للـ Cloudflare Workers، استخدم hono مباشرة بدون Bun (Cloudflare يستخدم runtime خاص).

أخطاء شائعة

المشكلة السبب الحل
Bun يفشل على Windows دعم Native غير مكتمل استخدم WSL2
Drizzle types خاطئة tsconfig قديم strict: true إلزامي
Migrations معلّقة oid في drizzle.config مفقود أعد تشغيل drizzle-kit
Connection pool ينفجر اتصال جديد كل request استخدم postgres-js مع max=10
« Bun is not Node » module يتطلب Node APIs تحقق من توافق Bun
Hono routes لا تعمل ترتيب middleware middleware قبل routes

للمزيد

مقالات ذات صلة

Partager