جديد 📢 عرض الإطلاق لفترة محدودة: خصم حتى 70% على شهرك الأول في كل لعبة.عرض الباقات

كيفية استخدام Prisma مع MySQL في بوت ديسكورد

استضافة بوتات DiscordUpdated Nov 6, 20269 min read

الجواب المختصر. استخدم Prisma 7 مع محوّل المشغّل @prisma/adapter-mariadb الذي توثّقه Prisma لـ MySQL وMariaDB. على PlayNHost لا يمكن تشغيل prisma migrate dev على قاعدة بيانات بوتك، لأنه يحتاج إلى إنشاء قاعدة بيانات "shadow" مؤقتة، ومستخدمو قواعد البيانات هنا لا يستطيعون إنشاء قواعد بيانات. اكتب الترحيل الأول بدلًا من ذلك باستخدام prisma migrate diff الذي لا يحتاج إلى أي قاعدة بيانات، واترك سكربت postinstall يشغّل prisma generate وprisma migrate deploy على الخادم. تتضمن كل خطة بوت قاعدة بيانات MySQL واحدة. اختبرنا الكود والأوامر أدناه في 6 نوفمبر 2026 مع Prisma 7.10.1 وdiscord.js 14 وNode.js 22، على MariaDB 10.11 بحساب قاعدة بيانات مُعدّ مثل حسابنا (بلا صلاحية إنشاء قواعد بيانات، و20 اتصالًا، وحد استعلام 120 ثانية)، لكن ليس بتسجيل دخول حقيقي إلى ديسكورد.

Prisma 7 وليس أحدث إصدار على npm

في 6 نوفمبر 2026 تشير الوسم latest لحزمة prisma على npm إلى نسخة مرشحة للإصدار من Prisma 8 (8.0.0-rc.30)، وبدء MySQL السريع الخاص بـ Prisma هو الخاص بـ Prisma 7: فهو يثبّت prisma@prev و@prisma/client@7. لذلك يثبّت هذا الدليل كل حزم Prisma على ^7. أمر npm install prisma العادي سيعطيك الإصدار 8.

ما تحتاج إليه

  • بوت discord.js مكتوب بلغة TypeScript. يشغّل نوع Node.js Bot لدينا ملفًا رئيسيًا ينتهي بـ .ts باستخدام tsx، فلا حاجة إلى ترجمة أي شيء مسبقًا: كيف تشغّل بوت Discord بلغة TypeScript.
  • خطة بوت من PlayNHost بنوع Node.js Bot. تتضمن كل خطة قاعدة بيانات واحدة.
  • Node.js وnpm على حاسوبك الخاص، لكتابة الترحيل مرة واحدة.

1. أنشئ قاعدة البيانات والمتغيرات

  1. افتح خادم بوتك في اللوحة، وانقر على قواعد البيانات، ثم على قاعدة بيانات جديدة وإنشاء قاعدة بيانات.
  2. تفتح صفحة قاعدة البيانات الجديدة (لاحقًا انقر عليها ضمن قواعد بياناتك). أبقها مفتوحة، واختر تحت إعداد جاهز العنصر Generic URL في إعداد لـ: إنه سلسلة الاتصال mysql://... التي يحتاج إليها سطر أوامر Prisma.
  3. افتح صفحة بوت، ثم لوحة البيئة، وانقر على لصق ملف .env والصق ستة أسطر بقيمك. في DATABASE_URL الصق Generic URL تمامًا كما يظهر: قد تحتوي كلمات مرور قواعد بياناتنا على رموز مثل @ و+ تحتاج Prisma إلى ترميزها بالنسبة المئوية داخل الرابط، والـ Generic URL مرمّز مسبقًا.
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
    DATABASE_URL=mysql://your-username:your-password@your-host:3306/your-database
  4. يحصل DB_PASSWORD وDATABASE_URL على شارة سر: يُحفظان للكتابة فقط. انقر على استيراد 6 متغيرات.

يتصل البوت نفسه بالقيم الخمس DB_، وتُمرَّر إلى المحوّل واحدة تلو الأخرى كما في البدء السريع لـ Prisma. أما DATABASE_URL فهو لأوامر prisma التي تعمل أثناء التثبيت فقط.

2. ملفات المشروع

package.json

{
  "name": "my-discord-bot",
  "private": true,
  "type": "module",
  "scripts": {
    "postinstall": "prisma generate && prisma migrate deploy"
  },
  "dependencies": {
    "@prisma/adapter-mariadb": "^7",
    "@prisma/client": "^7",
    "discord.js": "^14"
  },
  "devDependencies": {
    "prisma": "^7"
  }
}

prisma7.config.ts (يستخدم Prisma 7.10 وما بعده هذا الاسم كي لا يتعارض مع prisma.config.ts في Prisma 8)

import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: process.env.DATABASE_URL,
  },
});

prisma/schema.prisma

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
}

model Warning {
  id        Int      @id @default(autoincrement())
  guildId   String   @db.VarChar(20)
  userId    String   @db.VarChar(20)
  reason    String   @db.VarChar(200)
  createdAt DateTime @default(now())

  @@index([guildId, userId])
}

index.ts

import { Client, Events, GatewayIntentBits, InteractionContextType, MessageFlags, REST, Routes, SlashCommandBuilder } from "discord.js";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaClient } from "./generated/prisma/client";

// One client for the whole bot. The values come from the Environment pane.
const adapter = new PrismaMariaDb({
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  connectionLimit: 5,
});
const prisma = new PrismaClient({ adapter });

const commands = [
  new SlashCommandBuilder()
    .setName("warn")
    .setDescription("Warn a member and count their warnings")
    .setContexts(InteractionContextType.Guild)
    .addUserOption((o) => o.setName("user").setDescription("Who to warn").setRequired(true))
    .addStringOption((o) => o.setName("reason").setDescription("Why").setRequired(true).setMaxLength(200))
    .toJSON(),
];

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, async (c) => {
  await new REST().setToken(process.env.DISCORD_TOKEN!).put(Routes.applicationCommands(c.user.id), { body: commands });
  console.log(`Logged in as ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== "warn" || !interaction.guildId) return;
  const user = interaction.options.getUser("user", true);
  const reason = interaction.options.getString("reason", true);
  try {
    await prisma.warning.create({ data: { guildId: interaction.guildId, userId: user.id, reason } });
    const total = await prisma.warning.count({ where: { guildId: interaction.guildId, userId: user.id } });
    await interaction.reply(`${user} has ${total} warning(s).`);
  } catch (err) {
    console.error(err);
    if (!interaction.replied) await interaction.reply({ content: "Could not save the warning. Try again later.", flags: MessageFlags.Ephemeral }).catch(console.error);
  }
});

client.login(process.env.DISCORD_TOKEN);

setContexts(InteractionContextType.Guild) والتحقق !interaction.guildId يُبقيان /warn خارج الرسائل الخاصة. والـ try/catch مهم: فبدونه يمرّر discord.js الاستعلام الفاشل إلى حدثه error، وعندما لا يستمع أحد هناك يوقف Node.js البوت.

3. اكتب الترحيل الأول على حاسوبك

في مجلد المشروع على حاسوبك الخاص (على ويندوز أنشئ مجلدات prisma\migrations\0_init يدويًا بدلًا من mkdir -p):

npm install --ignore-scripts
mkdir -p prisma/migrations/0_init
npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

يقارن migrate diff قاعدة بيانات فارغة بمخططك ويكتب SQL دون الاتصال بأي قاعدة بيانات. يتخطى --ignore-scripts السكربت postinstall الذي سيحاول الوصول إلى قاعدة بيانات. يبدأ الملف بـ -- CreateTable وCREATE TABLE `Warning`.

4. ارفع الملفات وابدأ التشغيل

  1. في الملفات ارفع package.json وprisma7.config.ts وindex.ts ومجلد prisma مع schema.prisma وmigrations. لا ترفع node_modules ولا generated: فالخادم يبني كليهما.
  2. في صفحة بوت، لوحة الرمز، اضبط ملف البدء على index.ts.
  3. اضغط تشغيل وافتح وحدة التحكم. يشغّل التثبيت postinstall الخاص بك: سترى Generated Prisma Client (7.10.1)، ثم All migrations have been successfully applied.، ثم [PlayNHost] Starting your bot وLogged in as ....

تعرض وحدة التحكم أيضًا npm warn install-scripts 2 packages had install scripts blocked بخصوص prisma و@prisma/engines. يحظر إصدار npm في صورة Node.js 22 لدينا سكربتات تثبيت الاعتماديات ما لم يُسمح بها. ويظل postinstall الخاص بك يعمل، وفي اختبارنا ولّدت Prisma وهاجرت واستعلمت دون تلك السكربتات.

لماذا لا نستخدم prisma migrate dev؟

ينشئ prisma migrate dev أولًا قاعدة بيانات shadow مؤقتة للتحقق من ترحيلاتك. يستطيع مستخدم قاعدة بيانات بوتك العمل داخل قاعدته لكنه لا يستطيع إنشاء قواعد جديدة، لذلك يتوقف على خوادمنا برسالة:

Prisma Migrate could not create the shadow database. Please make sure the database user has permission to create databases. Read more about the shadow database (and workarounds) at https://pris.ly/d/migrate-shadow

Original error: Error code: P1010

User was denied access on the database `prisma_migrate_shadow_db_...`

لا يطبّق prisma migrate deploy سوى ملفات الترحيل ولا يحتاج إلى قاعدة shadow، ولهذا يستخدمه postinstall. ولا يمكنك أيضًا توجيه migrate dev إلى قاعدة بيانات البوت من حاسوبك: فالقاعدة لا تقبل إلا اتصالات من خوادم PlayNHost ومن phpMyAdmin.

تغيير المخطط لاحقًا

  1. على حاسوبك شغّل npx prisma migrate dev --name متبوعًا باسم للتغيير، على قاعدة MySQL أو MariaDB خاصة بك، مثل قاعدة داخل Docker: اضبط DATABASE_URL في الطرفية على تلك القاعدة، لأن prisma7.config.ts يقرؤه من البيئة. سيكتب مجلدًا جديدًا في prisma/migrations.
  2. ارفع مجلد الترحيل الجديد وملف schema.prisma المعدّل.
  3. يشغّل الخادم npm install، ومعه postinstall الخاص بك، فقط عندما يتغير package.json أو package-lock.json أو إصدار Node.js، أو عندما يغيب node_modules، أو عند أول تشغيل بعد أكثر من يوم على آخر تثبيت. لتطبيق الترحيل الآن، عدّل package.json (مثل حقل version) أو احذف مجلد node_modules في الملفات، ثم أعد التشغيل.

الذاكرة: التثبيت هو الجزء الثقيل

ثبّتنا هذا المشروع من الصفر في حاوية محدودة بـ 512 MB، مثل أصغر خطة بوت لدينا. استغرق التثبيت، مع prisma generate وprisma migrate deploy، 41 ثانية وبلغ ذروة 386 MB من الذاكرة (دون احتساب ذاكرة الملفات المؤقتة) في تشغيل واحد و417 MB في تشغيل ثانٍ، دون أي إنهاء بسبب نفاد الذاكرة في أيٍّ منهما. يناسب خطة 512 MB بهامش أقل من 100 MB، لذا قد يتجاوزها مشروع أكبر أو حزم أكثر. بلغ التثبيت نفسه دون Prisma (discord.js وmysql2 فقط) ذروة 147 MB.

الحدود والأخطاء

  • 20 اتصالًا في الوقت نفسه لكل مستخدم قاعدة بيانات، عبر كل العمليات التي تستخدم بيانات الدخول نفسها. أبقِ connectionLimit منخفضًا؛ 5 تكفي تمامًا لبوت.
  • الاستعلام الذي يستمر أكثر من 120 ثانية يُوقَف، بالخطأ 1969 Query execution was interrupted (max_statement_time exceeded).
  • 500 MB لكل قاعدة بيانات: عند 500 MB تتوقف عمليات الكتابة حتى تحذف صفوفًا. لا تتضمن النسخ الاحتياطية للخادم قاعدة البيانات. صدّرها من phpMyAdmin بين حين وآخر.
  • لمشكلات الاتصال الأسباب نفسها كما في mysql2 (كلمة مرور خاطئة، أو مضيف أو منفذ خاطئ، أو متغير مفقود في البيئة، أو الاتصال من حاسوبك الخاص)، لكن الرسائل مختلفة: فمحوّل MariaDB يستخدم مشغّل mariadb وليس mysql2، ولم نسجّل رسائله. الأسباب مدرجة في كيفية استخدام قاعدة بيانات MySQL مع بوت discord.js.

تفضّل SQL العادي مع mysql2 أو aiomysql؟ discord.js (mysql2)، discord.py (aiomysql). النظرة العامة: كيف تستخدم قاعدة بيانات مع بوت Discord. تحتاج إلى خادم بوت مع قاعدة بيانات مضمّنة؟ شاهد خطط البوتات.

بوتك مستضاف في مكان آخر بالفعل؟ انقله إلينا

أحدث مقالات المدونة