Running Your First Bot
In our last session, you successfully initialized a grammY project, installed the necessary dependency, and wrote the code to create a bot instance using your API token. You now have a dormant bot object, configured and ready, but not yet active.
This lesson is where we bring your bot to life. We will program it to respond to user messages and then launch it using the long polling mechanism. By the end of this session, you'll have a running bot that you can interact with directly in Telegram, a major milestone in your bot development journey. This process will cover the core grammY methods for handling updates and starting the bot.
Step 1: Handling Messages
A bot that doesn't react to messages isn't very useful. Our first task is to teach it how to respond. In grammY, this is done by registering listeners for different types of updates. For our first bot, we'll create a simple "echo" bot that replies with the exact text it receives.
To do this, we use the bot.on() method. This method allows you to listen for specific update types. We'll listen for text messages.
Modify your index.ts file to include the following handler, right after you create the bot instance:
import { Bot } from "grammy";
// Read the bot token from the environment variable.
const token = Bun.env.BOT_TOKEN;
if (!token) {
throw new Error("BOT_TOKEN is not set in the environment variables!");
}
// Create a new bot instance.
const bot = new Bot(token);
// Listen for text messages and echo them back.
bot.on("message:text", (ctx) => {
const receivedText = ctx.message.text;
console.log(`Received message: "${receivedText}"`);
ctx.reply(`You wrote: ${receivedText}`);
});
console.log("Bot is ready to be started...");
Let's dissect the new code:
bot.on("message:text", ...): This registers a listener. The first argument,"message:text", is a filter query that tells grammY to only run this code for updates that contain a text message.(ctx) => { ... }: This is an asynchronous arrow function that serves as our handler. It receives a single argument,ctx, which is the Context object.- The Context Object (
ctx): This is a central concept in grammY. It's a powerful object that contains all the information about the incoming update (like the message, the sender, the chat, etc.). It also provides methods to respond, such asctx.reply(). Your experience with event objects in front-end frameworks is analogous here;ctxis the payload and toolkit for the "message" event. ctx.message.text: We access the incoming message object viactx.messageand its text content viactx.message.text.ctx.reply(...): This is a convenient method on the context object to send a reply back to the same chat.
Our bot now has logic, but it's still not running. To do that, we need to start it and begin fetching updates from Telegram.
Step 2: Understanding and Implementing Long Polling
There are two primary ways a bot can receive updates from Telegram: long polling and webhooks. For development and many common hosting scenarios, long polling is the simplest and most direct method. It involves your bot actively asking Telegram for new messages.
The official grammY documentation has an excellent explanation of this concept. Reading it will clarify why we're choosing this method and what it entails.
This guide explains the two fundamental ways your bot can get messages from Telegram. It provides a great analogy to build intuition.
Start at the beginning of the article, reading the introduction. Then, read the section "How Does Long Polling Work?". The ice cream parlor analogy is a helpful way to visualize the process. Next, review the "Comparison" section to understand the trade-offs. Notice the emphasis on the simplicity of long polling. Finally, read the sections "I Still Have No Idea What to Use" and "How to Use Long Polling". This will show you the exact command we are about to use.
As you've just read, grammY makes this incredibly simple. All we need to do is call bot.start(). This single method call initiates the long polling loop, where the bot continuously fetches updates from Telegram.
Step 3: Launching and Verifying Your Bot
We're now ready to put everything together. Add the bot.start() call to the end of your index.ts file and update the log messages for clarity.
Your final index.ts should look like this:
import { Bot } from "grammy";
// Read the bot token from the environment variable.
const token = Bun.env.BOT_TOKEN;
if (!token) {
throw new Error("BOT_TOKEN is not set in the environment variables!");
}
// Create a new bot instance.
const bot = new Bot(token);
// Listen for text messages and echo them back.
bot.on("message:text", (ctx) => {
const receivedText = ctx.message.text;
console.log(`Received message from ${ctx.from.first_name}: "${receivedText}"`);
ctx.reply(`Echo: ${receivedText}`);
});
// Start the bot.
bot.start();
console.log("Bot has been started and is listening for messages...");
To run your bot, open your terminal in the project directory and execute:
bun run index.ts
If everything is set up correctly, you will see the message "Bot has been started and is listening for messages..." in your console. The process will not exit; it's now in an active loop, waiting for updates.
Now for the moment of truth:
- Open your Telegram client.
- Find the bot you created with BotFather.
- Send it any message, like "Hello, world!".
The bot should instantly reply with "Echo: Hello, world!". You will also see a corresponding log message in your terminal.

Under the Hood: What bot.start() Really Does
As an experienced developer, you likely appreciate knowing what a framework's convenience methods are abstracting away. You used getUpdates in a raw polling loop in Module 2, and bot.start() is essentially a sophisticated, robust version of that.
The following resource provides a concise, zero-dependency implementation of a polling bot and then maps those raw concepts to their grammY equivalents. It perfectly demystifies the framework's "magic."
This document breaks down a Telegram bot into its first principles. We are interested in how it connects these principles to the grammY framework.
Scroll down to the table under the heading "What grammY's bot.start() does under the hood". Pay close attention to the row for bot.start(). This shows you that the command is effectively running a while loop that calls getUpdates and manages the update offset for you, just as you did manually before, but with added resilience.
This reveals that bot.start() isn't magic; it's a well-built implementation of the long polling pattern, handling the continuous loop and update acknowledgments so you can focus on your bot's logic.
Conclusion
You have successfully launched your first interactive bot! It's a simple echo bot, but it demonstrates the fundamental workflow of a grammY application: initializing the bot, registering handlers for updates, and starting the long polling loop.
Key Takeaways:
- You can handle specific message types using
bot.on("filter_query", handler). - The Context object (
ctx) provides information about the incoming update and methods to reply. - Long polling is the default and simplest way to run a bot during development; it involves your bot actively requesting updates from Telegram.
- The
bot.start()method initiates the long polling loop and brings your bot online.
Your bot is now running, but if you stop the process with Ctrl+C, it terminates abruptly. In our next lesson, we will make it more robust by implementing proper error handling and a "graceful shutdown" mechanism, which are crucial for any real-world application.
Can't find a good explanation? Sign up and we'll make it for you
Sign up