<?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: Agustin</title>
    <description>The latest articles on DEV Community by Agustin (@agust1n).</description>
    <link>https://dev.to/agust1n</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%2F3503881%2Fb63209ab-95a4-4d98-b8b0-5d265ca45959.jpg</url>
      <title>DEV Community: Agustin</title>
      <link>https://dev.to/agust1n</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agust1n"/>
    <language>en</language>
    <item>
      <title>How We Use Redis with BullMQ on Hetzner for Queue Management at Fotify</title>
      <dc:creator>Agustin</dc:creator>
      <pubDate>Sun, 02 Nov 2025 14:33:25 +0000</pubDate>
      <link>https://dev.to/agust1n/how-we-use-redis-with-bullmq-on-hetzner-for-queue-management-at-fotify-2k8h</link>
      <guid>https://dev.to/agust1n/how-we-use-redis-with-bullmq-on-hetzner-for-queue-management-at-fotify-2k8h</guid>
      <description>&lt;p&gt;At &lt;a href="https://fotify.app" rel="noopener noreferrer"&gt;Fotify&lt;/a&gt;, we manage thousands of real-time photo uploads and live event interactions every week. Behind the scenes, all that speed and reliability comes down to how we handle our background jobs — and &lt;strong&gt;Redis + BullMQ&lt;/strong&gt; running on &lt;strong&gt;Hetzner Cloud&lt;/strong&gt; plays a big role in that.&lt;/p&gt;

&lt;p&gt;In this post, I’ll share how we set up our queue system, the lessons learned, and a few tips for keeping it scalable and efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why We Needed Queues
&lt;/h2&gt;

&lt;p&gt;When guests upload photos during an event, each upload triggers multiple asynchronous tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image optimization and metadata extraction&lt;/li&gt;
&lt;li&gt;Upload to Cloudflare R2 for storage&lt;/li&gt;
&lt;li&gt;AI-based content filtering&lt;/li&gt;
&lt;li&gt;Push notifications for real-time photo walls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing all that directly in the request cycle would slow down the user experience. So, we moved those tasks into background queues.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ The Stack
&lt;/h2&gt;

&lt;p&gt;Our setup is simple and battle-tested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; (single node on Hetzner Cloud CX21)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BullMQ&lt;/strong&gt; for queue management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NestJS&lt;/strong&gt; workers that process the jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare R2&lt;/strong&gt; for image storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; for core app data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use Docker Compose to deploy the entire stack on Hetzner. Redis runs in a dedicated container, while the workers and API run separately for better isolation.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 How BullMQ Fits In
&lt;/h2&gt;

&lt;p&gt;BullMQ gives us a clean interface to create, manage, and monitor queues.&lt;/p&gt;

&lt;p&gt;Each feature has its own queue — for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;photo-processing&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push-notifications&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;analytics&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our NestJS service, we inject a queue like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InjectQueue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&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;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhotoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;InjectQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photo-processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;)&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;handleUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optimize&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;span class="na"&gt;photoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This decouples our real-time uploads from heavier tasks that can safely run in the background.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Performance Tips
&lt;/h2&gt;

&lt;p&gt;A few lessons learned after running this setup for months:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a connection pool&lt;/strong&gt; — avoid creating new Redis connections for every queue instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add retry &amp;amp; backoff strategies&lt;/strong&gt; — jobs may fail due to transient network issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable metrics&lt;/strong&gt; — we use &lt;a href="https://github.com/felixmosh/bull-board" rel="noopener noreferrer"&gt;Bull Board&lt;/a&gt; to monitor queue performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy Redis close to your API&lt;/strong&gt; — latency between the app and Redis affects throughput.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧩 Why Hetzner
&lt;/h2&gt;

&lt;p&gt;Hetzner’s servers give us excellent performance for the price. A small Redis instance on Hetzner easily handles tens of thousands of jobs per hour.&lt;/p&gt;

&lt;p&gt;We use snapshots and weekly backups to ensure nothing gets lost.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Try Fotify
&lt;/h2&gt;

&lt;p&gt;If you’re into event tech or photo-sharing platforms, check out &lt;a href="https://fotify.app" rel="noopener noreferrer"&gt;Fotify&lt;/a&gt; — our real-time photo-sharing app for events. It’s built entirely with &lt;strong&gt;Next.js, NestJS, Redis, and BullMQ&lt;/strong&gt;, all deployed on Hetzner.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;br&gt;
If you’ve built something similar or have tips for scaling Redis queues, I’d love to hear from you in the comments.&lt;/p&gt;

</description>
      <category>developer</category>
    </item>
    <item>
      <title>Building Fotify: Real-Time Photo Sharing for Events</title>
      <dc:creator>Agustin</dc:creator>
      <pubDate>Mon, 15 Sep 2025 13:22:20 +0000</pubDate>
      <link>https://dev.to/agust1n/building-fotify-real-time-photo-sharing-for-events-53dj</link>
      <guid>https://dev.to/agust1n/building-fotify-real-time-photo-sharing-for-events-53dj</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%2Fuabquwia1oabax7f3dvj.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%2Fuabquwia1oabax7f3dvj.png" alt=" " width="600" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At every wedding, birthday, or corporate gathering, guests are taking pictures. The problem is that those photos end up scattered across devices and apps, making it hard for organizers to collect and share them.&lt;/p&gt;

&lt;p&gt;That’s the problem we set out to solve with Fotify.&lt;/p&gt;

&lt;p&gt;The Idea&lt;/p&gt;

&lt;p&gt;Fotify is a real-time photo-sharing and RSVP platform that makes events more interactive:&lt;/p&gt;

&lt;p&gt;Guests scan a QR code to upload photos instantly.&lt;/p&gt;

&lt;p&gt;Hosts can display live galleries on screens during the event.&lt;/p&gt;

&lt;p&gt;Digital invitations, RSVPs, and even multi-day itineraries are managed in the same platform.&lt;/p&gt;

&lt;p&gt;No app downloads. No accounts for guests. Just instant memories, accessible to everyone.&lt;/p&gt;

&lt;p&gt;The Tech Stack&lt;/p&gt;

&lt;p&gt;Here’s how we’re building Fotify under the hood:&lt;/p&gt;

&lt;p&gt;Frontend: Next.js&lt;br&gt;
 running on a Hetzner VPS.&lt;/p&gt;

&lt;p&gt;Backend: NestJS&lt;br&gt;
 deployed on another Hetzner VPS.&lt;/p&gt;

&lt;p&gt;Database: PostgreSQL, self-hosted for full control and performance.&lt;/p&gt;

&lt;p&gt;Authentication: Google OAuth, already integrated with Passport.js.&lt;/p&gt;

&lt;p&gt;Storage &amp;amp; Media: Planning Cloudflare R2 (for raw photos) and Cloudflare Images (for transformations &amp;amp; delivery).&lt;/p&gt;

&lt;p&gt;Analytics: Umami&lt;br&gt;
 for privacy-friendly event tracking.&lt;/p&gt;

&lt;p&gt;AI Layer: Intelligent moderation &amp;amp; filtering to keep galleries clean and safe.&lt;/p&gt;

&lt;p&gt;This setup gives us full control, performance, and scalability, without locking ourselves into expensive managed services.&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;/p&gt;

&lt;p&gt;Hetzner is underrated. With good setup (Docker + Traefik + backups), it’s cheap, fast, and reliable.&lt;/p&gt;

&lt;p&gt;Next.js + NestJS is a perfect combo. The separation of concerns keeps development clean and scaling easier.&lt;/p&gt;

&lt;p&gt;Postgres beats MySQL for flexibility. Especially with JSON fields for handling dynamic event data.&lt;/p&gt;

&lt;p&gt;Start with authentication early. Google OAuth via Passport.js made onboarding smoother for organizers.&lt;/p&gt;

&lt;p&gt;Try It Out&lt;/p&gt;

&lt;p&gt;If you’re a developer, photographer, or event organizer, I’d love your feedback.&lt;/p&gt;

&lt;p&gt;👉 Check out Fotify at &lt;a href="https://fotify.app" rel="noopener noreferrer"&gt;https://fotify.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
