DEV Community

Cover image for Telegraf VS Node-Telegram-Bot-API
makkentoshh {{☕}}
makkentoshh {{☕}}

Posted on • Edited on

Telegraf VS Node-Telegram-Bot-API

Today the most popular ways to build telegram chat bots in node js are Telegraf and Node-Telegram-Bot-Api. The second is more popular by weekly downloads(250.000), but in this post I don't want to equalize them by stats. I want to talk about my experience with them, in which cases I used them and show you really nice guide, especially for Telegraf, cause they haven't readable docs for newbies or people who don't use typescript.

Node Telegram Bot API

So, let's start with this package. It is really easy, but due to it we can't use it in really big projects, cause it will be hard to understand structure of bot without comments or million javascript files.

To download type:

npm i node-telegram-bot-api
Enter fullscreen mode Exit fullscreen mode

After this, as said in documentation, you need to initialize it with your telegram token, which you received from Bot Father

const TelegramBot = require('node-telegram-bot-api');

const token = 'YOUR_TELEGRAM_BOT_TOKEN';

// Create a bot that uses 'polling' to fetch new updates
const bot = new TelegramBot(token, {polling: true});
Enter fullscreen mode Exit fullscreen mode

And there are two main methods in this package - bot.sendMessage(chatId, message, options) and bot.on("one of params from api like message or text", function which contains message param and returns response)

bot.on('message', (msg) => {
  const chatId = msg.chat.id;

  // send a message to the chat acknowledging receipt of their message
  bot.sendMessage(chatId, 'Received your message');
});
Enter fullscreen mode Exit fullscreen mode

Here is message object with all fields. So, if we need to react on /start command we need to make something like this:

bot.on("message", async function (msg) {
  const text = msg.text;
  const chatId = msg.chat.id;
  if (text === "/start") {
    bot.sendMessage(chatId, `Hello ${msg.from.username}! Choose your language...`, 
   reply_markup: {
       one_time_keyboard: true,
       keyboard: [["ua"], ["en"]],
   },);
  }
});
Enter fullscreen mode Exit fullscreen mode

We checked if our text is equal to our command and responded with message. It is all main logic from this package, more sendMessage methods or params you can see here. Also we used reply_markup option in which we can use our keyboard, which will be sent with our message. And then we can check if text equal to "en" we will change our language to english and response with message.

In addition, if you have a question like can you send message without getting message from user - you can do it. All that you need is to save your user to database like MongoDB with chatId field and use something like express to make CRUD methods to send your message. Here is my example of this 'bot mailer':

app.get("/", async (req, res) => {
  await TelegramUser.find()
    .cursor()
    .eachAsync(async (user) => {
      bot.sendMessage(user.chatId, "<b>Hi!</b>", {
        parse_mode: "HTML",
        disable_web_page_preview: false,
      });
    });
});
Enter fullscreen mode Exit fullscreen mode

So here we sent our message "Hi" in async forEach mongodb method when we got request to our app. TelegramUser is a mongodb model, where I store user chatId. Also you can see two the most useful options from this package - parse_mode, which parses our message to HTML(but can't parse all tags, like img, video, cause there are methods like sendPhoto, sendVideo) and disable_web_page_preview, which you can use when in message you send link and you don't want to see preview of it.
I hope you got it, let's move to telegraf.

Telegraf

Let's talk about telegram bot library which I prefer to use in big projects.
To download:

npm i telegraf
Enter fullscreen mode Exit fullscreen mode

In this library we can see similar methods with node-telegram-bot-api:

const { Telegraf } = require('telegraf')

const bot = new Telegraf(process.env.BOT_TOKEN)
bot.start((ctx) => ctx.reply('Welcome'))
bot.help((ctx) => ctx.reply('Send me a sticker'))
bot.on('sticker', (ctx) => ctx.reply('👍'))
bot.hears('hi', (ctx) => ctx.reply('Hey there'))
bot.launch()
Enter fullscreen mode Exit fullscreen mode

To initialize we use new Telegraf(token). To react on /start command we type bot.start which is more readable than in previous package. Here instead of message object we have ctx - context object, which almost doesn't have big difference from first one but has many another methods.
ctx.reply is function with response message, here if we want to parse HTML or reply with audio or photo etc. we can use methods like ctx.replyWithHTML or ctx.replyWithPhoto, all methods you can see in previous link.
bot.on is the same as in node-telegram-bot-api, all params which can receive this function you can see here.
bot.hears is just a function which reacts on user's message.
And to launch our bot we use bot.launch()
It would seem that it is the end, but in fact it is one part of opportunities which this library gives. Here you can use scenes.

Telegraf Scenes

Let's imagine that telegram bot is a theatre with one viewer - our user. When performance starts, the first scene is beginning, let's name it start scene. There are some actions take place in this scene for some time and then the scene ends, in another words we left the scene. So this interval from beginning of our scene and ending is our scene.
Telegraf Scene is the same, but let's display it in code.
This library has two types of scene: Base Scene and Wizard Scene.

To set up our scenes we need to register them with Stage. Let's do it in our main file.

app.js

const { Scenes, Telegraf } = require("telegraf");
const start = require("./controllers/start"); // scene file 
const about = require("./controllers/about"); // scene file 
const settings = require("./controllers/settings"); // scene file 
const contact = require("./controllers/contact"); // scene file 
const search = require("./controllers/search"); // scene file 


const bot = new Telegraf(process.env.TELEGRAM_TOKEN);
const stage = new Scenes.Stage([start, about, settings, contact, search]); // Register our scenes
bot.use(stage.middleware()); // Stage middleware
bot.hears("settings", Scenes.Stage.enter("settings")); // Entering the settings scene when listener worked
Enter fullscreen mode Exit fullscreen mode

Here we imported our scenes and registered them in array. Also we added a stage middleware without which our scenes won't work. And to enter our scene we set bot listener with bot.hears and then as a first param we type our scene id(see in next paragraph) and then we enter the scene with Scenes.Stage.enter.

Base Scene

Here is an example of this scene:
settings.js

const { Scenes } = require("telegraf");
const Scene = Scenes.BaseScene;
const { getMainKeyboard, getBackKeyboard } = require("../../util/keyboards");
const { leave } = Scenes.Stage;
const settings = new Scene("settings");

settings.enter(async (ctx) => {
  const { backKeyboard } = getBackKeyboard();
  await ctx.reply("settings.what_to_change", backKeyboard);
});
settings.leave(async (ctx) => {
  const { mainKeyboard } = getMainKeyboard();
  await ctx.reply("shared.what_next", mainKeyboard);
  await ctx.scene.leave();
});
settings.hears("Back", leave());
settings.action("backButtonId", console.log("Back button was clicked"));
module.exports = settings;

Enter fullscreen mode Exit fullscreen mode

../../util/keyboards

const { Markup } = require("telegraf");

exports.getMainKeyboard = () => {
  let mainKeyboard = Markup.keyboard([
    ["Movies", "Settings"],
  ]);
  mainKeyboard = mainKeyboard.oneTime();

  return mainKeyboard;

};
exports.getBackKeyboard = () => {
  let backKeyboard = Markup.keyboard(["Back"]);
  backKeyboard = backKeyboard.oneTime();
  return backKeyboard;
};

Enter fullscreen mode Exit fullscreen mode

It is file with our settings scene. To initialize it, we use new Scene("scene id"). When user entered our scene he will receive response from settings.enter(async function(ctx)), in which we make our first step of scene. Then all actions in our scene will be in settings.on or in settings.hears. Then, when scene is ending we use settings.leave in which we have function with moving back to default scene(start scene) and ctx.scene.leave(), which is required to leave scene. Without it you have a risk to stay in this scene forever.
Also, if we have keyboard, which can be created by Markup.keyboard, all our buttons there are strings in array. If we want to make many rows in our keyboard, we can make many arrays, like if we have two arrays with two strings(buttons), we will get keyboard with 2 rows with 2 buttons in each.
When we tap on button, we can handle this tap in scene.action(buttonId), where buttonId is our button's second param in Markup.button.callback. Also we can create inline keyboard with Markup.inlineKeyboard

Markup.inlineKeyboard(
  [
    Markup.button.callback("Back", "backButtonId"),
  ],
  {}
)
  .oneTime()
  .resize();

Enter fullscreen mode Exit fullscreen mode

Wizard Scene

All logic of this scene is the same as in previous, but here we have a chain with scene's steps. To go to our next step we use ctx.wizard.next and ctx.wizard.steps[ctx.wizard.cursor](ctx) too, cause in last versions of Telegraf without it our step won't change. To leave from scene we use ctx.scene.leave

const { Scenes } = require("telegraf");
const { getMainKeyboard } = require("../../util/keyboards");
const Wizard = Scenes.WizardScene;
const about = new Wizard(
  "about", // Our wizard scene id, which we will use to enter the scene
  async (ctx) => {
    await ctx.reply(ctx.i18n.t("scenes.about.main"));
    ctx.wizard.next();
    return ctx.wizard.steps[ctx.wizard.cursor](ctx);
  },
  async (ctx) => {
    const { mainKeyboard } = getMainKeyboard(ctx);
    await ctx.reply(ctx.i18n.t("shared.what_next"), mainKeyboard);
    return ctx.scene.leave();
  }
);
module.exports = about;

Enter fullscreen mode Exit fullscreen mode

Session

And the last main thing in this library is session. It is like local storage in web but for telegram bots. Here we can save params that we need to save and use them in future. To set up, let's change our main file:

const { Scenes, session, Telegraf } = require("telegraf");
const start = require("./controllers/start"); // scene file 
const about = require("./controllers/about"); // scene file 
const settings = require("./controllers/settings"); // scene file 
const contact = require("./controllers/contact"); // scene file 
const search = require("./controllers/search"); // scene file 


const bot = new Telegraf(process.env.TELEGRAM_TOKEN);
const stage = new Scenes.Stage([start, about, settings, contact, search]); // Register our scenes
bot.use(session()); // Session middleware
bot.use(stage.middleware()); // Stage middleware
bot.hears("settings", Scenes.Stage.enter("settings")); // Entering the settings scene when listener worked
Enter fullscreen mode Exit fullscreen mode

Here we imported our session from telegraf and made a session middleware. Then we can use it in our scenes. For example:

const { Scenes, session } = require("telegraf");
const { getMainKeyboard, getBackKeyboard } = require("../../util/keyboards");
const { User } = require("../../models");
const { getMoviesBySearch, getMovieKeyboard } = require("./helpers");
const Base = Scenes.BaseScene;
const search = new Base("search");
const { leave } = Scenes.Stage;

search.enter(async (ctx) => {
  const backKeyboard = getBackKeyboard(ctx);
  await ctx.replyWithHTML("scenes.search.welcome_to_search",
    backKeyboard
  );
});

search.hears("Back"), leave());
search.on("text", async (ctx) => {
  const user = await User.findById(ctx.from.id);
  const movies = getMoviesBySearch(user.language, ctx.message.text);
  const movieKeyboard = getMovieKeyboard(ctx);
  ctx.session.movies = movies; // Set session field
  ctx.session.index = 0; // Set session field
  ctx.replyWithHTML(``, movieKeyboard);
});

search.leave(async (ctx) => {
  const { mainKeyboard } = getMainKeyboard(ctx);
  await ctx.reply("shared.what_next", mainKeyboard);
  delete ctx.session.index; // Delete session field
  delete ctx.session.movies; // Delete session field
  await ctx.scene.leave();
});
module.exports = search;
Enter fullscreen mode Exit fullscreen mode

Conclusions

In this post I explained how to deal with the most popular js telegram libraries step by step. As I said, I prefer Telegraf to Node-Telegram-Bot-Api because it is more interesting and readable. If you have any thoughts about it, write at the comments below and correct me if I made some mistakes in this post, cause I don't speak English fluently 😅

In addition

Today in my country, in Ukraine, is a war, which caused by Russia's aggression. I want to have a peace in my country, our army does their best and it would be great if you can support us writing #nowarinukraine in your dev posts. Thanks :)

Latest comments (15)

Collapse
 
caaza profile image
Caaza

Thank you for this post. I was able to get a solid intro to these. It's been 2 years since you wrote this article.

Do you still think that Telegraf is the best option?

nowarinukraine

Collapse
 
algora profile image
Algora

Where do we hire devs for our complex telegram bot? Is there a place for this here on this site?

Collapse
 
maklut profile image
makkentoshh {{☕}}

Yeah, my freelance account :) contra.com/makar_lutskyi_acx1fag2

Collapse
 
kytnick profile image
KYTNICK

Hello, I need a bot that will ask the user for his name and number, how and where I can get this data in order to call or write to the user in the future. Слава Україні!

Collapse
 
hellohost profile image
Denis • Edited

Bro, I didnt understand, if telegraph has more functions (sessions, scenes, etc.), why do you use Node-Telegram-Bot-Api? what makes it more interesting?

Collapse
 
maklut profile image
makkentoshh {{☕}}

I think nothing makes Node-Telegram-Bot-Api more interesting, I haven't wrote about that. I think it is easy to use for small projects, so for telegram bot with small logic, but for big projects where there are many scenes and user interaction with bot - it is easier to use telegraph

Collapse
 
vedmalex profile image
Vedanta-krit das (Alex Vedmedenko)

there is new BOSS in telegram api :) grammyjs
is use coolest conversation api ever 627d0a590e3a8b0b2ba822a1--grammy.n...

Collapse
 
iconicspidey profile image
Big Spidey🕷️

thank you

Collapse
 
vedmalex profile image
Vedanta-krit das (Alex Vedmedenko)

Awesome description of all needed features

Collapse
 
maklut profile image
makkentoshh {{☕}}

Thanks! 😊

Collapse
 
kkkholmes profile image
kkkholmes

Hi bro! I am from Ukraine too and i'll ask you on our native language
Використовую telegraf і хочу відправити повідомлення у чат без команди. Знайшов наче вирішення :
app.telegram.sendMessage(chat.id, 'my text');
Але не знаю звідки дістати chat.id
Був би вдячний за допомогу!

Collapse
 
maklut profile image
makkentoshh {{☕}} • Edited

Привіт, взагалі так просто в коді chatId ти зберігати навряд чи зможеш, тому тобі потрібно використовувати якусь базу данних чи сесію, наприклад MongoDB, вище в пості я наглядно показав приклад коду з node-telegram-bot-api, який через forEach проходить усіх юзерів(TelegramUser - модель юзерів, тобто схема, де і потрібно зберігати поле chatId). Зберігати chatId потрібно при реєстрації юзера, тобто його першого повідомлення в чат, трохи вище ніж приклад з mongoDB. chatId є у msg.chat.id. Якщо раніше не працював з mongoDB, то можеш подивитися туторіали на ютуб, бажано англійською, бо там набагато якісніший матеріал) Надіюсь зрозуміло пояснив 😊

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more