⏱️ Lectura: 11 min
Telegram just turned its own infrastructure into your bot’s hosting: with Telegram Serverless, the code that responds to a message or a button runs directly on Telegram’s servers, without you having to spin up a VPS, a container, or a third-party cloud function.
📑 En este artículo
- TL;DR
- What Telegram Serverless is and why it matters
- How the three-layer mental model works
- Practical examples: from hello world to a real use case
- How to get started step by step
- Real-world use cases
- Common mistakes and best practices
- Comparison with alternatives
- Going deeper: what happens under the hood
- Frequently Asked Questions
- Does Telegram Serverless completely replace a VPS?
- What happens to the data if my bot suddenly gets a lot of traffic?
- Can I use external npm libraries inside the modules?
- Do I need a Telegram Business or special developer account?
- How do I test a handler before deploying it to production?
- Can schema.js migrations be reverted?
- References
If you’ve ever set up a server just so your bot could reply to /start, this platform eliminates that step entirely: you write JavaScript modules, deploy them with a single command, and Telegram runs them alongside its own API and a built-in database.
TL;DR
- Understand what Telegram Serverless is and how it replaces a VPS or cloud function for bots.
- Learn the three-layer model: local folder, Telegram’s cloud, and the tgcloud CLI.
- Write a handlers/message.js file that responds to updates using the API and a SQLite database.
- Know how to define tables with schema.js using the SDK’s table() and integer() DSL.
- Deploy with npx tgcloud push and migrate the schema with npx tgcloud migrate.
- Know when Serverless makes sense versus a traditional VPS or an external cloud function.
- Identify common mistakes when migrating an existing bot to this model.
What Telegram Serverless is and why it matters
Telegram Serverless is a backend runtime built into the platform: it lets you write the code that processes your bot’s updates (messages, buttons, inline queries) as plain JavaScript modules, without managing any server of your own. Telegram runs it in an isolated V8 sandbox that runs close to the Bot API, which reduces the latency between receiving an update and responding to it.
This approach solves a problem anyone who’s built a bot knows well: keeping /start working 24/7 required an always-on process, reachable from the internet and kept patched. With Serverless that process doesn’t exist: your code runs on demand and scales automatically with the bot’s traffic.
The platform isn’t a template for a single type of app. According to the official documentation, it works for conversational bots with per-user state, for a Mini App’s backend, for games with leaderboards, and for automations that call external HTTP APIs and post results to a chat.
📌 Note: before writing a single line of code you need to enable the feature. In @BotFather: open your bot, go to Serverless, and turn it on. That unlocks the CLI token, the handlers, the library, and the database.
How the three-layer mental model works
You work across three places that map one-to-one. Your local folder holds the version-controlled code; Telegram’s cloud holds the deployed copy of those modules plus your bot’s database; and the tgcloud CLI is the bridge that shows you the differences between the two and syncs them.
You never SSH into anything. You edit files locally, run npx tgcloud push, and the platform handles the rest. Your bot’s traffic is handled by the deployed copy; the database persists across invocations even though each handler execution is ephemeral.
A project has only three kinds of code: handlers/ with one file per update type, lib/ with shared code you import from any handler, and schema.js with your database tables.
flowchart TD
A["Local folder: handlers, lib, schema.js"] --> B["npx tgcloud push"]
B --> C["Telegram's cloud: modules + database"]
C --> D["Bot responds to updates in production"]
When an update arrives (a message, a button press, an inline query), Telegram routes it to the corresponding handler (handlers/message.js, handlers/callback_query.js, etc.) and calls its default export. That function talks to the Bot API and to the database through the SDK, and returns. An update with no handler to catch it is simply ignored, so you only add the handlers you need.
Practical examples: from hello world to a real use case
The simplest possible handler receives a message and replies. It doesn’t touch the database, it imports nothing but the API SDK:
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Received your message: ${message.text ?? '(no text)'}`,
});
}
This file goes in handlers/message.js. Deployed with npx tgcloud push, it’s already a live bot that echoes every message: there’s no database or state, so there’s no need to run any migration.
The next example gets closer to a real use case: a support bot that counts how many tickets each chat has opened and remembers the last subject. First we define the table in schema.js:
import { table, integer, text } from 'sdk/db';
export const tickets = table('tickets', {
chatId: integer('chat_id').primaryKey(),
abiertos: integer('abiertos').notNull().default(0),
ultimoAsunto: text('ultimo_asunto'),
});
And then the handler that inserts or updates that row on every message, using onConflictDoUpdate to increment the counter atomically:
import { api, db } from 'sdk';
import { tickets } from 'schema';
import { sql } from 'sdk/db';
export default async function (message) {
const chatId = message.chat.id;
const asunto = message.text ?? 'No subject';
const [fila] = await db.insert(tickets)
.values({ chatId, abiertos: 1, ultimoAsunto: asunto })
.onConflictDoUpdate({
target: tickets.chatId,
set: { abiertos: sql`${tickets.abiertos} + 1`, ultimoAsunto: asunto },
})
.returning()
.run();
await api.sendMessage({
chat_id: chatId,
text: `Ticket #${fila.abiertos} registered for this chat. Subject: ${fila.ultimoAsunto}`,
});
}
With this you already have a bot with persistent per-chat state, without having touched a server. The expected result: every new message returns an incrementing ticket number and stores the last subject received.
sequenceDiagram
participant U as User
participant T as Telegram
participant H as Handler in V8
participant D as Database
U->>T: sends message
T->>H: delivers the update
H->>D: inserts or updates row
D-->>H: returns updated row
H-->>T: calls api.sendMessage
T-->>U: shows the response
How to get started step by step
You’ll need Node.js 18 or higher and a bot already registered with @BotFather. First, install Node according to your operating system.
Windows:
winget install OpenJS.NodeJS.LTS
macOS:
brew install node
Linux (Debian/Ubuntu):
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
With Node installed, the rest of the steps are identical across all three platforms. First enable Serverless in @BotFather (Bot → Serverless → enable) and then create the project:
npm create @tgcloud/bot support_bot
cd support_bot
The command scaffolds a folder ready to edit, with a sample handler at handlers/message.js and a docs/tgcloud-sdk.md file with the SDK reference. Pass . as an argument if you want to scaffold into the current folder.
Write your schema.js and your handlers as in the previous examples, and then deploy:
npx tgcloud push
npx tgcloud migrate
npx tgcloud status
push uploads your modules, migrate applies pending schema changes against the cloud database, and status shows you which version is running in production and whether there are undeployed differences. Use run to test a specific handler before deploying.
Real-world use cases
An AI conversational bot that needs to remember each user’s context across messages fits naturally: the state lives in the table, not in the memory of a process that could restart. A Mini App backend can use the same handlers to serve dynamic data to the frontend embedded in Telegram without exposing a separate public endpoint.
For games and tools, the built-in database works for leaderboards and quizzes without contracting an external engine. And for automations and integrations, the outbound HTTP available in every module lets you call third-party APIs and post the result to a channel, for example a bot that checks a feed periodically and posts alerts.
Common mistakes and best practices
The most common mistake when migrating an existing bot is forgetting that there’s no persistent filesystem: any state your bot needs to remember between invocations has to live in the schema.js table, not in a global variable or a temporary file.
⚠️ Watch out: if you run
tgcloud pushwithout having runtgcloud migrateafter adding a new table, the handler will fail when it tries to read or write to a table that doesn’t yet exist in the deployed database.
Another typical oversight is assuming that an update with no handler triggers some kind of error: it’s actually ignored silently, so if your bot doesn’t respond to a certain update type, check first whether the corresponding file is missing from handlers/ before suspecting the internal logic.
Finally, treat migrations as reviewed code, not an automatic step: every schema change gets recorded and version-controlled along with the rest of the project, so a git diff on schema.js should be as readable as any other code change.
Comparison with alternatives
| Option | When to use it | Advantage | Limitation |
|---|---|---|---|
| Telegram Serverless | Bots and Mini Apps that don’t need their own infrastructure | Zero servers, built-in database and API, atomic deploy | Tied to Telegram, doesn’t work for logic outside the bot ecosystem |
| Your own VPS | Bots that already integrate other in-house services or need full control of the environment | Absolute control over the runtime and operating system | Has to be kept running, patched, and scaled manually |
| Cloud Function (Lambda, Cloud Run) | Backends that serve other clients or channels besides Telegram | Portable across providers, integrates with the rest of a cloud stack | You have to wire up the Bot API, the database, and the credentials yourself |
| Hosting panel (cPanel/Heroku-style) | Quick prototypes with a graphical admin interface | Visual configuration, little infrastructure knowledge needed | Less fine-grained control, dependency on the panel provider |
Going deeper: what happens under the hood
Each handler invocation runs in a lightweight V8 isolate, the same isolation mechanism used by runtimes like Cloudflare Workers or Deno Deploy: a separate execution context per invocation, without the cost of starting a full operating system process. Because it runs physically close to the Bot API infrastructure, calls to api.* and to the database have fewer network hops than a handler hosted on another cloud provider.
💭 Key point: the deploy is atomic:
tgcloud pushreplaces all modules at once, not file by file, so there’s no window where the bot runs with a mix of old and new code.
The included database is backed by SQLite, which explains why the SDK exposes a table DSL (table(), integer(), text()) instead of asking you for a connection string: there’s no separate database server to configure, just the file managed by the platform.
flowchart LR
A["Edit schema.js and handlers/"] --> B["npx tgcloud push"]
B --> C["npx tgcloud migrate"]
C --> D["npx tgcloud status"]
D --> E["Bot in production"]
To confirm a deploy went live, npx tgcloud status shows the deployed version and whether your local copy differs from the cloud; no external panel is needed to check it.
📖 Summary on Telegram: View summary
Your next step: clone the sample project with npm create @tgcloud/bot, add your own table in schema.js, and deploy it with tgcloud push followed by tgcloud migrate to see the full cycle on a real bot.
Frequently Asked Questions
Does Telegram Serverless completely replace a VPS?
For the bot’s own logic, yes. If your backend also serves other clients outside Telegram, you’ll still need that separate infrastructure.
What happens to the data if my bot suddenly gets a lot of traffic?
Execution scales automatically because each invocation runs in its own isolate; there’s no single process that gets overwhelmed.
Can I use external npm libraries inside the modules?
The project is structured as standard JavaScript code in lib/, although external dependencies are subject to what the platform’s V8 sandbox supports.
Do I need a Telegram Business or special developer account?
No, a bot registered with @BotFather with the Serverless option enabled from the bot’s panel is enough.
How do I test a handler before deploying it to production?
The CLI includes the run command to execute a specific handler locally before uploading it with push.
Can schema.js migrations be reverted?
They’re managed as reviewed changes within the normal tgcloud migrate flow, version-controlled together with the rest of the project just like any other code file.
References
- Telegram Serverless: official documentation with the full reference for the SDK, the CLI, and the project model.
- Telegram Bot API: reference for the methods available on the SDK’s
apiobject. - SQLite: the database engine backing the storage included in every project.
- Node.js: the required runtime, version 18 or higher, to use the
tgcloudCLI.
📱 Enjoy this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.
0 Comments