НОВОЕ 📢 Ограниченное стартовое предложение: скидка до 70% на первый месяц для любой игры!Посмотреть тарифы

Как использовать Prisma с MySQL в Discord-боте

Хостинг Discord-ботовUpdated 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 секунд), но не с настоящим входом в Discord.

Prisma 7, а не новейшая версия из npm

6 ноября 2026 года тег latest пакета prisma в npm указывает на релиз-кандидат Prisma 8 (8.0.0-rc.30), а официальный quickstart Prisma для MySQL написан для Prisma 7: он ставит prisma@prev и @prisma/client@7. Поэтому в этом руководстве все пакеты Prisma закреплены на ^7. Обычный npm install prisma поставил бы вам версию 8.

Что вам понадобится

  • Бот discord.js, написанный на TypeScript. Наш тип Node.js Bot запускает через tsx главный файл с расширением .ts, поэтому ничего не нужно компилировать заранее: Как запустить Discord-бота на TypeScript.
  • Тариф для ботов PlayNHost с типом Node.js Bot. В каждый тариф входит одна база данных.
  • Node.js и npm на вашем компьютере, чтобы один раз написать миграцию.

1. Создайте базу данных и переменные

  1. Откройте сервер бота в панели, нажмите Базы данных, затем Новая база данных и Создать базу данных.
  2. Откроется страница новой базы (позже её можно открыть в разделе Ваши базы данных). Не закрывайте её и в разделе Готовый конфиг выберите Generic URL в списке Конфиг для: это строка подключения mysql://..., которая нужна командной строке Prisma.
  3. Откройте страницу Бот, вкладку Окружение, нажмите Вставить файл .env и вставьте шесть строк со своими значениями. В DATABASE_URL вставьте Generic URL в точности как он показан: пароли наших баз могут содержать символы вроде @ и +, которые Prisma требует в URL в процентной кодировке, а 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_, которые передаются адаптеру по одному, как в quickstart 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. Напишите первую миграцию на своём компьютере

В папке проекта на вашем компьютере (в Windows создайте папки 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-ботом. Нужен сервер для бота с базой данных в комплекте? Посмотрите тарифы для ботов.

Ваш бот уже у другого хостинга? Перенесите его к нам

ПОСЛЕДНИЕ БЛОГИ