Cosa ti serve
- Un bot discord.js scritto in TypeScript. Il nostro tipo Node.js Bot esegue con tsx un file principale che termina in
.ts, quindi non serve compilare nulla prima: Come eseguire un bot Discord in TypeScript.
- Un piano bot PlayNHost con il tipo Node.js Bot. Ogni piano include un database.
- Node.js e npm sul tuo computer, per scrivere la migrazione una volta.
1. Crea il database e le variabili
- Apri il server del tuo bot nel pannello, clicca su Database, poi su Nuovo database e Crea database.
- Si apre la pagina del nuovo database (più avanti, cliccalo in I tuoi database). Tienila aperta e, sotto Config pronta all’uso, scegli Generic URL in Config per: è la stringa di connessione
mysql://... che serve alla riga di comando di Prisma.
- Apri la pagina Bot, il riquadro Ambiente, clicca su Incolla un file .env e incolla sei righe con i tuoi valori. Per
DATABASE_URL, incolla la Generic URL esattamente come viene mostrata: le password dei nostri database possono contenere caratteri come @ e +, che Prisma richiede codificati in percentuale dentro un URL, e la Generic URL è già codificata.
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 e DATABASE_URL ricevono un badge Segreto: vengono salvate in sola scrittura. Clicca su Importa 6 variabili.
Il bot si collega con i cinque valori DB_, passati all'adapter uno per uno come nel quickstart di Prisma. DATABASE_URL serve solo ai comandi prisma eseguiti durante l'installazione.
2. I file del progetto
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 e successivi usano questo nome, così non entra in conflitto con il prisma.config.ts di 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) e il controllo !interaction.guildId tengono /warn fuori dai messaggi privati. Il try/catch è importante: senza, discord.js passa una query fallita al suo evento error, e quando nessuno lo ascolta Node.js ferma il bot.
3. Scrivi la prima migrazione sul tuo computer
Nella cartella del progetto sul tuo computer (su Windows, crea a mano le cartelle prisma\migrations\0_init invece di usare 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 confronta un database vuoto con il tuo schema e scrive l'SQL, senza collegarsi ad alcun database. --ignore-scripts salta il postinstall, che proverebbe a raggiungere un database. Il file inizia con -- CreateTable e CREATE TABLE `Warning`.
4. Carica e avvia
- In Files, carica
package.json, prisma7.config.ts, index.ts e la cartella prisma con schema.prisma e migrations. Non caricare node_modules né generated: il server crea entrambe.
- Nella pagina Bot, riquadro Codice, imposta File di avvio su
index.ts.
- Premi Avvia e apri Console. L'installazione esegue il tuo
postinstall: vedi Generated Prisma Client (7.10.1), poi All migrations have been successfully applied., poi [PlayNHost] Starting your bot e Logged in as ....
La console mostra anche npm warn install-scripts 2 packages had install scripts blocked per prisma e @prisma/engines. La versione di npm nella nostra immagine Node.js 22 blocca gli script di installazione delle dipendenze se non sono consentiti. Il tuo postinstall gira comunque, e nel nostro test Prisma ha generato, migrato e interrogato il database senza quegli script.
Perché non prisma migrate dev?
prisma migrate dev crea prima un database shadow temporaneo per verificare le tue migrazioni. L'utente database del tuo bot può lavorare nel proprio database ma non può crearne di nuovi, quindi sui nostri server si ferma con:
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 applica solo i file di migrazione e non richiede un database shadow, ed è per questo che il postinstall lo usa. Non puoi nemmeno puntare migrate dev al database del bot dal tuo computer: il database accetta solo connessioni dai server PlayNHost e da phpMyAdmin.
Modificare lo schema in seguito
- Sul tuo computer, esegui
npx prisma migrate dev --name più un nome per la modifica, su un MySQL o MariaDB tuo, per esempio uno in Docker: imposta DATABASE_URL nel terminale su quel database, perché prisma7.config.ts la legge dall'ambiente. Scrive una nuova cartella in prisma/migrations.
- Carica la nuova cartella di migrazione e il
schema.prisma modificato.
- Il server esegue
npm install, e con esso il tuo postinstall, solo quando cambiano package.json, package-lock.json o la versione di Node.js, quando manca node_modules, o al primo avvio a più di un giorno dall'ultima installazione. Per applicare subito la migrazione, modifica package.json (per esempio il suo version) o elimina la cartella node_modules in Files, poi riavvia.
Memoria: l'installazione è la parte pesante
Abbiamo installato questo progetto da zero in un container limitato a 512 MB, come il nostro piano bot più piccolo. L'installazione, con prisma generate e prisma migrate deploy, ha richiesto 41 secondi e ha raggiunto un picco di 386 MB di memoria (cache dei file esclusa) in una esecuzione e di 417 MB in una seconda, senza alcun kill per memoria esaurita in nessuna delle due. Sta nel piano da 512 MB con meno di 100 MB di margine, quindi un progetto più grande o più pacchetti potrebbero superarlo. La stessa installazione senza Prisma (solo discord.js e mysql2) ha raggiunto un picco di 147 MB.
Limiti ed errori
- 20 connessioni contemporanee per utente database, tra tutti i processi che usano lo stesso login. Tieni
connectionLimit basso; 5 sono più che sufficienti per un bot.
- Una query che dura più di 120 secondi viene fermata, con l'errore 1969
Query execution was interrupted (max_statement_time exceeded).
- 500 MB per database: a 500 MB le scritture vengono bloccate finché non elimini delle righe. I backup del server non includono il database. Esportalo da phpMyAdmin ogni tanto.
- I problemi di connessione hanno le stesse cause che con mysql2 (password sbagliata, host o porta sbagliati, una variabile mancante in Ambiente, connessione dal tuo computer), ma i messaggi sono diversi: l'adapter MariaDB usa il driver
mariadb, non mysql2, e non abbiamo registrato i suoi messaggi. Le cause sono elencate in Come usare un database MySQL con un bot discord.js.
Preferisci SQL semplice con mysql2 o aiomysql? discord.js (mysql2), discord.py (aiomysql). La panoramica: Come usare un database con il tuo bot Discord. Ti serve un server bot con database incluso? Guarda i piani bot.