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
- Otwórz serwer swojego bota w panelu, kliknij Bazy danych, potem Nowa baza danych i Utwórz bazę danych.
- 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.
- 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
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
- 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.
- Na stronie Bot, w panelu Kod, ustaw Plik startowy na
index.ts.
- 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
- 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.
- Wgraj nowy folder migracji i zmieniony
schema.prisma.
- 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.