NOVO 📢 Oferta de lançamento limitada: até 70% de desconto no seu primeiro mês, em todos os jogos!Ver planos

Como usar um banco de dados MySQL com um bot grammY (mysql2)

Hospedagem de bot do TelegramUpdated Nov 6, 202612 min read

Resposta curta. Adicione mysql2 ao seu bot grammY, crie um pool de conexões a partir de variáveis de ambiente quando o bot iniciar, crie suas tabelas antes de bot.start()e execute cada consulta pelo pool. Use marcadores ? e guarde os IDs do Telegram como BIGINT, não como INT. Na PlayNHost, cada plano de bot inclui um banco de dados MySQL: crie-o na página Bancos de dados , cole os dados dele no painel Ambiente e o código abaixo funciona do jeito que está. O banco aceita conexões dos seus servidores da PlayNHost e do phpMyAdmin, não do seu próprio computador. Testamos este código em 6 de novembro de 2026 com grammY 1.46.0, mysql2 3.24.6 e Node.js 22, contra MariaDB 10.11 com uma conta de banco configurada como a nossa (20 conexões, limite de consulta de 120 segundos) e um substituto do servidor Bot API do Telegram: 8 comandos entrando, 8 respostas saindo, as notas salvas no banco. Um login real no Telegram não fez parte do teste.

O que você precisa

1. Crie o banco de dados

  1. Abra o servidor do seu bot no painel e clique em Bancos de dados.
  2. Clique em Novo banco de dados. Um Rótulo é opcional; Será criado como mostra o nome completo. Clique em Criar banco de dados.
  3. A página do novo banco abre (depois, clique nele em Seus bancos de dados). O cartão Conexão mostra Host, Porta, Banco de dados, Nome de usuário e Senha. Em Config pronta para usar, Config para tem um trecho Node.js (mysql2) com os mesmos valores já preenchidos.

2. Coloque os dados em Ambiente

  1. Abra a página Bot , painel Ambientee clique em Colar um arquivo .env.
  2. Cole estas cinco linhas com os valores do cartão Conexão :
    DB_HOST=your-host
    DB_PORT=3306
    DB_NAME=your-database
    DB_USER=your-username
    DB_PASSWORD=your-password
  3. DB_PASSWORD ganha um selo Segredo : ela é salva só para escrita e nunca mais é mostrada. Clique em Importar 5 variáveis.

Use a porta real do cartão Conexão . O token do bot não é uma dessas linhas: o token que você salvou na página Bot , painel Telegram, já chega ao seu código como TELEGRAM_BOT_TOKEN, o nome que o código abaixo lê (e como BOT_TOKEN, TOKEN, TELEGRAM_TOKEN e TELOXIDE_TOKEN). Se o seu arquivo .env tiver uma linha de token, a janela a ignora. Mais sobre este painel: Como definir variáveis de ambiente e segredos para o seu bot do Discord.

3. O código

Um pequeno bot de notas: /note buy milk salva uma nota, /notes mostra suas ú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);
});

O que ele faz: main() cria a tabela notes se ela não existir e imprime Database ready antes de bot.start() pedir mensagens ao Telegram. O pool é criado uma vez, no topo do arquivo, e todo comando o usa: pool.execute() pega uma conexão emprestada, executa a consulta e devolve a conexão. ctx.match é o texto depois do comando: o grammY remove os espaços na frente dele e mantém o resto como foi digitado, quebras de linha incluídas. bot.catch() importa aqui: a documentação do grammY diz que, sem um tratador de erros, um erro em um handler interrompe um bot iniciado com bot.start(), e um erro de banco de dados é exatamente esse tipo de erro.

Os marcadores ? enviam a nota ao banco separadamente do SQL, então uma nota como '); DROP TABLE notes; -- é guardada como texto simples. No nosso teste ela foi salva como a linha 2 e a tabela continuou lá. Nunca monte o SQL com uma string de template.

Guarde os IDs do Telegram como BIGINT

A documentação da Bot API do Telegram diz que um ID de usuário ou de chat “pode ter mais de 32 bits significativos”, mas no máximo 52, então um inteiro de 64 bits é seguro. A página sobre IDs traz as faixas: IDs de usuário vão até 1.099.511.627.775, enquanto IDs de grupos, supergrupos e canais são negativos. Supergrupos e canais vão de -1.000.000.000.001 a -1.997.852.516.352 (por exemplo -1001234567890). O tipo INT do MySQL para em 2.147.483.647. No nosso teste, com o mesmo modo SQL estrito que o nosso servidor de banco usa, salvar o ID de usuário 7.000.000.001 em uma coluna INT falhou. O mysql2 deu o código de erro ER_WARN_DATA_OUT_OF_RANGE (errno 1264) com esta mensagem:

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

Uma coluna BIGINT o guardou, e -1001234567890 também. Use BIGINTsimples, não BIGINT UNSIGNED, para que os IDs de chat também caibam.

4. Execute

  1. Envie index.js e package.json em Files. Não envie node_modules.
  2. Pressione Iniciar e abra Console. Ao iniciar, os pacotes de package.jsonsão instalados e depois aparecem [PlayNHost] Starting your bot, Database ready e Logged in as @your_bot. Essa última linha muda o status em Visão geral para Online no Telegram.
  3. No Telegram, envie ao seu bot /note buy milke depois /notes. Para ver as linhas, abra a página do banco e clique em Abrir no phpMyAdmin.

No nosso teste, o substituto do Telegram enviou estes comandos e recebeu estas respostas:

EnviadoResposta
/note buy milkSaved. Send /notes to see your last five.
/noteWrite your note after the command, for example: /note buy milk
/note e 501 caracteresA note can be up to 500 characters.
/notesAs notas salvas, as mais recentes primeiro
/notes de um segundo usuárioYou have no notes yet.

Muitas mensagens ao mesmo tempo

A documentação do grammY diz que bot.start() processa todas as atualizações em sequência: um comando termina antes de o próximo começar. No nosso teste, o bot manteve 1 conexão aberta depois de responder aos 8 comandos. connectionLimit: 5 importa quando o seu código executa consultas em paralelo, ou se você passar para o grammY runner, que processa as atualizações de forma concorrente: o pool nunca abre mais de 5 conexões, e uma consulta extra espera por uma livre.

Uma consulta que falha não derruba o bot

No nosso teste, apagamos a tabela com o bot rodando e enviamos /notes. bot.catch() imprimiu Error while handling update 500001: Error: Table '...notes' doesn't exist com code: 'ER_NO_SUCH_TABLE', o bot não enviou resposta a esse comando e continuou rodando: quando a tabela voltou, o próximo /note foi salvo. Quem enviou o comando não vê nada, então, se uma consulta pode falhar, envolva-a em try/catch no handler e responda com uma mensagem curta.

Uma noite calma não quebra o pool

Nosso servidor de banco fecha uma conexão que ficou ociosa por 8 horas. Um bot que não recebe mensagens por tanto tempo, de madrugada por exemplo, encontra as conexões do pool fechadas. O pool do mysql2 resolve isso sozinho. Testamos com o limite de ociosidade reduzido para 5 segundos: após 7 segundos de silêncio, o próximo /note foi salvo sem erro.

Limites que importam para o seu pool

  • 20 conexões ao mesmo tempo por usuário do banco. Todo processo que usa o mesmo login conta. A conexão 21 é recusada com ER_USER_LIMIT_REACHED. No nosso teste, um pool com connectionLimit: 30 falhou com esse erro quando 30 consultas rodaram ao mesmo tempo. Um pool de 5 mantém o bot bem abaixo disso.
  • Uma consulta que roda por mais de 120 segundos é interrompida. O MariaDB a encerra com o erro 1969, Query execution was interrupted (max_statement_time exceeded). Em um teste anterior nosso, em uma conta configurada do mesmo jeito, isso aconteceu depois de 120,03 segundos. As consultas de um bot normalmente levam milissegundos, então isso só atinge uma consulta descontrolada.
  • 500 MB por banco de dados. A página Bancos de dados mostra quanto está em uso. Em 500 MB, as gravações são interrompidas até você apagar linhas.
  • Os backups do servidor não incluem o banco de dados. Exporte-o do phpMyAdmin de vez em quando.

Erros e o que significam

Estes são os códigos de erro que o mysql2 deu no nosso teste:

  • ER_ACCESS_DENIED_ERROR ... Access denied for user: DB_USER ou DB_PASSWORDincorretos. Depois de Redefinir senha no cartão Conexão a senha antiga para de funcionar na hora: troque DB_PASSWORD e reinicie o bot.
  • ECONNREFUSED: nada aceitou a conexão nesse host e porta. Confira DB_HOST e DB_PORT. Um DB_HOST ausente também termina aqui, porque o mysql2 então se conecta a localhost, onde nenhum banco roda: a variável está faltando em Ambiente, ou o bot não foi reiniciado depois de adicioná-la.
  • ENOTFOUND: DB_HOST está escrito errado, então o nome não existe.
  • ETIMEDOUT: nada respondeu a tempo. Seja qual for a mensagem, uma conexão do seu próprio computador falha: o banco só aceita conexões de servidores da PlayNHost e do phpMyAdmin.
  • ER_USER_LIMIT_REACHED ... has exceeded the 'max_user_connections' resource (current value: 20): conexões abertas demais, veja os limites acima. Por exemplo, uma segunda cópia do bot ou um pool criado para cada mensagem.
  • ER_WARN_DATA_OUT_OF_RANGE (errno 1264, Out of range value for column 'user_id' at row 1): uma coluna de ID é INT. Mude-a para BIGINT.
  • ER_NO_SUCH_TABLE (errno 1146): a tabela não existe. O código acima a cria na inicialização; se você a fez à mão, confira o nome.

Um token errado aparece depois da parte do banco. Com o nosso token falso contra o Telegram real, o bot imprimiu Database readye parou depois de cerca de um quarto de segundo com GrammyError: Call to 'deleteWebhook' failed! (401: Unauthorized). Copie o token de novo do @BotFather e cole na página Bot , painel Telegram. Mais erros do Telegram: Como corrigir o erro 409 Conflict do bot do Telegram.

Quanta memória ele usa

No nosso teste, o bot em execução usou 67,6 MB de memória (residente) depois de responder aos 8 comandos, com o pool aberto, em um contêiner limitado a 512 MB como o nosso menor plano de bot. No nosso teste de hospedagem anterior, o grammY sozinho media 60 MB (aquele bot parou em um token recusado, antes de tratar qualquer mensagem), então a diferença, cerca de 8 MB, cobre o mysql2, o pool e os comandos tratados. Instalar grammY e mysql2 com npm do zero chegou a um pico de 140 MB, incluindo o cache de arquivos.

A mesma coisa com outra biblioteca

Precisa de um servidor para o seu bot do Telegram com banco de dados incluído? Veja os planos de bots do Telegram.

Seu bot já está em outro lugar? Traga-o para nós

NOSSOS ÚLTIMOS ARTIGOS