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 grammY (mysql2)

Hosting de bots de TelegramUpdated Nov 6, 202612 min read

Respuesta corta. Añade mysql2 a tu bot de grammY, crea un pool de conexiones a partir de variables de entorno cuando arranque el bot, crea tus tablas antes de bot.start()y ejecuta cada consulta a través del pool. Usa marcadores ? y guarda los IDs de Telegram como BIGINT, no como INT. En PlayNHost cada plan de bot incluye 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 funcionará tal cual. La base de datos acepta conexiones desde tus servidores de PlayNHost y desde phpMyAdmin, no desde tu propio ordenador. Probamos este código el 6 de noviembre de 2026 con grammY 1.46.0, 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) y un sustituto del servidor Bot API de Telegram: 8 comandos de entrada, 8 respuestas de salida, las notas guardadas en la base de datos. Un inicio de sesión real en Telegram no formó parte de la prueba.

Lo que 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. Una 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 hay un fragmento Node.js (mysql2) con los mismos valores ya rellenados.

2. Pon los datos en Entorno

  1. Abre la página Bot , panel Entornoy 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 en modo solo escritura y no se vuelve a mostrar. Haz clic en Importar 5 variables.

Usa el puerto real de la tarjeta Conexión . El token del bot no es una de estas líneas: el token que guardaste en la página Bot , panel Telegram, ya llega a tu código como TELEGRAM_BOT_TOKEN, el nombre que lee el código de abajo (y como BOT_TOKEN, TOKEN, TELEGRAM_TOKEN y TELOXIDE_TOKEN). Si tu archivo .env tiene una línea de token, el cuadro de diálogo la omite. Más sobre este panel: Cómo definir variables de entorno y secretos para tu bot de Discord.

3. El código

Un pequeño bot de notas: /note buy milk guarda una nota, /notes muestra tus últimas cinco.

package.json

{
  "name": "my-grammy-bot",
  "private": true,
  "main": "index.js",
  "dependencies": {
    "grammy": "^1.46.0",
    "mysql2": "^3.24.6"
  }
}

index.js

const { Bot } = require("grammy");
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),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  connectionLimit: 5,
});

const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);

bot.command("note", async (ctx) => {
  const text = ctx.match.trim();
  if (!text) return ctx.reply("Write your note after the command, for example: /note buy milk");
  if (text.length > 500) return ctx.reply("A note can be up to 500 characters.");
  await pool.execute("INSERT INTO notes (user_id, text) VALUES (?, ?)", [ctx.from.id, text]);
  await ctx.reply("Saved. Send /notes to see your last five.");
});

bot.command("notes", async (ctx) => {
  const [rows] = await pool.execute(
    "SELECT text FROM notes WHERE user_id = ? ORDER BY id DESC LIMIT 5",
    [ctx.from.id],
  );
  if (rows.length === 0) return ctx.reply("You have no notes yet.");
  await ctx.reply(rows.map((row) => `- ${row.text}`).join("\n"));
});

// Without this, an error in one command stops the whole bot.
bot.catch((err) => {
  console.error(`Error while handling update ${err.ctx.update.update_id}:`, err.error);
});

async function main() {
  await pool.query(
    "CREATE TABLE IF NOT EXISTS notes (" +
      "id INT AUTO_INCREMENT PRIMARY KEY, " +
      "user_id BIGINT NOT NULL, " +
      "text VARCHAR(500) NOT NULL, " +
      "created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
      "KEY user_id (user_id))",
  );
  console.log("Database ready");
  // bot.start() only returns when the bot stops, so the ready line goes in onStart.
  await bot.start({ onStart: (me) => console.log(`Logged in as @${me.username}`) });
}

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

Qué hace: main() crea la tabla notes si no existe e imprime Database ready antes de que bot.start() pida mensajes a Telegram. El pool se crea una sola vez, al principio del archivo, y todos los comandos lo usan: pool.execute() toma prestada una conexión, ejecuta la consulta y la devuelve. ctx.match es el texto que va después del comando: grammY quita los espacios de delante y conserva el resto tal como se escribió, saltos de línea incluidos. bot.catch() importa aquí: la documentación de grammY dice que, sin un manejador de errores, un error en un manejador detiene un bot iniciado con bot.start(), y un error de base de datos es justo ese tipo de error.

Los marcadores ? envían la nota a la base de datos separada del SQL, así que una nota como '); DROP TABLE notes; -- se guarda como texto normal. En nuestra prueba se guardó como la fila 2 y la tabla siguió ahí. Nunca construyas el SQL con una plantilla de texto.

Guarda los IDs de Telegram como BIGINT

La documentación de la Bot API de Telegram dice que un ID de usuario o de chat «puede tener más de 32 bits significativos», pero como máximo 52, así que un entero de 64 bits es seguro. Su página sobre los IDs da los rangos: los IDs de usuario llegan hasta 1.099.511.627.775, mientras que los IDs de grupos, supergrupos y canales son negativos. Los supergrupos y canales van de -1.000.000.000.001 a -1.997.852.516.352 (por ejemplo -1001234567890). El INT de MySQL llega hasta 2.147.483.647. En nuestra prueba, con el mismo modo SQL estricto que usa nuestro servidor de bases de datos, guardar el ID de usuario 7.000.000.001 en una columna INT falló. mysql2 dio el código de error ER_WARN_DATA_OUT_OF_RANGE (errno 1264) con este mensaje:

Out of range value for column 'user_id' at row 1

Una columna BIGINT lo guardó, y también -1001234567890 . Usa BIGINTa secas, no BIGINT UNSIGNED, para que también quepan los IDs de chat.

4. Ejecútalo

  1. Sube index.js y package.json en Archivos. No subas node_modules.
  2. Pulsa Iniciar y abre Consola. Al iniciar se instalan los paquetes de package.json, y después se muestran [PlayNHost] Starting your bot, Database ready y Logged in as @your_bot. Esa última línea cambia el estado en Resumen a En línea en Telegram.
  3. En Telegram, envía a tu bot /note buy milky después /notes. Para ver las filas, abre la página de la base de datos y haz clic en Abrir en phpMyAdmin.

En nuestra prueba, el sustituto de Telegram envió estos comandos y recibió estas respuestas:

EnviadoRespuesta
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note y 501 caracteresA note can be up to 500 characters.
/notesLas notas guardadas, las más recientes primero
/notes de un segundo usuarioYou have no notes yet.

Muchos mensajes a la vez

La documentación de grammY dice que bot.start() procesa todas las actualizaciones de forma secuencial: un comando termina antes de que empiece el siguiente. En nuestra prueba el bot mantuvo 1 conexión abierta tras responder a los 8 comandos. connectionLimit: 5 importa cuando tu código ejecuta consultas en paralelo, o si pasas a grammY runner, que procesa las actualizaciones de forma concurrente: el pool nunca abre más de 5 conexiones, y una consulta adicional espera a que haya una libre.

Una consulta fallida no detiene el bot

En nuestra prueba borramos la tabla mientras el bot estaba en marcha y enviamos /notes. bot.catch() imprimió Error while handling update 500001: Error: Table '...notes' doesn't exist con code: 'ER_NO_SUCH_TABLE', el bot no envió ninguna respuesta a ese comando y siguió funcionando: cuando la tabla volvió, el siguiente /note se guardó. Quien envió el comando no ve nada, así que si una consulta puede fallar, envuélvela en try/catch dentro del manejador y responde con un mensaje corto.

Una noche tranquila no rompe el pool

Nuestro servidor de bases de datos cierra una conexión que ha estado inactiva durante 8 horas. Un bot que no recibe mensajes durante tanto tiempo, por ejemplo por la noche, encuentra cerradas sus conexiones del pool. El pool de mysql2 lo gestiona solo. Lo probamos con el límite de inactividad reducido a 5 segundos: tras 7 segundos de calma, el siguiente /note se guardó sin errores.

Límites que importan para tu pool

  • 20 conexiones a la vez por usuario de base de datos. Cuenta cada proceso que use el mismo acceso. La conexión 21 se rechaza con ER_USER_LIMIT_REACHED. En nuestra prueba, un pool con connectionLimit: 30 falló con ese error cuando se ejecutaron 30 consultas a la vez. Un pool de 5 mantiene al bot muy por debajo.
  • Una consulta que se ejecuta durante más de 120 segundos se detiene. MariaDB la termina con el error 1969, Query execution was interrupted (max_statement_time exceeded). En una prueba anterior nuestra, con una cuenta configurada igual, eso llegó a los 120,03 segundos. Las consultas de un bot normalmente tardan 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 detienen las escrituras hasta que borres filas.
  • Las copias de seguridad del servidor no incluyen la base de datos. Expórtala de vez en cuando desde phpMyAdmin.

Errores y qué significan

Estos son los códigos de error que dio mysql2 en nuestra prueba:

  • ER_ACCESS_DENIED_ERROR ... Access denied for user: DB_USER o DB_PASSWORDincorrectos. Después de Restablecer contraseña en la tarjeta Conexión la contraseña antigua deja de funcionar al instante: sustituye DB_PASSWORD y reinicia el bot.
  • ECONNREFUSED: nada aceptó la conexión en ese host y puerto. Comprueba 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 se reinició el bot después de añadirla.
  • ENOTFOUND: DB_HOST está mal escrito, así que el nombre no existe.
  • ETIMEDOUT: nada respondió a tiempo. Sea cual sea el mensaje, una conexión desde tu propio ordenador falla: la base de datos solo acepta conexiones desde servidores de PlayNHost y 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. Por ejemplo, una segunda copia del bot o un pool creado para cada mensaje.
  • ER_WARN_DATA_OUT_OF_RANGE (errno 1264, Out of range value for column 'user_id' at row 1): una columna de ID es INT. Cámbiala a BIGINT.
  • ER_NO_SUCH_TABLE (errno 1146): falta la tabla. El código de arriba la crea al arrancar; si la hiciste a mano, revisa su nombre.

Un token incorrecto aparece después de la parte de la base de datos. Con nuestro token falso contra el Telegram real, el bot imprimió Database readyy se detuvo tras un cuarto de segundo aproximadamente con GrammyError: Call to 'deleteWebhook' failed! (401: Unauthorized). Copia de nuevo el token desde @BotFather y pégalo en la página Bot , panel Telegram. Más errores de Telegram: Solucionar el error 409 Conflict de un bot de Telegram.

Cuánta memoria usa

En nuestra prueba, el bot en marcha usó 67,6 MB de memoria (residente) tras responder a los 8 comandos, con su pool abierto, en un contenedor limitado a 512 MB como nuestro plan de bot más pequeño. En nuestra prueba de alojamiento anterior, grammY solo midió 60 MB (ese bot se detuvo por un token rechazado, antes de procesar ningún mensaje), así que la diferencia, unos 8 MB, cubre mysql2, el pool y los comandos procesados. Instalar grammY y mysql2 con npm desde cero alcanzó un máximo de 140 MB, caché de archivos incluida.

Lo mismo con otra biblioteca

¿Necesitas un servidor para tu bot de Telegram con base de datos incluida? Mira los planes de bots de Telegram.

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

NUESTROS ÚLTIMOS BLOGS