<?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 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>
