Build a Telegram Bot using TypeScript, Node.js, and Telegraf and deploy it on Heroku

Alberto Piras
Geek Culture
Published in
10 min readOct 29, 2021
Photo by Joan Gamell on Unsplash

Telegram allows developers to build third-party applications and run them within its platform. It provides the Bot API, a well-documented HTTP-based interface, and supplies many sample bots written in several languages as examples to get started rapidly.

However, there are a lot of different libraries, and it can be overwhelming and time-consuming to choose which one to use, understand it and then find a hosting to deploy the bot to access it at any time.

In this article, I’ll explain how to write a Telegram bot using Node.js with TypeScript and how to deploy it on Heroku.

Development environment

I am using a computer with Linux Mint 20.2 and Visual Studio Code 1.61.2 to write the bot.

The project will use the following dependencies. I’ll explain later how to install them.

  • Node.js: 17.0.1
  • npm: 8.1.0
  • typescript: 4.4.4
  • telegraf: 4.4.2
  • @types/node: 16.11.2
  • eslint: 8.1.0

You can see and download the project as described below from this GitHub Repository.

The Node.js project

Install Node.js

The first step is to install Node.js from its official website. On Linux, I used the Node Version Manager (nvm).

I installed nvm using the following command.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

And then installed node with the next one.

nvm install node

Create the project

The second step is to start a new project. Create a directory for your project, open it and run the following command in the terminal to initialize it.

npm init

It will ask for some information regarding the project. You can add the -y option to the previous command to use the default values.

You can find more information on how to create a Node.js project on the Getting Start guide.

At this point, the project should look like this commit.

Add TypeScript

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.

Add TypeScript to the project with the following commands.

npm install -D typescript
npm install -D tslint

The -D is a shortcut for --save-dev. These commands install the latest TypeScript versions; you can specify the desired ones by adding @version at the end of the dependency name, for example,npm install -D typescript@4.4.4.

Configure TypeScript

The next step is to configure the compiler options. Add a file tsconfig.json in the root of the project and add the following content.

{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es2021",
"moduleResolution": "node",
"strict": true,
"sourceMap": true,
"lib": ["es2021"],
"skipLibCheck": true,
"rootDir": "src",
"outDir": "dist"
}
}

It defines:

  • module: specifies the generation method. Node uses commonjs.
  • esModuleInterop: when true, it generates additional files to ease support for importing CommonJS modules.
  • target: is the JavaScript language version for emitted JavaScript files.
  • moduleResolution: specifies how TypeScript looks up a file from a given module specifier.
  • strict: enables all strict type-checking options.
  • sourceMap: when true, it generates additional JavaScript files to make debugging easier.
  • lib: specifies the libraries available on the target runtime environment.
  • skipLibCheck: skip type checking all .d.ts files, which are information for TypeScript about API written in JavaScript.
  • rootDir: specifies the source folder. I’ll add my source files inside the src folder.
  • outDir: specifies the output folder for the generated JavaScript files.

Alternatively, it is possible to create this file using the following command.

tsc --init

You can read more about it on the TypeScript Documentation and the JSON Schema.

You can optionally configure TypeScript linting for the project. The TypeScript ESLint documentation describes how to do it and how it works.

It is also possible to define the editor configuration for your project using EditorConfig. This way, your IDE will follow the rules defined in the .editorconfig file.

Update the Node.js configuration

The next step is to configure the Node.js project to build the TypeScript code. Update the package.json as follows.

{
...
"main": "dist/app.js",
"scripts": {
"build": "tsc",
"start": "node dist/app.js",
...
},
...
}
  • main: TypeScript generates the JavaScript entry point inside the outDir. Update this value so that it points to the JS file generated inside the output directory.
  • scripts.build: contains the command tsc that compiles the TypeScript files. You can run this script with npm run build.
  • scripts.start: contains the command to run the project. You can run this script with npm start.

At this point, the project should look like this commit.

Telegraf

Telegram provides APIs that allow developers to write bots. You can find them all on the Telegram Bot API page.

These APIs require a token to interact with them. It is possible to request one by using BotFather, as explained in the official guide. You can see a screenshot of the process in my other story Tasker and Telegram integration.

Telegraf is a well-documented and widely-used library for developing bots using Node.js. Along with many others, it makes the interaction with the APIs easier than sending HTTP requests. You can find all of them, for Node.js and other languages, on this Telegram page.

Use the following command to install the dependency.

npm install telegraf

Write a simple bot

The next step is to write some logic for the bot. You can see the documentation on the telegraf.js page, where you can also copy an example from the Shorthand methods paragraph.

Import the library at the very beginning and then create a new Telegraf object as follows.

import { Context, Telegraf } from 'telegraf';
import { Update } from 'typegram';
const bot: Telegraf<Context<Update>> = new Telegraf(process.env.BOT_TOKEN as string);

The IDE will show an error with the process object. Install the type definitions for nodes with the following command to solve them.

npm i --save-dev @types/node

By using process.env.BOT_TOKEN, it is possible to provide the Telegram token through an environment variable. This way, you don’t need to write it in the code and push it, for example, on your repository.

At this point, it’s time to define the message handlers. You can see all of them on the Telegraf | telegraf.js page. Some examples are:

  • on: listens for the specified event.
  • start: listens for the /start command.
  • help: listens for the /help command.
  • command: listens for the specified command.

Here is an example of handlers.

bot.start((ctx) => {
ctx.reply('Hello ' + ctx.from.first_name + '!');
});
bot.help((ctx) => {
ctx.reply('Send /start to receive a greeting');
ctx.reply('Send /keyboard to receive a message with a keyboard');
ctx.reply('Send /quit to stop the bot');
});
bot.command('quit', (ctx) => {
// Explicit usage
ctx.telegram.leaveChat(ctx.message.chat.id);
// Context shortcut
ctx.leaveChat();
});
bot.command('keyboard', (ctx) => {
ctx.reply(
'Keyboard',
Markup.inlineKeyboard([
Markup.button.callback('First option', 'first'),
Markup.button.callback('Second option', 'second'),
])
);
});
bot.on('text', (ctx) => {
ctx.reply(
'You choose the ' +
(ctx.message.text === 'first' ? 'First' : 'Second') +
' Option!'
);
});

You can find more about the keyboard on the Markup and Markup > button pages of the documentation.

After the declaration of the handlers, it is possible to start the bot.

bot.launch();

It is essential to declare the handlers before starting the bot. Otherwise, they will not be registered and, therefore, will not work.

Add these instructions if you want to graceful shutdown the bot when stopping the Node.js process.

process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));

You can also use the methods described in the Telegram Bot API page using the Telegram component. With this, it is possible to send messages without direct user interaction.

import { Telegram } from 'telegraf';const telegram: Telegram = new Telegram(process.env.BOT_TOKEN as string);const chatId = '...';telegram.sendMessage(
chatId,
'This message was sent without your interaction!'
);

Keep in mind that Telegram allows bots to only send messages to users who have interacted with it.

You can find the complete app.ts in the GitHub repository at this commit.

Build the project

Use the build script added previously to the package.json to build the project.

$:~/Telegram-Bot_Node.js$ npm run build> telegram-bot_node@1.0.0 build
> tsc

It will create an app.js file inside the dist directory defined in the outDir parameter in the tsconfig.json.

You can also continuously build the project as explained in the documentation with the following command.

tsc --watch

Run the project

The bot requires a BOT_TOKEN env variable to run. Declare it and run the start script added previously to the package.json as follows.

BOT_TOKEN="110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" npm start

The bot will start and allow you to interact with it.

Remember to replace the token in the command, which is invalid, with the one you got from BotFather.

Heroku

You will need to find a hosting provider to keep the bot running all the time.

There are really a lot of providers, with different plans and services. You can find a good list on this GitHub repository and in the Node.js section the ones that provide Node.js hosting.

In my opinion, the provider with the best free plan is Heroku. It allows you to run up to 5 apps and gives you 550 dyno hours/month.

There are two types of dyno:

  • Worker dyno: executes tasks in the background and does not respond to web requests. Apps that only utilize a free worker dyno do not sleep; they run 24/7.
  • Web dyno: exposes a website and sleeps if it receives no web traffic in a 30-minute period. In addition to the web dyno sleeping, the worker dyno (if present) will also sleep. Free web dynos do not consume free dyno hours while sleeping.

A bot is a worker dyno that runs continuously for up to 550 hours/month (~22 days/month). If you add a card to your account, the limit goes up to 1000 hours/month (~42 days/month).

When you use all your free dyno hours for a given month, Heroku will force all your apps to sleep for the rest of the month.

You can read more about the free plan here.

Add deployment information

Heroku requires some additional configuration in the project to deploy it correctly.

The first thing to add is the node and npm versions in the package.json. Add an engines object with node and npm keys and their respective versions as follows.

{
...
"engines": {
"node": "17.0",
"npm": "8.1"
},
...
}

Heroku looks in the package.json for the following scripts.

  • build script: lets the developer specify the steps to build the project.
  • start script: Heroku uses it to boot the app.

It also looks for a file called Procfile that configures the deployment steps, optionally including the build and start instructions. You can read how it works and how to configure it on the Procfile documentation.

Add the Procfile in the root of your project with the following content. It specifies that the application is a worker dyno and how to start it.

worker: node dist/app.js

At this point, the project should look like this commit.

You can read the Deploying Node.js Apps on Heroku documentation to find more information about these steps.

Create a Heroku app

Create an account if you don’t have one yet, and then log in to Heroku.

Click on the New button and select Create new app.

Type your App name, select the region where you want to deploy it, and click the Create app button.

Next, Heroku shows the Deploy tab, which offers three deployment methods. Two of them require the Heroku CLI, while the other one deploys a GitHub repository. I’ll use the GitHub method.

Click the GitHub option and, once you have connected your GitHub account, search and connect the repository you want to deploy.

Before deploying the application, it’s necessary to define the environment variable that stores the Telegram token. Open the Settings tab and click the Reveal Config Vars button.

Define a new environment variable identified by the BOT_TOKEN key and set the token received by BotFather as its value.

Deploy

The environment is now ready, so the next step is to deploy the application.

Go back to the Deploy tab. You can decide to use the following options for deployment.

  • Automatic deploys: Heroku monitors the selected branch and deploys it on every commit.
  • Manual deploy: you can trigger the deployment manually.

It’s possible to configure the Automatic deploys and also manually trigger the deployment. I’ll manually deploy my dev branch.

At the end of the deployment, go to the Resource tab. Disable the web dyno and enable the worker.

It is now possible to interact with the bot.

--

--

Alberto Piras
Geek Culture

Software development engineer at Amazon. Thoughts and articles are my own.