NOWOŚĆ 📢 Ograniczona oferta na start: do 70% zniżki na pierwszy miesiąc, na każdą grę!Zobacz plany

Jak używać Prisma z MySQL w bocie Discord

Hosting botów DiscordUpdated Nov 6, 20269 min read

Krótka odpowiedź. Użyj Prisma 7 z adapterem sterownika @prisma/adapter-mariadb, który Prisma dokumentuje dla MySQL i MariaDB. W PlayNHost prisma migrate dev nie może działać na bazie danych twojego bota, bo musi utworzyć tymczasową bazę „shadow", a użytkownicy baz danych nie mogą tu tworzyć baz. Pierwszą migrację napisz zamiast tego przez prisma migrate diff, które nie potrzebuje żadnej bazy, a skrypt postinstall niech uruchomi na serwerze prisma generate i prisma migrate deploy. Każdy plan bota zawiera jedną bazę MySQL. Kod i polecenia poniżej przetestowaliśmy 6 listopada 2026 z Prisma 7.10.1, discord.js 14 i Node.js 22, na MariaDB 10.11 z kontem bazy danych skonfigurowanym tak jak nasze (bez prawa tworzenia baz, 20 połączeń, limit zapytania 120 sekund), ale nie z prawdziwym logowaniem do Discorda.

Prisma 7, a nie najnowsza wersja z npm

6 listopada 2026 tag latest pakietu prisma w npm wskazuje na release candidate Prisma 8 (8.0.0-rc.30), a oficjalny quickstart Prisma dla MySQL jest ten dla Prisma 7: instaluje prisma@prev i @prisma/client@7. Dlatego ten poradnik przypina wszystkie pakiety Prisma do ^7. Zwykłe npm install prisma dałoby ci wersję 8.

Czego potrzebujesz

  • Bota discord.js napisanego w TypeScript. Nasz typ Node.js Bot uruchamia przez tsx główny plik z rozszerzeniem .ts, więc nic nie trzeba wcześniej kompilować: Jak uruchomić bota Discord w TypeScript.
  • Plan bota PlayNHost z typem Node.js Bot. Każdy plan zawiera jedną bazę danych.
  • Node.js i npm na twoim komputerze, żeby jednorazowo napisać migrację.

1. Utwórz bazę danych i zmienne

  1. Otwórz serwer swojego bota w panelu, kliknij Bazy danych, potem Nowa baza danych i Utwórz bazę danych.
  2. Otworzy się strona nowej bazy (później klikniesz ją w Twoje bazy danych). Zostaw ją otwartą i w sekcji Gotowa konfiguracja wybierz Generic URL w polu Konfiguracja dla: to ciąg połączenia mysql://..., którego potrzebuje wiersz poleceń Prisma.
  3. Otwórz stronę Bot, panel Środowisko, kliknij Wklej plik .env i wklej sześć linii ze swoimi wartościami. Jako DATABASE_URL wklej Generic URL dokładnie tak, jak jest pokazany: hasła naszych baz mogą zawierać znaki takie jak @ i +, które Prisma wymaga zakodowane procentowo w adresie URL, a Generic URL jest już zakodowany.
    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 i DATABASE_URL dostają plakietkę Sekret: są zapisane tylko do zapisu. Kliknij Importuj 6 zmiennych.

Sam bot łączy się przez pięć wartości DB_, przekazywanych adapterowi pojedynczo, tak jak w quickstarcie Prisma. DATABASE_URL służy tylko poleceniom prisma uruchamianym podczas instalacji.

2. Pliki projektu

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 i nowsze używają tej nazwy, żeby nie kolidowała z prisma.config.ts z 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) i sprawdzenie !interaction.guildId nie pozwalają używać /warn w wiadomościach prywatnych. try/catch jest ważne: bez niego discord.js przekazuje nieudane zapytanie do zdarzenia error, a gdy nikt go tam nie nasłuchuje, Node.js zatrzymuje bota.

3. Napisz pierwszą migrację na swoim komputerze

W folderze projektu na swoim komputerze (w Windows utwórz ręcznie foldery prisma\migrations\0_init zamiast używać 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 porównuje pustą bazę z twoim schematem i zapisuje SQL, bez łączenia się z żadną bazą. --ignore-scripts pomija postinstall, który próbowałby połączyć się z bazą. Plik zaczyna się od -- CreateTable i CREATE TABLE `Warning`.

4. Wgraj pliki i uruchom

  1. W sekcji Files wgraj package.json, prisma7.config.ts, index.ts oraz folder prisma z schema.prisma i migrations. Nie wgrywaj node_modules ani generated: serwer zbuduje oba.
  2. Na stronie Bot, w panelu Kod, ustaw Plik startowy na index.ts.
  3. Naciśnij Uruchom i otwórz Console. Instalacja uruchamia twój postinstall: zobaczysz Generated Prisma Client (7.10.1), potem All migrations have been successfully applied., potem [PlayNHost] Starting your bot i Logged in as ....

W konsoli pojawi się też npm warn install-scripts 2 packages had install scripts blocked dla prisma i @prisma/engines. Wersja npm w naszym obrazie Node.js 22 blokuje skrypty instalacyjne zależności, dopóki nie zostaną dozwolone. Twój własny postinstall nadal działa, a w naszym teście Prisma wygenerował klienta, zmigrował i wykonał zapytania bez tych skryptów.

Dlaczego nie prisma migrate dev?

prisma migrate dev najpierw tworzy tymczasową bazę shadow, żeby sprawdzić twoje migracje. Użytkownik bazy twojego bota może pracować w swojej bazie, ale nie może tworzyć nowych, więc na naszych serwerach polecenie kończy się błędem:

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 tylko stosuje pliki migracji i nie potrzebuje bazy shadow, dlatego postinstall go używa. Nie wskażesz też migrate dev na bazę bota ze swojego komputera: baza przyjmuje połączenia tylko z serwerów PlayNHost i z phpMyAdmin.

Zmiana schematu później

  1. Na swoim komputerze uruchom npx prisma migrate dev --name plus nazwę zmiany, na własnym MySQL lub MariaDB, na przykład w Dockerze: ustaw DATABASE_URL w terminalu na tę bazę, bo prisma7.config.ts czyta ją ze środowiska. Polecenie zapisze nowy folder w prisma/migrations.
  2. Wgraj nowy folder migracji i zmieniony schema.prisma.
  3. Serwer uruchamia npm install, a z nim twój postinstall, tylko gdy zmienił się package.json, package-lock.json lub wersja Node.js, gdy brakuje node_modules albo przy pierwszym starcie ponad dobę po ostatniej instalacji. Żeby zastosować migrację od razu, zmień package.json (na przykład jego version) albo usuń folder node_modules w Files, a potem zrestartuj bota.

Pamięć: ciężka jest instalacja

Zainstalowaliśmy ten projekt od zera w kontenerze z limitem 512 MB, tak jak nasz najmniejszy plan bota. Instalacja, z prisma generate i prisma migrate deploy, trwała 41 sekund i w jednym uruchomieniu osiągnęła szczyt 386 MB pamięci (bez cache plików), a w drugim 417 MB, bez zabicia procesu z powodu braku pamięci w żadnym z nich. Mieści się w planie 512 MB z zapasem poniżej 100 MB, więc większy projekt lub więcej pakietów mogą go przekroczyć. Ta sama instalacja bez Prisma (tylko discord.js i mysql2) osiągnęła szczyt 147 MB.

Limity i błędy

  • 20 połączeń naraz na użytkownika bazy, we wszystkich procesach używających tego samego loginu. Trzymaj connectionLimit nisko; 5 w zupełności wystarcza botowi.
  • Zapytanie trwające dłużej niż 120 sekund jest zatrzymywane, z błędem 1969 Query execution was interrupted (max_statement_time exceeded).
  • 500 MB na bazę: przy 500 MB zapisy są wstrzymywane, dopóki nie usuniesz wierszy. Kopie zapasowe serwera nie zawierają bazy danych. Eksportuj ją od czasu do czasu z phpMyAdmin.
  • Problemy z połączeniem mają te same przyczyny co przy mysql2 (złe hasło, zły host lub port, brakująca zmienna w Środowisko, łączenie z własnego komputera), ale komunikaty są inne: adapter MariaDB używa sterownika mariadb, a nie mysql2, i nie zapisaliśmy jego komunikatów. Przyczyny opisuje Jak używać bazy danych MySQL z botem discord.js.

Wolisz zwykły SQL z mysql2 lub aiomysql? discord.js (mysql2), discord.py (aiomysql). Przegląd: Jak używać bazy danych z botem Discord. Potrzebujesz serwera bota z wbudowaną bazą? Zobacz plany botów.

Twój bot działa już gdzie indziej? Przenieś go do nas

NASZE OSTATNIE WPISY