Hosting: Heroku
We assume that you have the basic knowledge about creating bots using grammY. If you are not ready yet, don’t hesitate to head over to our friendly Guide! 🚀
This tutorial will guide you how to deploy a Telegram bot to Heroku by using either webhooks or long polling. We also assume that you have a Heroku account already.
Prerequisites
First, install some dependencies:
# Create a project directory.
mkdir grammy-bot
cd grammy-bot
npm init --y
# Install main dependencies.
npm install grammy express
# Install development dependencies.
npm install -D typescript @types/express @types/node
# Create TypeScript config.
npx tsc --init
2
3
4
5
6
7
8
9
10
11
12
13
We will store our TypeScript files inside a folder src
, and our compiled files in a folder dist
. Create the folders in the project’s root directory. Then, inside folder src
, create a new file named bot
. Our folder structure should now look like this:
.
├── node_modules/
├── dist/
├── src/
│ └── bot.ts
├── package.json
├── package-lock.json
└── tsconfig.json
After that, open tsconfig
and change it to use this configuration:
{
"compilerOptions": {
"target": "ESNEXT",
"module": "ESNext", // [!code hl] // changed from commonjs to esnext
"lib": ["ES2021"],
"outDir": "./dist/",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Because the module
option above has been set from commonjs
to esnext
, we have to add "type":
to our package
. Our package
should now be similar to this:
{
"name": "grammy-bot",
"version": "0.0.1",
"description": "",
"main": "dist/app.js",
"type": "module", // [!code hl] // add property of "type": "module"
"scripts": {
"dev-build": "tsc"
},
"author": "",
"license": "ISC",
"dependencies": {
"grammy": "^1.2.0",
"express": "^4.17.1"
},
"devDependencies": {
"typescript": "^4.3.5",
"@types/express": "^4.17.13",
"@types/node": "^16.3.1"
},
"keywords": []
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
As mentioned earlier, we have two options for receiving data from Telegram: webhooks and long polling. You can learn more about the both advantages and then decide which ones is suitable in these awesome tips!
Webhooks
If you decide to use long polling instead, you can skip this section and jump down to the section about long polling. 🚀
In short, unlike long polling, webhook do not run continuously for checking incoming messages from Telegram. This will reduce server load and save us a lot of dyno hours, especially when you are using the Eco plan. 😁
Okay, let us continue! Remember we have created bot
earlier? We will not dump all the code there, and leave coding the bot up to you. Instead, we are going to make app
our main entry point. That means every time Telegram (or anyone else) visits our site, express
decides which part of your server will be responsible for handling the request. This is useful when you are deploying both website and bot in the same domain. Also, by splitting codes to different files, it make our code look tidy. ✨
Express and Its Middleware
Now create app
inside folder src
and write this code inside:
import express from "express";
import { webhookCallback } from "grammy";
import { bot } from "./bot.js";
const domain = String(process.env.DOMAIN);
const secretPath = String(process.env.BOT_TOKEN);
const app = express();
app.use(express.json());
app.use(`/${secretPath}`, webhookCallback(bot, "express"));
app.listen(Number(process.env.PORT), async () => {
// Make sure it is `https` not `http`!
await bot.api.setWebhook(`https://${domain}/${secretPath}`);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Let’s take a look at our code above:
process
: Remember, NEVER store credentials in our code! For creating environment variables in Heroku, head over to this guide..env secret
: It could be ourPath BOT
or any random string. It is best practice to hide our bot path as explained by Telegram._TOKEN
⚡ Optimization (optional)
bot
at line 14 will always run when Heroku starts your server again. For low traffic bots, this will be for every request. However, we do not need this code to run every time a request is coming. Therefore, we can delete this part completely, and execute the GET
only once manually. Open this link on your web browser after deploying our bot:
https://api.telegram.org/bot<bot_token>/setWebhook?url=<webhook_url>
Note that some browsers require you to manually encode the webhook
before passing it. For instance, if we have bot token abcd:
and URL https://
, then our link should look like this:
https://api.telegram.org/botabcd:1234/setWebhook?url=https%3A%2F%2Fgrammybot.herokuapp.com%2Fsecret_path
⚡ Optimization (optional)
Use Webhook Reply for more efficiency.
Creating bot.ts
(Webhooks)
Next step, head over to bot
:
import { Bot } from "grammy";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is unset");
export const bot = new Bot(token);
bot.command("start", (ctx) => ctx.reply("Hello there!"));
bot.on("message", (ctx) => ctx.reply("Got another message!"));
2
3
4
5
6
7
8
9
Good! We have now finished coding our main files. But before we go to the deployment steps, we can optimize our bot a little bit. As usual, this is optional.
⚡ Optimization (optional)
Every time your server starts up, grammY will request information about the bot from Telegram in order to provide it on the context object under ctx
. We can set the bot information to prevent excessive get
calls.
Open this link
https://
in your favorite web browser. Firefox is recommended since it displaysapi .telegram .org /bot<bot _token> /get Me json
format nicely.Change our code at line 4 above and fill the value according to the results from
get
:Me tsconst token = process.env.BOT_TOKEN; if (!token) throw new Error("BOT_TOKEN is unset"); export const bot = new Bot(token, { botInfo: { id: 111111111, is_bot: true, first_name: "xxxxxxxxx", username: "xxxxxxbot", can_join_groups: true, can_read_all_group_messages: false, supports_inline_queries: false, }, });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Cool! It’s time to prepare our deployment environment! Straight to Deployment Section everyone! 💪
Long Polling
Your Script Will Run Continuously When Using Long Polling
Unless you know how to handle this behavior, make sure you have enough dyno hours.
Consider using webhooks? Jump up to the webhooks section. 🚀
Using long polling on your server is not always a bad idea. Sometimes, it is suitable for data gathering bots that don’t need to respond quickly and handle lots of data. If you want to do this once an hour, you can do that easily. That’s something you cannot control with webhooks. If your bot gets flooded with messages, you will see a lot of webhooks requests, however, you can more easily limit the rate of updates to process with long polling.
Creating bot.ts
(Long Polling)
Let’s open the bot
file that we have created earlier. Have it contain these lines of code:
import { Bot } from "grammy";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is unset");
const bot = new Bot(token);
bot.command(
"start",
(ctx) => ctx.reply("I'm running on Heroku using long polling!"),
);
bot.start();
2
3
4
5
6
7
8
9
10
11
12
13
That’s it! We are ready to deploy it. Pretty simple, right? 😃 If you think it is too easy, check out our Deployment Checklist! 🚀
Deployment
Nope… our Rocket Bot is not ready to launch yet. Complete these stages first!
Compile Files
Run this code in your terminal to compile the TypeScript files to JavaScript:
npx tsc
If it runs successfully and does not print any errors, our compiled files should be in the dist
folder with .js
extensions.
Set up Procfile
For the time being, Heroku
has several types of dynos. Two of them are:
Web dynos:
Web dynos are dynos of the “web” process that receive HTTP traffic from routers. This kind of dyno has a timeout of 30 seconds for executing code. Also, it will sleep if there is no request to handle within a 30 minutes period. This type of dyno is quite suitable for webhooks.
Worker dynos:
Worker dynos are typically used for background jobs. It does NOT have a timeout, and will NOT sleep if it does not handle any web requests. It fits long polling.
Create file named Procfile
without a file extension in the root directory of our project. For example, Procfile
and procfile
are not valid. Then write this single line code format:
<dynos type>: <command for executing our main entry file>
For our case it should be:
web: node dist/app.js
worker: node dist/bot.js
Set up Git
We are going to deploy our bot using Git and Heroku Cli. Here is the link for the installation:
Assuming that you already have them in your machine, and you have a terminal open in the root of our project’s directory. Now initialize a local git repository by running this code in your terminal:
git init
Next, we need to prevent unnecessary files from reaching our production server, in this case Heroku
. Create a file named .gitignore
in root of our project’s directory. Then add this list:
node_modules/
src/
tsconfig.json
2
3
Our final folder structure should now look like this:
.
├── .git/
├── node_modules/
├── dist/
│ ├── bot.js
│ └── app.js
├── src/
│ ├── bot.ts
│ └── app.ts
├── package.json
├── package-lock.json
├── tsconfig.json
├── Procfile
└── .gitignore
.
├── .git/
├── node_modules/
├── dist/
│ └── bot.js
├── src/
│ └── bot.ts
├── package.json
├── package-lock.json
├── tsconfig.json
├── Procfile
└── .gitignore
Commit files to our git repository:
git add .
git commit -m "My first commit"
2
Set Up a Heroku Remote
If you have already created Heroku app, pass your Existing app
’s name in <my
below, then run the code. Otherwise, run New app
.
heroku create
git remote -v
2
heroku git:remote -a <myApp>
Deploying Code
Finally, press the red button and liftoff! 🚀
git push heroku main
If it doesn’t work, it’s probably our git branch is not main
but master
. Press this blue button instead:
git push heroku master