Blog Node.js Telegram Bot API: Complete Guide for Developers (2026)
Editorial

Node.js Telegram Bot API: Complete Guide for Developers (2026)

Admin {{ $post->author->username }} 7 min read

Node.js Telegram Bot API: Complete Guide for Developers (2026)

Node.js is one of the most popular runtime environments for building Telegram bots. Its event-driven, non-blocking architecture is a natural fit for the message-handling patterns that bots require, and the npm ecosystem provides excellent Telegram bot libraries. In 2026, two libraries dominate the Node.js Telegram bot landscape: node-telegram-bot-api (the original, widely used) and grammy (the modern, TypeScript-first alternative).

This guide covers both, walking through setup, message handling, inline keyboards, webhooks, and deployment with working code examples throughout. Also see our guides on Telegram Bot Token Security, the BotFather Complete Guide, and the Developer Tools category.

Getting Started with Node.js Telegram Bot Libraries

The two main choices in 2026:

Library npm package TypeScript Stars (2026) Best For
node-telegram-bot-api node-telegram-bot-api Types available ~7,000 Quick scripts, legacy projects
grammy grammy Native TypeScript ~5,000 New projects, TypeScript, scale
telegraf telegraf Good types ~8,000 Express-style middleware pattern

This guide uses grammy for new code examples (it's the most developer-friendly in 2026) and shows node-telegram-bot-api equivalents where relevant.

Setting Up Your First Bot

Prerequisites: Node.js 18+ and npm installed. A bot token from @BotFather.

# Create project
mkdir my-telegram-bot && cd my-telegram-bot
npm init -y

# Install grammy
npm install grammy

# Or if you prefer node-telegram-bot-api:
# npm install node-telegram-bot-api

Create index.js:

const { Bot } = require("grammy");

const bot = new Bot(process.env.BOT_TOKEN); // Never hardcode the token!

// Handle the /start command
bot.command("start", (ctx) => {
  ctx.reply(
    `Hello, ${ctx.from.first_name}! I'm your new bot. Send me a message.`
  );
});

// Handle all text messages
bot.on("message:text", (ctx) => {
  ctx.reply(`You said: ${ctx.message.text}`);
});

// Start the bot (long polling)
bot.start();
console.log("Bot is running...");

Run with:

BOT_TOKEN="1234567890:ABC..." node index.js

Open Telegram, find your bot, send /start — it responds. Your first Node.js Telegram bot is running.

Handling Messages and Commands

Grammy's filter system makes routing messages expressive and type-safe:

const { Bot, InlineKeyboard } = require("grammy");
const bot = new Bot(process.env.BOT_TOKEN);

// Commands
bot.command("start", (ctx) => ctx.reply("Welcome!"));
bot.command("help", (ctx) => ctx.reply("Available commands:\n/start\n/help\n/about"));
bot.command("about", (ctx) => ctx.reply("I'm a demo bot built with grammy."));

// Text matching (exact)
bot.hears("Hello", (ctx) => ctx.reply("Hi there!"));

// Text matching (regex)
bot.hears(/price of (.+)/i, (ctx) => {
  const item = ctx.match[1];
  ctx.reply(`Checking price for: ${item}...`);
});

// All text messages (catch-all)
bot.on("message:text", (ctx) => {
  console.log(`Received: ${ctx.message.text} from ${ctx.from.id}`);
  ctx.reply("Got your message!");
});

// Photos
bot.on("message:photo", (ctx) => {
  const photo = ctx.message.photo.at(-1); // Get highest resolution
  ctx.reply(`Received photo: ${photo.file_id} (${photo.width}x${photo.height})`);
});

// Documents
bot.on("message:document", (ctx) => {
  ctx.reply(`Received file: ${ctx.message.document.file_name}`);
});

bot.start();

Command handling with node-telegram-bot-api equivalent:

const TelegramBot = require("node-telegram-bot-api");
const bot = new TelegramBot(process.env.BOT_TOKEN, { polling: true });

bot.onText(/\/start/, (msg) => {
  bot.sendMessage(msg.chat.id, "Welcome!");
});

bot.onText(/\/help/, (msg) => {
  bot.sendMessage(msg.chat.id, "Available commands:\n/start\n/help");
});

bot.on("message", (msg) => {
  if (msg.text && !msg.text.startsWith("/")) {
    bot.sendMessage(msg.chat.id, `You said: ${msg.text}`);
  }
});

Inline Keyboards in Node.js

Inline keyboards attach buttons to messages. Buttons can trigger callbacks, open URLs, or launch Mini Apps:

const { Bot, InlineKeyboard } = require("grammy");
const bot = new Bot(process.env.BOT_TOKEN);

bot.command("menu", async (ctx) => {
  const keyboard = new InlineKeyboard()
    .text("Option A", "option_a").text("Option B", "option_b")
    .row()
    .url("Visit tgram.bot", "https://tgram.bot")
    .row()
    .text("Cancel", "cancel");

  await ctx.reply("Choose an option:", { reply_markup: keyboard });
});

// Handle button presses
bot.callbackQuery("option_a", async (ctx) => {
  await ctx.answerCallbackQuery(); // dismiss loading indicator
  await ctx.editMessageText("You chose Option A!");
});

bot.callbackQuery("option_b", async (ctx) => {
  await ctx.answerCallbackQuery();
  await ctx.editMessageText("You chose Option B!");
});

bot.callbackQuery("cancel", async (ctx) => {
  await ctx.answerCallbackQuery("Cancelled");
  await ctx.deleteMessage();
});

// Wildcard: match any callback starting with "item:"
bot.callbackQuery(/^item:(.+)$/, async (ctx) => {
  const itemId = ctx.match[1];
  await ctx.answerCallbackQuery();
  await ctx.reply(`Loading item ${itemId}...`);
});

bot.start();

Webhooks vs Polling in Node.js

Bots receive updates from Telegram in two ways:

Long Polling (development)

Your bot repeatedly asks Telegram "any new messages?" every few seconds. Simple to set up — no public URL required. Best for development. The bot.start() call in grammy uses polling by default.

Webhooks (production)

Telegram pushes updates to your server in real time via HTTP POST. Requires a public HTTPS URL with a valid TLS certificate. Better for production — lower latency, no polling overhead.

// Webhook setup with grammy + Express
const { Bot, webhookCallback } = require("grammy");
const express = require("express");

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

// Register your handlers here
bot.command("start", (ctx) => ctx.reply("Hello!"));

const app = express();
app.use(express.json());

// Mount the webhook handler at your path
app.use(`/bot${process.env.BOT_TOKEN}`, webhookCallback(bot, "express"));

app.listen(3000, async () => {
  // Tell Telegram where to send updates
  const webhookUrl = `${process.env.PUBLIC_URL}/bot${process.env.BOT_TOKEN}`;
  await bot.api.setWebhook(webhookUrl);
  console.log(`Webhook set to ${webhookUrl}`);
});

For local development with webhooks, use ngrok to expose your local port with a public HTTPS URL:

ngrok http 3000
# Note the https URL it gives you, set it as PUBLIC_URL

Deploying Your Node.js Telegram Bot

Option 1: VPS with PM2

PM2 is a process manager for Node.js that keeps your bot running and restarts it on crashes:

npm install -g pm2

# Start the bot
pm2 start index.js --name "telegram-bot"

# Auto-start on server reboot
pm2 startup
pm2 save

# Monitor
pm2 status
pm2 logs telegram-bot

Option 2: Railway / Render (free tiers)

Both Railway and Render offer free tiers suitable for small bots. Push your code to GitHub, connect the repository, set your BOT_TOKEN as an environment variable, and deploy. Use webhooks (not polling) on these platforms — polling consumes more CPU and most free tiers have usage limits.

Option 3: Cloudflare Workers (serverless)

For lightweight bots without persistent state, Cloudflare Workers run your bot logic at the edge globally with a generous free tier (100,000 requests/day). Grammy has a first-class Cloudflare Workers adapter:

// worker.js
import { Bot, webhookCallback } = from "grammy";

const bot = new Bot(BOT_TOKEN); // Cloudflare env variable
bot.command("start", (ctx) => ctx.reply("Hello from the edge!"));

export default {
  async fetch(req, env) {
    const cb = webhookCallback(bot, "cloudflare-mod");
    return cb(req);
  }
};

FAQ

Grammy vs node-telegram-bot-api: which should I use in 2026?

For new projects, grammy. It's TypeScript-native, actively maintained, has a plugin ecosystem (sessions, conversations, menus), and handles edge cases like flood control better. node-telegram-bot-api is fine for simple scripts and has more Stack Overflow answers due to its age, but grammy is the modern standard.

How do I store user data between conversations?

Grammy has a sessions plugin: npm install @grammyjs/storage-redis for Redis-backed sessions, or use SQLite for simple persistence. For stateless bots on serverless platforms, use an external database (PlanetScale, Supabase, Upstash).

My bot works locally but messages aren't received on the server. What's wrong?

Most likely a webhook vs polling conflict. You can only use one at a time. If you previously set a webhook and now want polling, clear it first: await bot.api.deleteWebhook(). If you're using webhooks and messages aren't arriving, check that your server's URL is publicly reachable via HTTPS and the TLS certificate is valid.

How do I handle multiple languages in my bot?

Check ctx.from.language_code which contains the user's Telegram language setting (e.g. "en", "de", "ru"). Use an i18n library like i18next or Grammy's internationalization plugin (@grammyjs/i18n) to serve translated messages based on this code.

Is Node.js or Python better for Telegram bots?

Both are excellent. Python (python-telegram-bot, aiogram) has slightly more tutorials and examples online due to its popularity in the bot community. Node.js (grammy, telegraf) has better performance for high-concurrency scenarios and superior TypeScript support. Choose whichever language you're more comfortable with — the bot capabilities are identical.

Share this article

Share on X