<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikita Zavada</title>
    <description>The latest articles on DEV Community by Nikita Zavada (@nikitosit).</description>
    <link>https://dev.to/nikitosit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946720%2F57daecc5-5dd2-40c3-9429-7886caa1b898.png</url>
      <title>DEV Community: Nikita Zavada</title>
      <link>https://dev.to/nikitosit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikitosit"/>
    <language>en</language>
    <item>
      <title>How to Handle Telegram Albums in Telegraf</title>
      <dc:creator>Nikita Zavada</dc:creator>
      <pubDate>Mon, 25 May 2026 20:14:33 +0000</pubDate>
      <link>https://dev.to/nikitosit/how-to-handle-telegram-albums-in-telegraf-1le3</link>
      <guid>https://dev.to/nikitosit/how-to-handle-telegram-albums-in-telegraf-1le3</guid>
      <description>&lt;p&gt;When building Telegram bots with Telegraf, it is easy to expect that a media album arrives as a single message.&lt;/p&gt;

&lt;p&gt;In reality, Telegram sends every media item as a separate update.&lt;/p&gt;

&lt;p&gt;For example, if a user sends an album with 3 photos, your bot receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update #1 -&amp;gt; photo_1
update #2 -&amp;gt; photo_2
update #3 -&amp;gt; photo_3

instead of:

single_post -&amp;gt; [photo_1, photo_2, photo_3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates several annoying production problems:&lt;/p&gt;

&lt;p&gt;duplicate database records&lt;br&gt;
race conditions&lt;br&gt;
buffering logic&lt;br&gt;
ordering issues&lt;br&gt;
timeout handling&lt;/p&gt;

&lt;p&gt;At first, the solution usually starts with something simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mediaGroups = new Map();

// buffering
// sorting
// cleanup
// timeout handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But once Redis, multiple workers, or higher traffic enter the picture, the logic becomes much harder to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built telegram-media — a TypeScript library for Node.js that collects Telegram media groups into a single normalized object.&lt;/p&gt;

&lt;p&gt;Installation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install telegram-media
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Telegraf Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Telegraf } from "telegraf";
import {
  createRedisMediaGroupStorage,
  createTelegramMediaGroup,
} from "telegram-media";

const bot = new Telegraf(process.env.BOT_TOKEN!);

const collector = createTelegramMediaGroup({
  storage: createRedisMediaGroupStorage(redisClient),

  timeoutMs: 3000,

  supportedMediaTypes: ["photo", "video", "audio"],

  async onCollected(post) {
    console.log("Collected media group:", post);
  },
});

bot.on("message", async (ctx) =&amp;gt; {
  await collector.collect(ctx.update);
});

bot.launch();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why This Helps?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually buffering Telegram updates, the collector:&lt;/p&gt;

&lt;p&gt;groups related media automatically&lt;br&gt;
preserves media ordering&lt;br&gt;
prevents duplicate processing&lt;br&gt;
works with Redis&lt;br&gt;
returns a single normalized post object&lt;br&gt;
Features&lt;br&gt;
Media group aggregation&lt;br&gt;
Redis support&lt;br&gt;
Duplicate prevention&lt;br&gt;
Media ordering&lt;br&gt;
Configurable timeout&lt;br&gt;
TypeScript support&lt;br&gt;
&lt;strong&gt;Links&lt;/strong&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/telegram-media" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/telegram-media&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/NikitosIT/telegram-media" rel="noopener noreferrer"&gt;https://github.com/NikitosIT/telegram-media&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>node</category>
      <category>backend</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to Handle Telegram Albums in grammY</title>
      <dc:creator>Nikita Zavada</dc:creator>
      <pubDate>Mon, 25 May 2026 20:12:10 +0000</pubDate>
      <link>https://dev.to/nikitosit/how-to-handle-telegram-albums-in-grammy-2mpo</link>
      <guid>https://dev.to/nikitosit/how-to-handle-telegram-albums-in-grammy-2mpo</guid>
      <description>&lt;p&gt;When building Telegram bots with grammY, it is natural to expect that a media album arrives as a single message.&lt;/p&gt;

&lt;p&gt;In reality, Telegram works differently.&lt;/p&gt;

&lt;p&gt;If a user sends an album with multiple photos or videos, your bot receives multiple separate update events instead of one complete post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update #1 -&amp;gt; photo_1
update #2 -&amp;gt; photo_2
update #3 -&amp;gt; photo_3

instead of:

single_post -&amp;gt; [photo_1, photo_2, photo_3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This quickly creates several annoying problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate database records&lt;/li&gt;
&lt;li&gt;race conditions&lt;/li&gt;
&lt;li&gt;buffering logic&lt;/li&gt;
&lt;li&gt;ordering issues&lt;/li&gt;
&lt;li&gt;timeout handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, handling this manually may seem simple.&lt;/p&gt;

&lt;p&gt;Usually the implementation starts with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mediaGroups = new Map();

// buffering
// sorting
// cleanup
// timeout handling

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But once Redis, multiple workers, or production traffic enter the picture, the logic becomes much more complicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install the library telegram-media — a TypeScript library for Node.js that collects Telegram media groups into a single normalized object.&lt;/p&gt;

&lt;p&gt;Installation&lt;br&gt;
npm install telegram-media&lt;br&gt;
&lt;strong&gt;grammY Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Bot } from "grammy";
import {
  createRedisMediaGroupStorage,
  createTelegramMediaGroup,
} from "telegram-media";

const bot = new Bot(process.env.BOT_TOKEN!);

const collector = createTelegramMediaGroup({
  storage: createRedisMediaGroupStorage(redisClient),

  timeoutMs: 3000,

  supportedMediaTypes: ["photo", "video", "audio"],

  async onCollected(post) {
    console.log("Collected media group:", post);
  },
});

bot.on("message", async (ctx) =&amp;gt; {
  await collector.collect(ctx.update);
});

bot.start();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why This Helps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually buffering Telegram updates, the collector:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;groups related media automatically&lt;/li&gt;
&lt;li&gt;preserves media ordering&lt;/li&gt;
&lt;li&gt;prevents duplicate processing&lt;/li&gt;
&lt;li&gt;works with Redis&lt;/li&gt;
&lt;li&gt;returns a single normalized post object&lt;/li&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;Media group aggregation&lt;/li&gt;
&lt;li&gt;Redis support&lt;/li&gt;
&lt;li&gt;Duplicate prevention Media ordering&lt;/li&gt;
&lt;li&gt;Configurable timeout&lt;/li&gt;
&lt;li&gt;TypeScript support
&lt;strong&gt;Links&lt;/strong&gt;
npm: &lt;a href="https://www.npmjs.com/package/telegram-media" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/telegram-media&lt;/a&gt;
GitHub: &lt;a href="https://github.com/NikitosIT/telegram-media" rel="noopener noreferrer"&gt;https://github.com/NikitosIT/telegram-media&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>backend</category>
      <category>redis</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to Collect Telegram Media Groups in Node.js</title>
      <dc:creator>Nikita Zavada</dc:creator>
      <pubDate>Fri, 22 May 2026 20:12:52 +0000</pubDate>
      <link>https://dev.to/nikitosit/how-to-collect-telegram-media-groups-in-nodejs-d8c</link>
      <guid>https://dev.to/nikitosit/how-to-collect-telegram-media-groups-in-nodejs-d8c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ogsq5ae6hw6f66ixcr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ogsq5ae6hw6f66ixcr9.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working with the Telegram Bot API, it is easy to expect that an album with multiple photos will arrive as a single event.&lt;/p&gt;

&lt;p&gt;But Telegram works differently.&lt;/p&gt;

&lt;p&gt;If a user sends an album of 3 photos, your bot doesn't receive one "post" object. Instead, it receives 3 separate updates in rapid succession:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update #1 -&amp;gt; photo_1
Update #2 -&amp;gt; photo_2
Update #3 -&amp;gt; photo_3

instead of:

single_post -&amp;gt; [photo_1, photo_2, photo_3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Headache: Why this is hard&lt;br&gt;
Handling these updates manually forces you to deal with a lot of "plumbing" code that has nothing to do with your actual bot logic:&lt;/p&gt;

&lt;p&gt;Duplicate database records: Accidentally creating 3 posts instead of 1.&lt;br&gt;
Race conditions: Multiple updates hitting your server at the exact same time.&lt;br&gt;
Buffering &amp;amp; Timeouts: Deciding how long to wait for the "next" photo before assuming the group is complete.&lt;br&gt;
Ordering: Ensuring the photos stay in the order the user intended.&lt;br&gt;
Typical "infrastructure" code usually starts looking like this mess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaGroups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// buffering logic...&lt;/span&gt;
&lt;span class="c1"&gt;// sorting logic...&lt;/span&gt;
&lt;span class="c1"&gt;// duplicate prevention...&lt;/span&gt;
&lt;span class="c1"&gt;// cleanup jobs...&lt;/span&gt;
&lt;span class="c1"&gt;// timeout handling...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some point, you realize you are rewriting low-level infrastructure instead of building your actual features.&lt;/p&gt;

&lt;p&gt;The Solution: telegram-media&lt;br&gt;
I built telegram-media—a lightweight TypeScript library for Node.js that collects these scattered Telegram updates into a single, normalized object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;telegram-media
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage Example&lt;/strong&gt;&lt;br&gt;
Here is how you can use it to collect media and save it to a database (like Prisma) using a Redis storage backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTelegramMediaGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onCollected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This only fires ONCE per media group&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegramPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mapCollectedPostToPrismaInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Use Redis for distributed environments&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createRedisMediaGroupStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redisClient&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;supportedMediaTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why I Built This?&lt;/strong&gt;&lt;br&gt;
I ran into this problem while building a Telegram ingestion system for a personal project. At first, the logic seemed simple—just a small Set and a setTimeout.&lt;/p&gt;

&lt;p&gt;Then the edge cases hit: distributed workers fighting over the same group, incomplete groups caused by network lag, and Redis synchronization issues. I extracted the logic into a standalone package so no one else has to solve this from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Media Group Aggregation&lt;/strong&gt; — Automatically groups related Telegram updates into a single normalized post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis Support&lt;/strong&gt; — Ready for production and distributed environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate Prevention&lt;/strong&gt; — Handles Telegram retry updates safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering&lt;/strong&gt; — Preserves the original media sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — Fully typed for a better developer experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Explore&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/telegram-media" rel="noopener noreferrer"&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/NikitosIT/telegram-media" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt;&lt;br&gt;
I'd love to hear how others are handling media groups. Do you use a custom buffer, or do you just process each image individually and update the record as you go?&lt;/p&gt;

&lt;p&gt;Let me know in the comments!&lt;/p&gt;

</description>
      <category>node</category>
      <category>telegram</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
