NUEVO 📢 Oferta de lanzamiento limitada: ¡hasta un 70% de descuento en tu primer mes, en todos los juegos!Ver planes

Cómo usar una base de datos MySQL con un bot de discord.js (mysql2)

Hosting de bots de DiscordUpdated Nov 6, 20269 min read

Respuesta corta. Añade mysql2 a tu bot de discord.js, crea un único pool de conexiones a partir de variables de entorno cuando el bot arranque y ejecuta todas las consultas con él, usando marcadores ?. En PlayNHost, todos los planes de bot incluyen una base de datos MySQL: créala en la página Bases de datos, pega sus datos en el panel Entorno y el código de abajo funciona tal cual. La base de datos acepta conexiones de tus servidores de PlayNHost y de phpMyAdmin, no de tu propio ordenador. Probamos este código el 6 de noviembre de 2026 con discord.js 14.28.1, mysql2 3.24.6 y Node.js 22, contra MariaDB 10.11 con una cuenta de base de datos configurada como la nuestra (20 conexiones, límite de consulta de 120 segundos): el arranque, la tabla y el manejador de /warn, pero no un inicio de sesión real en Discord.

Qué necesitas

1. Crea la base de datos

  1. Abre tu servidor de bot en el panel y haz clic en Bases de datos.
  2. Haz clic en Base de datos nueva. La Etiqueta es opcional; Se creará como muestra el nombre completo. Haz clic en Crear base de datos.
  3. Se abre la página de la nueva base de datos (más adelante, haz clic en ella en Tus bases de datos). Su tarjeta Conexión muestra Host, Puerto, Base de datos, Nombre de usuario y Contraseña. En Config lista para usar, Config para incluye un fragmento Node.js (mysql2) con los mismos valores ya rellenados.

2. Guarda los datos en Entorno

  1. Abre la página Bot, el panel Entorno, y haz clic en Pegar un archivo .env.
  2. Pega estas cinco líneas con los valores de la tarjeta Conexión:
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD recibe una insignia Secreto: se guarda solo para escritura y no se vuelve a mostrar. Haz clic en Importar 5 variables.

Usa el puerto real de la tarjeta Conexión. Mantener la contraseña fuera de tu código significa que nunca acaba en un archivo ni en un repositorio de Git. Más sobre este panel: Cómo definir variables de entorno y secretos para tu bot de Discord.

3. El código

package.json

{
  "name": "my-discord-bot",
  "private": true,
  "main": "index.js",
  "dependencies": {
    "discord.js": "^14",
    "mysql2": "^3"
  }
}

index.js

const { Client, Events, GatewayIntentBits, InteractionContextType, MessageFlags, REST, Routes, SlashCommandBuilder } = require('discord.js');
const mysql = require('mysql2/promise');

// One pool for the whole bot. The values come from the Environment pane.
const pool = mysql.createPool({
  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 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 () => {
  await new REST().setToken(process.env.DISCORD_TOKEN).put(Routes.applicationCommands(client.user.id), { body: commands });
  console.log(`Logged in as ${client.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 pool.execute('INSERT INTO warnings (guild_id, user_id, reason) VALUES (?, ?, ?)', [interaction.guildId, user.id, reason]);
    const [rows] = await pool.execute('SELECT COUNT(*) AS total FROM warnings WHERE guild_id = ? AND user_id = ?', [interaction.guildId, user.id]);
    await interaction.reply(`${user} has ${rows[0].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);
  }
});

async function main() {
  await pool.query(`CREATE TABLE IF NOT EXISTS warnings (
    id INT AUTO_INCREMENT PRIMARY KEY,
    guild_id VARCHAR(20) NOT NULL,
    user_id VARCHAR(20) NOT NULL,
    reason VARCHAR(200) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY guild_user (guild_id, user_id)
  )`);
  console.log('Database ready');
  await client.login(process.env.DISCORD_TOKEN);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Qué hace: al arrancar, el bot crea la tabla warnings si no existe, muestra Database ready y luego inicia sesión. /warn guarda una advertencia y responde con el total del miembro. discord.js entrega los ID de Discord como cadenas, porque son demasiado grandes para un número de JavaScript, así que se guardan como texto. Los marcadores ? envían el texto del miembro a la base de datos por separado del SQL, así que un motivo como '); DROP TABLE warnings; -- se guarda como texto normal.

Dos líneas mantienen vivo el bot. setContexts(InteractionContextType.Guild) y la comprobación !interaction.guildId impiden /warn en un mensaje directo, donde no hay ningún servidor al que asociar la advertencia. El try/catch responde con un mensaje privado corto cuando falla una consulta. Sin él, discord.js pasa el error a su evento error, y si nadie escucha ahí, Node.js detiene el bot. En nuestra prueba borramos la tabla y ejecutamos /warn: el bot registró ER_NO_SUCH_TABLE, respondió Could not save the warning. Try again later. y siguió funcionando.

4. Ejecútalo

  1. Sube index.js y package.json en Archivos. No subas node_modules.
  2. Pulsa Iniciar y abre Consola. Al arrancar se instalan los paquetes y luego aparecen [PlayNHost] Starting your bot, Database ready y Logged in as ....
  3. En Discord, escribe /warn. Para ver las filas, abre la página de la base de datos y haz clic en Abrir en phpMyAdmin.

Límites que importan para tu pool

  • 20 conexiones a la vez por usuario de base de datos. Cuenta todo proceso que use el mismo acceso: tu bot, una segunda copia, un panel de control. La conexión 21 se rechaza con ER_USER_LIMIT_REACHED. En nuestra prueba, un pool con connectionLimit: 30 falló con ese error en cuanto se ejecutaron 30 consultas a la vez. Un pool de 5 nunca abre más de 5: las consultas extra esperan una conexión libre.
  • Una consulta que dura más de 120 segundos se detiene. MariaDB la termina con el error 1969, Query execution was interrupted (max_statement_time exceeded). En nuestra prueba llegó a los 120.03 segundos. Las consultas de un bot suelen tardar milisegundos, así que esto solo afecta a una consulta descontrolada.
  • 500 MB por base de datos. La página Bases de datos muestra cuánto se usa. Al llegar a 500 MB, se bloquean las escrituras hasta que borres filas.
  • Las copias de seguridad del servidor no incluyen la base de datos. Expórtala desde phpMyAdmin de vez en cuando.

Errores y qué significan

Estos son los mensajes exactos que dio mysql2 en nuestra prueba:

  • ER_ACCESS_DENIED_ERROR ... Access denied for user: DB_USER o DB_PASSWORD incorrectos. Tras Restablecer contraseña en la tarjeta Conexión, la contraseña antigua deja de funcionar al instante: cambia DB_PASSWORD y reinicia el bot.
  • ECONNREFUSED: nada aceptó la conexión en ese host y puerto. Revisa DB_HOST y DB_PORT. Si falta DB_HOST, también acabas aquí, porque mysql2 se conecta entonces a localhost, donde no hay ninguna base de datos: falta la variable en Entorno o no reiniciaste el bot después de añadirla.
  • ENOTFOUND: DB_HOST está mal escrito, así que el nombre no existe.
  • ETIMEDOUT: nada respondió en 10 segundos, la espera predeterminada de mysql2. Sea cual sea el mensaje, una conexión desde tu propio ordenador falla: la base de datos solo acepta conexiones de servidores de PlayNHost y de phpMyAdmin.
  • ER_USER_LIMIT_REACHED ... has exceeded the 'max_user_connections' resource (current value: 20): hay demasiadas conexiones abiertas, mira los límites de arriba. Normalmente es una conexión nueva por comando que nunca se cierra, o una segunda copia del bot.

Más errores de bots: Errores comunes de los bots de Discord y qué significan.

Cuánta memoria añade

Instalar discord.js y mysql2 desde cero alcanzó un máximo de 147 MB de memoria (sin contar la caché de archivos) en nuestra prueba, en un contenedor limitado a 512 MB como nuestro plan de bot más pequeño. Desde el arranque hasta que Discord rechazó nuestro token de prueba, el bot usó como máximo 31 MB; había creado la tabla pero nunca inició sesión. Un bot con sesión iniciada y en muchos servidores usa más: ¿Cuánta RAM necesita un bot de Discord?

Lo mismo en Python o con Prisma

¿Necesitas un servidor de bot con base de datos incluida? Ver los planes de bots.

¿Ya tienes tu bot en otro proveedor? Tráelo con nosotros

NUESTROS ÚLTIMOS BLOGS