ORM ولا SQL: query builders و repositories و migrations و transactions

جارٍ التحميل…

01 / ملخص الدرس

ORM ولا SQL: query builders و repositories و migrations و transactions

استخدمنا ORM علشان نخلص بسرعة، وكان كله سحر لحد الإنتاج: صفحة الـ orders بتعمل واحد وعشرين query، وخمسة اشتروا آخر قطعة، والـ sort اتعمل بـ raw query فيها injection، والـ schema بتتغير لوحدها وقت الـ start. بنفهم الطيف كله: الـ driver، والـ query builder، والـ ORM بنوعيه active record و data mapper، وإيه اللي كل واحد بيكسبك وبيخبّيه. والـ N plus one والـ lazy loading، وإزاي تشوف الـ SQL الحقيقي. والـ migrations المتسجلة بـ checksums و advisory lock.

02 / شرح الدرس مكتوبًا

نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.

أهلًا بيك. أول ما بنبدأ project، السؤال بيبقى: أستخدم ORM ولا أكتب SQL؟ ناس كتير بتختار ORM علشان تخلص بسرعة، وده مش غلط. المشكلة لما تستخدمه من غير ما تعرف بيعمل إيه من تحت. بص على orders dot naive dot js. findAll وبعدين getItems لكل order. شكلها code بريء، بس دي query لكل order. عشرين order، واحد وعشرين query. الـ ORM ما عرفش يعمل الـ sort، فاتكتب raw query بـ string، والـ sort جاي من الـ user، يعني SQL injection. الـ stock: read، وبعدين check، وبعدين save، من غير transaction ولا lock. خمسة قروا إن فيه قطعة، والخمسة اشتروها. الـ total جاي من الـ client. save بيكتب كل الـ columns، فتعديل حد تاني بيضيع. و sync alter وقت الـ start: الـ schema بتتغير لوحدها في الإنتاج، ومن غير ما حد يراجع، ومن غير history.

خلينا نشوف الطيف كله. أول حاجة: الـ driver، زي pg. بتكتب SQL، وبتاخد rows. كل حاجة واضحة، بس الـ mapping والـ dynamic queries عليك. التانية: query builder، زي Knex و Kysely. بتبني الـ SQL بـ functions، فالـ dynamic filters أسهل والـ values دايمًا parameters. و Kysely كمان بيدّيك types من الـ schema. التالتة: ORM. وده نوعين. active record، زي Sequelize: الـ object نفسه بيعرف يحفظ نفسه، order dot save. و data mapper، زي Prisma و TypeORM و Drizzle: الـ objects عادية، وفيه حاجة تانية بتعمل الـ queries. والقاعدة: كل ما تطلع لفوق، بتكتب أقل، بس بتشوف أقل. والـ ORM بيكسبك الـ types والـ relations والـ migrations والسرعة في الـ CRUD. وبيخبّي عنك الـ queries، والـ locks، وإمتى بيعمل query أصلًا.

أكبر مشكلة بيخبّيها الـ ORM: الـ N plus one. الـ lazy loading معناه إن الـ relation بتتجاب لما تلمسها. فـ loop على عشرين order، وكل واحد بيلمس items، عشرين query. على الـ laptop بتاخد ms، وفي الإنتاج والـ DB على network، بتاخد تواني. الحل: eager loading. في Prisma بتقول include، وفي Sequelize include برضه. وهو بيعمل join أو query تانية بـ IN. أو في SQL انت، join و json_agg، فالـ items ترجع جُوّه الـ order في query واحدة. والأهم: فعّل الـ query logging في الـ development، وبص على عدد الـ queries لكل request. وفي الـ tests، ممكن تعمل assert على العدد ده، زي ما هنعمل. ولو الـ ORM عمل query غريبة، الـ escape hatch بتاعه بيسمحلك تكتب SQL، بس بالـ parameters، مش بـ string. طب لو هتكتب SQL، إزاي تكتبه آمن ومن غير ما تتعب؟ الـ tagged template. بتكتب sql وبعدها backtick، وأي حاجة جُوّه dollar braces ما بتبقاش جزء من الـ SQL أبدًا، بتبقى placeholder، والـ value بتروح لوحدها. فالـ injection مستحيلة بالتصميم، مش بالحرص. والـ fragments: تبني الـ WHERE من أجزاء، كل جزء ليه values، والـ placeholders بتترقّم لوحدها. وأسماء الـ columns ما ينفعش تبقى parameters، فبنعمل sql dot id، بيعمل quote وبيرفض أي اسم غريب. والـ sort من الـ user بيعدّي على allowlist الأول، والـ user بيختار اسم، مش column. وكمان الـ db بيرفض أي string. مفيش طريقة تبعت SQL من غير الـ tag. وده تقريبًا اللي بتعمله libraries زي postgres dot js و slonik.

الـ migrations. الـ schema بتاعتك code، ولازم يبقى ليها history زي الـ code. كل migration ليها id، ومتسجلة في table اسمه schema_migrations. فكل environment عارف هو فين. ومعاها checksum. لو حد عدّل migration بعد ما اشتغلت، الـ checksum مش هيطابق، والـ migrate يقف. ليه؟ لأن الـ production عنده النسخة القديمة، والـ development عنده الجديدة، والاتنين مختلفين ومحدش واخد باله. اللي اشتغلت ما بتتعدّلش. اعمل migration جديدة. وكل migration في transaction. وقعت في النص؟ ترجع كلها. و Postgres بيسمح بـ DDL جُوّه transaction، وده مش موجود في كل الـ databases. و advisory lock: عشر pods قاموا مع بعض وكلهم عايزين يعملوا migrate. الـ lock بيخلّي واحد بس يشتغل، والباقيين يستنوا ويلاقوا كل حاجة اتعملت. و Prisma migrate و Knex و Flyway كلهم بيعملوا ده. والـ migrations الكبيرة على tables فيها data كتير ليها درس لوحدها: expand و contract، وده في قسم الجاهزية الإنتاجية.

الـ transactions والـ concurrency. transaction: كل الـ queries جُوّاها يا كلها تحصل يا ولا واحدة. والمهم إنها كلها على نفس الـ connection. لو عملت BEGIN على connection و INSERT على connection تانية من الـ pool، مفيش transaction أصلًا. علشان كده tx بياخد client ويدّيه للـ function. و tx جُوّه tx: Postgres ما عندوش nested transactions، فبنستخدم SAVEPOINT. الجُوّاني يقع، يرجع لحد الـ savepoint بس. والـ stock: SELECT FOR UPDATE بيقفل الـ rows لحد آخر الـ transaction. فالتاني بيستنى، ولما يقرا، يلاقي الـ stock الجديد. وبنقفل بالـ sku بالترتيب، علشان اتنين ما يقفلوش بالعكس ويعملوا deadlock. ولما ما ينفعش تقفل، زي form الـ user فاتحه بقاله ربع ساعة، optimistic locking: column اسمه version، والـ UPDATE بيشترط الـ version القديم. لو حد سبقك، صفر rows، و CONFLICT، والـ user يشوف التعديل الجديد. وفيه كمان isolation levels: الـ default read committed، و serializable أقوى بس ممكن يرجّع serialization failure ولازم تعمل retry.

طب نختار إيه؟ وفين نحط الـ SQL؟ الـ architecture: الـ SQL كله في repository، في مكان واحد. الـ routes والـ services بيكلّموا repo dot placeOrder، ما بيعرفوش يعني إيه table. والـ repo بيرجّع objects بالشكل بتاع الـ domain: camelCase و numbers و Dates، زي ما عملنا في الـ boundaries في الدرس اللي فات. ولو غيّرت من ORM لـ SQL أو العكس، بتغيّر الـ repo بس. والاختيار: CRUD كتير و relations و team بيتغير؟ ORM زي Prisma أو Drizzle، وده معقول أوي. reports و queries تقيلة و performance حرجة؟ SQL أو query builder. وأغلب الـ projects الكبيرة بتستخدم الاتنين: ORM للـ CRUD، و SQL للـ queries المهمة. وأيًا كان اختيارك: شوف الـ SQL اللي بيتعمل، واعرف فين الـ transactions، وما تعتمدش على السحر. التحدي. عندك schema dot js فيه الـ migrations والـ seed، و lab dot js بيقارن الـ lazy loading بالـ listOrders، وخمسة بيشتروا آخر قطعة، والـ sort من الـ user، والـ lost update. والمطلوب data dot js. كل التفاصيل في الكومنت: الـ sql tag والـ fragments و sql dot id و sql dot join، و createDb بالـ query و one و maybeOne والـ stats، و tx بالـ savepoints، و migrate بالـ checksums والـ advisory lock، و createRepo فيه listOrders و placeOrder و renameCustomer و search. الـ test بيشتغل على Postgres حقيقي. اقرا الكومنت كويس، ونشغّل test dot js لحد ما تبقى أربعتاشر من أربعتاشر.

تمرين

اكتب data.js: الـ sql tag بالـ fragments والـ placeholders، و sql.id و sql.join، و createDb بـ query و one و maybeOne و stats و tx بالـ savepoints، و migrate بالـ schema_migrations والـ checksums والـ advisory lock، و createRepo بـ listOrders (query واحدة) و placeOrder (FOR UPDATE) و renameCustomer (version) و search (allowlist). كل التفاصيل في الكومنت، وشغّل node test.js.

الحل. Sql فيه الـ parts والـ values، و toQuery بيمشي عليهم، والـ fragment بيترقّم من مكانه. الـ db: api واحدة، مرة على الـ pool ومرة على الـ client جُوّه الـ tx، و tx جُوّه tx بيبقى savepoint. migrate: lock، وبعدين table، وبعدين loop بالـ checksums. placeOrder: FOR UPDATE بالترتيب، والـ checks، والأسعار من الـ DB، و unnest علشان الـ items والـ stock كل واحد في query واحدة. نشغّل lab dot js.

data.js
// data.js: data layer فوق pg: sql tagged template آمن، و db بالـ transactions والـ savepoints، و migrations بـ checksums و advisory lock، و repository بـ SQL واضح
// lab.js و test.js بيعملوا import من هنا، والـ schema في schema.js
import pg from "pg";
import { createHash } from "node:crypto";

export class DataError extends Error {
  constructor(code, message = code) {
    super(message);
    this.code = code;
  }
}

// ———— 1) sql ————
class Sql {
  constructor(parts, values) {
    this.parts = parts; // strings، وبينهم values أو Sql
    this.values = values;
  }
  toQuery(start = 1) {
    let text = "";
    const values = [];
    this.parts.forEach((p, i) => {
      text += p;
      if (i >= this.values.length) return;
      const v = this.values[i];
      if (v instanceof Sql) {
        const q = v.toQuery(start + values.length);
        text += q.text;
        values.push(...q.values);
      } else {
        values.push(v);
        text += `$${start + values.length - 1}`;
      }
    });
    return { text, values };
  }
  get text() { return this.toQuery().text; }
}

export function sql(strings, ...values) {
  return new Sql([...strings], values);
}
sql.id = (...names) => {
  for (const n of names) if (!/^[a-z_][a-z0-9_]*$/.test(n)) throw new DataError("BAD_IDENTIFIER", `Bad identifier: ${n}`);
  return new Sql([names.map((n) => `"${n}"`).join(".")], []);
};
sql.join = (items, sep = ", ") => {
  if (!items.length) return new Sql([""], []);
  return new Sql([...items.map((_, i) => (i ? sep : "")), ""], items);
};
sql.empty = new Sql([""], []);

// ———— 2) db ————
function api(run, stats) {
  const query = async (q) => {
    if (!(q instanceof Sql)) throw new DataError("NOT_SQL", "Use the sql tag");
    stats.queries++;
    return (await run(q.toQuery())).rows;
  };
  const maybeOne = async (q) => {
    const rows = await query(q);
    if (rows.length > 1) throw new DataError("TOO_MANY_ROWS");
    return rows[0] ?? null;
  };
  const one = async (q) => {
    const row = await maybeOne(q);
    if (!row) throw new DataError("NOT_FOUND");
    return row;
  };
  return { query, one, maybeOne };
}

export function createDb({ connectionString = process.env.DATABASE_URL, max = 10, onQuery } = {}) {
  const pool = new pg.Pool({ connectionString, max });
  const stats = { queries: 0 };
  const exec = (c) => (q) => { onQuery?.(q); return c.query(q); };

  function txApi(client, depth) {
    const t = api(exec(client), stats);
    t.tx = async (fn) => {
      const sp = `sp_${depth}`;
      await client.query(`SAVEPOINT ${sp}`);
      try {
        const r = await fn(txApi(client, depth + 1));
        await client.query(`RELEASE SAVEPOINT ${sp}`);
        return r;
      } catch (e) {
        await client.query(`ROLLBACK TO SAVEPOINT ${sp}`);
        throw e;
      }
    };
    return t;
  }

  const db = api(exec(pool), stats);
  db.stats = stats;
  db.tx = async (fn) => {
    const client = await pool.connect();
    try {
      await client.query("BEGI
…

المخرجات

$ node lab.js
— 1) migrations —
  أول مرة: [ '001_customers_products', '002_orders' ]
  أربع pods مع بعض: [[],[],[],[]]

— 2) N+1 —
  lazy loading: 21 queries في 6ms
  listOrders: 1 query في 2ms ← أول order: {"id":30,"customerId":1,"status":"pending","totalCents":15000,"createdAt":"2026-10-17T14:47:11.673Z","items":[{"sku":"MUG-1","qty":1,"priceCents":15000}]}

— 3) آخر قطعة، وخمسة بيشتروا مع بعض —
  read-modify-write: OK OK OK OK OK ← خمسة اشتروا قطعة واحدة
  placeOrder بـ FOR UPDATE: OUT_OF_STOCK OK OUT_OF_STOCK OUT_OF_STOCK OUT_OF_STOCK ، والـ stock: 0

— 4) الـ sort من الـ query —
  الـ string اللي كانت هتتحط في الـ SQL: SELECT * FROM orders ORDER BY total_cents; DROP TABLE orders
  search: BAD_SORT
  sort=total: 99000 , 50000 , 50000

— 5) lost update —
  التاني: CONFLICT ← بدل ما يمسح تعديل الأول

الـ migrations: اشتغلت مرة، والأربع pods ما لقوش حاجة. الـ N plus one: واحد وعشرين query قصاد query واحدة، بنفس الـ data. آخر قطعة: من غير lock، الخمسة اشتروا. وبـ FOR UPDATE، واحد بس، والـ stock صفر. الـ sort: الـ string البايز اتقفل بـ BAD_SORT، والـ sort الصح اشتغل. والـ lost update بقى CONFLICT. ونشغّل test dot js.

data.js

المخرجات

$ node test.js
✅ sql: كل value بقت placeholder بالترتيب، والـ fragment اللي جُوّه اترقّم من جديد {"text":"SELECT * FROM orders WHERE customer_id = $1 AND status = $2 AND total_cents > $3 LIMIT $4","values":[7,"paid",100,5]}
✅ sql.id بيعمل quote وبيرفض أي اسم غريب، و sql.join بالـ separator، و join فاضي {"j":{"text":"SELECT \"orders\".\"total_cents\" FROM t WHERE a = $1 OR b = $2 OR c = $3","values":[1,2,3]},"bad":["BAD_IDENTIFIER","BAD_IDENTIFIER","BAD_IDENTIFIER","BAD_IDENTIFIER"]}
✅ migrate: اتنفذوا بالترتيب واتسجلوا بـ checksum، والمرة التانية ما عملتش حاجة {"a1":["001_customers_products","002_orders"],"a2":[],"recorded":["001_customers_products:64","002_orders:64"]}
✅ الـ values عمرها ما بتبقى SQL: ' OR 1=1 رجّع صفر rows، و query من غير الـ sql tag ← NOT_SQL {"notSql":"NOT_SQL"}
✅ one و maybeOne: واحد ← الـ row، صفر ← null أو NOT_FOUND، أكتر من واحد ← TOO_MANY_ROWS، و stats.queries بيعدّ {"sara":{"id":1,"name":"سارة"},"c4":["NOT_FOUND","TOO_MANY_ROWS"]}
✅ tx: error جُوّه ← rollback والـ error بيطلع زي ما هو، ومن غير error ← commit {"c5":"BOOM","left":["[email protected]"]}
✅ tx جُوّه tx ← savepoint: الجُوّاني وقع ورجع لورا لوحده، والبرّاني عمل commit {"r6":"OTHER 23505 duplicate key 
…

كله نجح. في الدرس الجاي: serverless. الـ function بتقوم وتنام، فإيه اللي بيحصل للـ connections والـ cache والـ timers؟ الـ cold starts، والـ execution context، والـ connection pooling مع الـ databases، والـ timeouts، والـ idempotency.

orders.naive.js
// orders.naive.js: ORM "سحري"، وكله كان شغّال على الـ laptop بعشر orders
const orders = await Order.findAll({ where: { customerId } });          // query
for (const o of orders) o.items = await o.getItems();                   // query لكل order ← N+1، ومحدش شايفه

app.get("/orders", async (req, res) => {
  // الـ ORM ما بيعرفش يعمل الـ sort ده، فـ raw query بـ string
  res.json(await sequelize.query(`SELECT * FROM orders ORDER BY ${req.query.sort}`));
});

app.post("/orders", async (req, res) => {
  const p = await Product.findByPk(req.body.sku);
  if (p.stock >= req.body.qty) {             // اتنين قروا stock واحد في نفس الوقت
    p.stock -= req.body.qty;
    await p.save();                          // من غير transaction ولا lock ← الاتنين اشتروا آخر قطعة
  }
  await Order.create({ customerId, total: req.body.total }); // الـ total من الـ client
});

// customer.save() بيكتب كل الـ columns، فتعديل حد تاني في نفس الوقت بيضيع (lost update)
// sequelize.sync({ alter: true }) وقت الـ start في الإنتاج ← الـ schema بتتغير لوحدها، ومن غير history
schema.js
// schema.js: الـ migrations والـ seed. الـ test والـ lab بيستخدموهم، وانت ما تعدّلش فيهم
export const MIGRATIONS = [
  {
    id: "001_customers_products",
    up: `CREATE TABLE customers (id serial PRIMARY KEY, name text NOT NULL, email text NOT NULL UNIQUE, version int NOT NULL DEFAULT 1);
         CREATE TABLE products (sku text PRIMARY KEY, name text NOT NULL, price_cents int NOT NULL CHECK (price_cents >= 0), stock int NOT NULL CHECK (stock >= 0));`,
  },
  {
    id: "002_orders",
    up: `CREATE TABLE orders (id serial PRIMARY KEY, customer_id int NOT NULL REFERENCES customers(id), status text NOT NULL DEFAULT 'pending',
           total_cents bigint NOT NULL, created_at timestamptz NOT NULL DEFAULT now());
         CREATE TABLE order_items (order_id int NOT NULL REFERENCES orders(id), sku text NOT NULL REFERENCES products(sku), qty int NOT NULL CHECK (qty > 0),
           price_cents int NOT NULL, PRIMARY KEY (order_id, sku));
         CREATE INDEX orders_customer_created ON orders (customer_id, created_at DESC);`,
  },
];

export async function seed(db, sql) {
  await db.query(sql`INSERT INTO customers (name, email) VALUES ('سارة', '[email protected]'), ('عمر', '[email protected]'), ('منى', '[email protected]')`);
  await db.query(sql`INSERT INTO products (sku, name, price_cents, stock) VALUES
    ('MUG-1', 'مج', 15000, 50), ('TSHIRT-M', 'تيشيرت', 35000, 20), ('CAP-1', 'كاب', 12000, 3), ('LAST-1', 'آخر قطعة', 99000, 1)`);
}
lab.js
// lab.js: نفس الشغل بطريقة الـ ORM الساذجة، وبالـ data layer
import { sql, createDb, migrate, createRepo } from "./data.js";
import { MIGRATIONS, seed } from "./schema.js";

const db = createDb();
await db.query(sql`DROP SCHEMA public CASCADE`);
await db.query(sql`CREATE SCHEMA public`);
console.log("— 1) migrations —");
console.log("  أول مرة:", await migrate(db, MIGRATIONS));
console.log("  أربع pods مع بعض:", JSON.stringify(await Promise.all([1, 2, 3, 4].map(() => migrate(db, MIGRATIONS)))));
await seed(db, sql);
const repo = createRepo(db);
for (let i = 0; i < 30; i++) await repo.placeOrder({ customerId: 1, items: [{ sku: "MUG-1", qty: 1 }, ...(i % 3 ? [] : [{ sku: "TSHIRT-M", qty: 1 }])] });

console.log("\n— 2) N+1 —");
let q0 = db.stats.queries, t0 = Date.now();
const orders = await db.query(sql`SELECT * FROM orders WHERE customer_id = ${1} ORDER BY created_at DESC LIMIT ${20}`);
for (const o of orders) o.items = await db.query(sql`SELECT * FROM order_items WHERE order_id = ${o.id}`);
console.log(`  lazy loading: ${db.stats.queries - q0} queries في ${Date.now() - t0}ms`);
q0 = db.stats.queries; t0 = Date.now();
const list = await repo.listOrders({ customerId: 1, limit: 20 });
console.log(`  listOrders: ${db.stats.queries - q0} query في ${Date.now() - t0}ms ← أول order:`, JSON.stringify(list[0]));

console.log("\n— 3) آخر قطعة، وخمسة بيشتروا مع بعض —");
const naiveBuy = async () => {
  const p = await db.one(sql`SELECT stock FROM products WHERE sku = ${"LAST-1"}`);
  await new Promise((r) => setTimeout(r, 20)); // الـ request بيعمل حاجات تانية
  if (p.stock >= 1) { await db.query(sql`UPDATE products SET stock = ${p.stock - 1} WHERE sku = ${"LAST-1"}`); return "OK"; }
  return "OUT_OF_STOCK";
};
console.log("  read-modify-write:", (await Promise.all([1, 2, 3, 4, 5].map(naiveBuy))).join(" "), "← خمسة اشتروا قطعة واحدة");
await db.query(sql`UPDATE products SET stock = 1 WHERE sku = ${"LAST-1"}`);
const tx = await Promise.all([1, 2, 3, 4, 5].map((c) => repo.placeOrder({ customerId: (c % 3) + 1, items: [{ sku: "LAST-1", qty: 1 }] }).then(() => "OK", (e) => e.code)));
console.log("  placeOrder بـ FOR UPDATE:", tx.join(" "), "، والـ stock:", (await db.one(sql`SELECT stock FROM products WHERE sku = ${"LAST-1"}`)).stock);

console.log("\n— 4) الـ sort من الـ query —");
const evil = "total_cents; DROP TABLE orders";
console.log("  الـ string اللي كانت هتتحط في الـ SQL:", `SELECT * FROM orders ORDER BY ${evil}`);
try { await repo.search({ sort: evil }); } catch (e) { console.log("  search:", e.code); }
const top = await repo.search({ sort: "total", dir: "desc", limit: 3 });
console.log("  sort=total:", top.map((o) => o.totalCents).join(" , "));

console.log("\n— 5) lost update —");
await repo.renameCustomer(2, "عمر خالد", 1);
try { await repo.renameCustomer(2, "عمر من tab قديم", 1); } catch (e) { console.log("  التاني:", e.code, "← بدل ما يمسح تعديل الأول"); }
await db.close();
03 / دروس المسار (122)
  1. 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
  2. 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
  3. 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
  4. 04القيم والمتغيرات والأنواع: const و let و typeof
  5. 05القرارات والتكرار: if و === والـ truthy والـ loops
  6. 06الـ functions: parameters و return والـ arrow functions والـ scope
  7. 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
  8. 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
  9. 09الـ callbacks والـ closures وأول لمسة async: setTimeout
  10. 10الـ classes والـ errors: throw و try/catch و stack trace
  11. 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
  12. 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
  13. 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
  14. 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
  15. 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
  16. 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
  17. 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
  18. 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
  19. 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
  20. 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
  21. 21الـ Node test runner والـ debugging: tests من غير مكتبات
  22. 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
  23. 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
  24. 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
  25. 25الـ Middleware ودورة حياة الـ request جُوّه Express
  26. 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
  27. 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
  28. 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
  29. 29رفع الملفات وحدود الحجم: multer من غير ثغرات
  30. 30الـ Realtime: Server-Sent Events و WebSockets
  31. 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
  32. 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
  33. 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
  34. 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
  35. 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
  36. 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
  37. 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
  38. 38Partial و Covering indexes: indexes أصغر وأسرع
  39. 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
  40. 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
  41. 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
  42. 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
  43. 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
  44. 44الـ Locks والـ Deadlocks: مين مستني مين
  45. 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
  46. 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
  47. 47Migrations من غير downtime: expand و backfill و contract
  48. 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
  49. 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر
  50. 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
  51. 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
  52. 52Cache-aside والـ TTL: Redis قدام الـ database
  53. 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
  54. 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
  55. 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
  56. 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
  57. 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
  58. 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
  59. 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
  60. 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
  61. 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
  62. 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
  63. 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
  64. 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
  65. 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
  66. 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
  67. 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
  68. 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
  69. 69الـ MFA والـ recovery: التاني factor اللي بجد
  70. 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
  71. 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
  72. 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
  73. 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
  74. 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
  75. 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
  76. 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع
  77. 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
  78. 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
  79. 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
  80. 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
  81. 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
  82. 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
  83. 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
  84. 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
  85. 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
  86. 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
  87. 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
  88. 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
  89. 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
  90. 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
  91. 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
  92. 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
  93. 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
  94. 94Docker Volumes: الـ data بتتحفَظ فين
  95. 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
  96. 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
  97. 97Reverse Proxy و TLS: اللي واقف قدام الـ app
  98. 98Multi-arch Images والـ Native Modules: اشتغلت عندي
  99. 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
  100. 100Kubernetes من الأول: Pods و Deployments و Services و Probes
  101. 101Modular Monolith و DDD: حدود واضحة من غير network
  102. 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
  103. 103Sagas: عملية واحدة على تلات services من غير transaction واحدة
  104. 104CQRS و Event Sourcing: الـ events هي الحقيقة
  105. 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
  106. 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
  107. 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
  108. 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
  109. 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
  110. 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
  111. 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
  112. 112ORM ولا SQL: query builders و repositories و migrations و transactionsانت هنا
  113. 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
  114. 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
  115. 115Contract Testing و Property-Based Testing بـ fast-check
  116. 116الـ Payments: idempotency و webhooks و refunds و reconciliation
  117. 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
  118. 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
  119. 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
  120. 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
  121. 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
  122. 122Incident Runbooks: من الـ alert للـ postmortem