<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[YaffaLab: Learn Web Dev, Data Science, and Master Tech Skills]]></title><description><![CDATA[Master web dev, data science & tech skills with Yaffa Labs! Tutorials, projects & explanations for developers & enthusiasts.]]></description><link>https://blog.yaffalab.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1707564003137/6NKi6O4rR.png</url><title>YaffaLab: Learn Web Dev, Data Science, and Master Tech Skills</title><link>https://blog.yaffalab.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 10 Sep 2026 07:57:19 GMT</lastBuildDate><atom:link href="https://blog.yaffalab.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[My Homelab Journey: Paperless-ngx - The Document Management Game-Changer - Part 5]]></title><description><![CDATA[Hello again!
Welcome to Part 5 of my homelab journey series! After setting up my Lenovo M920 Tiny with Proxmox (as you saw in Part 4), I've been exploring various self-hosted applications. Today, I want to share what has become my absolute favorite s...]]></description><link>https://blog.yaffalab.com/my-homelab-journey-paperless-ngx-the-document-management-game-changer-part-5</link><guid isPermaLink="true">https://blog.yaffalab.com/my-homelab-journey-paperless-ngx-the-document-management-game-changer-part-5</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[SelfHosting]]></category><category><![CDATA[Paperless-ngx]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[paperless]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 02 Jun 2025 07:26:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747908060027/4e7b43be-9dfe-413f-9f78-fe9f6c25e2c3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again!</p>
<p>Welcome to Part 5 of my homelab journey series! After setting up my Lenovo M920 Tiny with Proxmox (as you saw in Part 4), I've been exploring various self-hosted applications. Today, I want to share what has become my absolute favorite service: Paperless-ngx! 😄</p>
<h2 id="heading-why-paperless-ngx-changed-my-life">Why Paperless-ngx Changed My Life</h2>
<p>If you're anything like me, you've experienced that moment of panic when you need an important document—insurance certificate, warranty, medical record—and have absolutely no idea where you put it. Before Paperless-ngx, my "filing system" consisted of various folders, drawers.</p>
<p>Paperless-ngx solved this problem so elegantly. As their website puts it: <em>"Paperless-ngx is a community-supported open-source document management system that transforms your physical documents into a searchable online archive so you can keep, well, less paper."</em></p>
<h2 id="heading-what-makes-paperless-ngx-so-powerful">What Makes Paperless-ngx So Powerful?</h2>
<h3 id="heading-1-comprehensive-document-organization">1. Comprehensive Document Organization</h3>
<p>Paperless-ngx doesn't just store your documents—it makes them truly searchable and organized:</p>
<ul>
<li><p><strong>Full-text search</strong>: Find any document by searching its contents, not just the filename</p>
</li>
<li><p><strong>Tagging system</strong>: Create custom tags for easy categorization</p>
</li>
<li><p><strong>Custom fields</strong>: My favorite feature! I created a "Physical Location" field so I can note exactly where the original document is stored in my home</p>
</li>
<li><p><strong>Automatic document parsing</strong>: It can extract dates, correspondents, and other information automatically</p>
</li>
</ul>
<h3 id="heading-2-smart-document-processing">2. Smart Document Processing</h3>
<p>The OCR (Optical Character Recognition) capabilities are impressive:</p>
<ul>
<li><p>Converts scanned images to searchable text</p>
</li>
<li><p>Supports multiple languages (I use French and English)</p>
</li>
<li><p>Can automatically categorize documents based on content</p>
</li>
<li><p>Creates thumbnails for easy visual identification</p>
</li>
</ul>
<h3 id="heading-3-flexible-input-methods">3. Flexible Input Methods</h3>
<p>Getting documents into the system is remarkably flexible:</p>
<ul>
<li><p>Direct upload through the web interface</p>
</li>
<li><p>Mobile app for scanning on the go</p>
</li>
<li><p>Watch folder for automatic importing</p>
</li>
</ul>
<h2 id="heading-my-paperless-workflow">My Paperless Workflow</h2>
<p>After some experimentation, I've settled on a workflow that works perfectly for me:</p>
<ol>
<li><p><strong>Document scanning</strong>: I use my phone to scan documents via the Google Drive app (it has excellent document scanning capabilities)</p>
</li>
<li><p><strong>Automatic import</strong>: A Node.js script I wrote monitors my Google Drive folder hourly and copies new documents to Paperless-ngx's consumption folder</p>
</li>
<li><p><strong>Processing</strong>: Paperless-ngx automatically OCRs the documents and makes an initial attempt at classification</p>
</li>
<li><p><strong>Organization</strong>: Once a week, I log into the dashboard to verify and adjust tags, categories, and add any custom field information</p>
</li>
<li><p><strong>Search and retrieve</strong>: Whenever I need a document, a quick search brings it up instantly!</p>
</li>
</ol>
<p>The mobile app has been a game-changer for quick document retrieval. No more digging through filing cabinets when I need to reference something while on the phone with my insurance company!</p>
<h2 id="heading-setting-up-paperless-ngx">Setting Up Paperless-ngx</h2>
<p>I won't sugar-coat it—setting up Paperless-ngx wasn't as straightforward as some of my other services. But the effort was absolutely worth it! Here's the Docker Compose configuration that's been working flawlessly for me:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">broker:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/library/redis:7</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/redis:/data</span>

  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/library/postgres:16.0-alpine</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/db:/var/lib/postgresql/data</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">paperless</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">paperless</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">paperless</span>

  <span class="hljs-attr">webserver:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/paperless-ngx/paperless-ngx:latest</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">broker</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8033:8000"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/data:/usr/src/paperless/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/media:/usr/src/paperless/media</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/export:/usr/src/paperless/export</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/data/paperless/consume:/usr/src/paperless/consume</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">PAPERLESS_ADMIN_USER:</span> <span class="hljs-string">"your_username"</span>
      <span class="hljs-attr">PAPERLESS_ADMIN_PASSWORD:</span> <span class="hljs-string">"your_secure_password"</span>
      <span class="hljs-attr">PAPERLESS_REDIS:</span> <span class="hljs-string">redis://broker:6379</span>
      <span class="hljs-attr">PAPERLESS_DBHOST:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">USERMAP_UID:</span> <span class="hljs-number">1000</span>
      <span class="hljs-attr">USERMAP_GID:</span> <span class="hljs-number">1000</span>
      <span class="hljs-attr">PAPERLESS_SECRET_KEY:</span> <span class="hljs-string">"generate_a_long_random_string_here"</span>
      <span class="hljs-attr">PAPERLESS_TIME_ZONE:</span> <span class="hljs-string">Europe/Paris</span>
      <span class="hljs-attr">PAPERLESS_OCR_LANGUAGE:</span> <span class="hljs-string">fra+eng</span>
      <span class="hljs-attr">PAPERLESS_URL:</span> <span class="hljs-string">"https://your.domain.or.ip"</span>
      <span class="hljs-attr">PAPERLESS_CSRF_TRUSTED_ORIGINS:</span> <span class="hljs-string">"https://your.domain.or.ip"</span>
      <span class="hljs-attr">PAPERLESS_CORS_ALLOWED_HOSTS:</span> <span class="hljs-string">"your.domain.or.ip"</span>
</code></pre>
<p>A few important configuration notes:</p>
<ul>
<li><p>Make sure to replace the placeholder values with your actual username, password, and domain/IP</p>
</li>
<li><p>The <code>PAPERLESS_OCR_LANGUAGE</code> setting is crucial—set it to the languages your documents use</p>
</li>
<li><p>The <code>consume</code> folder is where new documents will be picked up automatically</p>
</li>
<li><p>You'll need to adjust the <code>USERMAP_UID</code> and <code>USERMAP_GID</code> to match your system's user/group IDs</p>
</li>
</ul>
<h2 id="heading-tips-from-my-experience">Tips From My Experience</h2>
<p>After using Paperless-ngx for several months, I've gathered some helpful insights:</p>
<ol>
<li><p><strong>Invest time in creating a tagging system</strong> that makes sense for your documents before importing too many files</p>
</li>
<li><p><strong>Set up document types early</strong> to help with automatic classification</p>
</li>
<li><p><strong>Use custom fields creatively</strong>—I track physical location, importance level, and action required</p>
</li>
<li><p><strong>Configure corresponding parties</strong> for common senders like your bank, insurance, etc.</p>
</li>
</ol>
<p>Of all the services in my homelab, Paperless-ngx has had the most tangible impact on my day-to-day life. The time savings from not hunting for documents, the peace of mind from having everything securely backed up, and the satisfaction of a well-organized system make it the crown jewel of my setup.</p>
<p>If you're on the fence about setting up a document management system, I can't recommend Paperless-ngx enough. Yes, there's an initial time investment to scan and organize your backlog of documents, but the long-term benefits are enormous.</p>
]]></content:encoded></item><item><title><![CDATA[BackPair: A Simple Solution for Local Backup Management]]></title><description><![CDATA[Hello the world,
I'm excited to share BackPair with you today – a desktop application I developed to streamline local backups between folders and devices. This project emerged from two needs: simplifying my own backup process for my PC and homelab, a...]]></description><link>https://blog.yaffalab.com/backpair-a-simple-solution-for-local-backup-management</link><guid isPermaLink="true">https://blog.yaffalab.com/backpair-a-simple-solution-for-local-backup-management</guid><category><![CDATA[Rust]]></category><category><![CDATA[Tauri]]></category><category><![CDATA[Backup]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[SelfHosting]]></category><category><![CDATA[React]]></category><category><![CDATA[yaffalab]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Sat, 31 May 2025 11:27:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747919355235/c2f54ab9-9fa8-4699-b8ac-89da72e6216a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello the world,</p>
<p>I'm excited to share <strong>BackPair</strong> with you today – a desktop application I developed to streamline local backups between folders and devices. This project emerged from two needs: simplifying my own backup process for my PC and homelab, and deepening my knowledge of Rust programming.</p>
<h2 id="heading-what-backpair-does">What BackPair Does</h2>
<p>BackPair makes backing up folders to internal or external devices quick and efficient through a profile-based approach. Instead of manually copying folders one by one, you create profiles with source and destination folder pairs, then execute complete backups with a single click.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748690174725/4e00e057-bc88-4b7a-9b18-33bbbde012b2.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-key-features">Key Features</h2>
<p><strong>Profile Management</strong> – Create and manage multiple backup profiles for different purposes (work, personal, homelab configurations).</p>
<p><strong>Folder Pairing</strong> – Define specific source ("From") and destination ("To") folder pairs within each profile.</p>
<p><strong>One-Click Backup</strong> – Select a profile and launch the backup process with a single button click.</p>
<p><strong>Backup Logs</strong> – View real-time progress as your files are copied, with detailed status tracking.</p>
<p><strong>Cross-Platform</strong> – Works on Windows, macOS, and Linux thanks to Tauri.</p>
<p><strong>Clean Interface</strong> – Modern UI built with React.js and shadcn components keeps things intuitive.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Getting started with BackPair is straightforward:</p>
<ol>
<li><p><a target="_blank" href="https://github.com/AyubTouba/back-pair/releases"><strong>Download</strong></a> – Get the latest version from the <a target="_blank" href="https://github.com/AyubTouba/back-pair/releases">Releases</a> page (note: not code-signed yet, so you may see security warnings).</p>
</li>
<li><p><strong>Create Profiles</strong> – Navigate to "Add Profile," name it, and add your folder pairs.</p>
</li>
<li><p><strong>Run Backups</strong> – Select your profile and click "Launch Backup." The logs will show progress in real-time.</p>
</li>
<li><p><strong>Review History</strong> – Check the "History" section to see records of past backup operations.</p>
</li>
</ol>
<p>If you're looking for a straightforward way to manage local backups without the complexity of enterprise solutions, give BackPair a try. It transforms a tedious manual process into a simple, one-click operation – whether you're backing up important documents, media files, or homelab configurations.</p>
<p><strong>Links:</strong></p>
<ul>
<li><p><a target="_blank" href="https://github.com/AyubTouba/back-pair/releases">Download BackPair</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/AyubTouba/back-pair">Source Code</a></p>
</li>
</ul>
<p>Happy backing up!</p>
]]></content:encoded></item><item><title><![CDATA[My Homelab Journey: Upgrading to a Lenovo M920 Tiny with Proxmox - Part 4]]></title><description><![CDATA[Hello again!
After playing around with my Raspberry Pi 4 setup for a while (as you saw in my previous articles), I started feeling the itch to try something with a bit more muscle. Don't get me wrong, the Pi has been amazing for getting my feet wet i...]]></description><link>https://blog.yaffalab.com/my-homelab-journey-upgrading-to-a-lenovo-m920-tiny-with-proxmox-part-4</link><guid isPermaLink="true">https://blog.yaffalab.com/my-homelab-journey-upgrading-to-a-lenovo-m920-tiny-with-proxmox-part-4</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[SelfHosting]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Fri, 23 May 2025 06:00:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747905954357/7f022e2a-f8cf-412d-ac1d-7d173532cdee.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again!</p>
<p>After playing around with my Raspberry Pi 4 setup for a while (as you saw in my previous articles), I started feeling the itch to try something with a bit more muscle. Don't get me wrong, the Pi has been amazing for getting my feet wet in the homelab world, but I was starting to bump into its limitations when trying to run multiple services at once.</p>
<p>This led me to make the jump to a <strong>Lenovo M920 Tiny</strong> with an <em>Intel i5 8th Generation processor, 32GB RAM, and a 2TB SSD</em>. It's compact, but packs enough power to let me explore all those cool self-hosted applications I've been eyeing on r/homelab! 😄</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747906645033/793f55e2-808b-41a8-9e30-4f44c75ba47b.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-why-upgrade-from-raspberry-pi">Why Upgrade from Raspberry Pi?</h2>
<p>None :D, Let's be honest, once you catch the homelab bug, you're always thinking about the next expansion! 🤓</p>
<h2 id="heading-1-setting-up-proxmox-ve-as-my-hypervisor">1. Setting Up Proxmox VE as My Hypervisor</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747906787680/b6ef6186-8101-464c-84ac-6f9a1b0fe923.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-is-proxmox">What is Proxmox?</h3>
<p>One of the first things I kept seeing mentioned in homelab communities was Proxmox Virtual Environment (Proxmox VE). Their website describes it as "a complete open-source platform for enterprise virtualization. With the built-in web interface, you can easily manage VMs and containers."</p>
<p>Basically, it's an operating system designed specifically for running and managing virtual machines and containers, all through a nice web interface. After digging into Reddit threads and YouTube videos, I was sold on giving it a try!</p>
<h3 id="heading-why-proxmox">Why Proxmox?</h3>
<p>The more I researched, the more Proxmox seemed like the perfect fit:</p>
<ul>
<li><p>It's completely free and open-source</p>
</li>
<li><p>Comes with a user-friendly web interface</p>
</li>
<li><p>Has a super active community (which is great when you inevitably get stuck!)</p>
</li>
<li><p>Seems powerful enough for my needs but not overly complicated</p>
</li>
</ul>
<h3 id="heading-11-proxmox-installation">1.1 Proxmox Installation</h3>
<p>Rather than boring you with step-by-step instructions (which would just be me copy-pasting from better guides), I'll share the resources that saved me a ton of headaches:</p>
<ul>
<li><p>Best Proxmox course : <a target="_blank" href="https://www.youtube.com/playlist?list=PLT98CRl2KxKHnlbYhtABg6cF50bYa8Ulo">https://www.youtube.com/playlist?list=PLT98CRl2KxKHnlbYhtABg6cF50bYa8Ulo</a></p>
</li>
<li><p>How to install proxmox: <a target="_blank" href="https://www.youtube.com/watch?v=u8E3-Zy9NvI&amp;list=PLT98CRl2KxKHnlbYhtABg6cF50bYa8Ulo">https://www.youtube.com/watch?v=u8E3-Zy9NvI&amp;list=PLT98CRl2KxKHnlbYhtABg6cF50bYa8Ulo</a></p>
</li>
</ul>
<p>I just created a bootable USB drive with the Proxmox VE ISO, plugged it into my Lenovo, and followed the guided installation. It was surprisingly straightforward, and within about 30 minutes I had Proxmox up and running!</p>
<h2 id="heading-2-creating-my-main-home-server-vm">2. Creating My Main Home Server VM</h2>
<p>With Proxmox happily installed, the next step was to create a virtual machine to be my main home server. I went with Ubuntu Server 24.04 LTS because it's what I'm most familiar with.</p>
<h3 id="heading-21-vm-configuration">2.1 VM Configuration</h3>
<p>For my home server VM, I allocated:</p>
<ul>
<li><p>4 CPU cores</p>
</li>
<li><p>8GB RAM (keeping some headroom for other VMs)</p>
</li>
<li><p>100GB storage (plenty for Docker containers)</p>
</li>
<li><p>Bridged network adapter so it would get its own IP on my network</p>
</li>
</ul>
<h3 id="heading-22-software-stack">2.2 Software Stack</h3>
<p>I stuck with the same software stack I was already using on my Raspberry Pi:</p>
<ul>
<li><p><strong>Docker</strong>: For running containerized applications</p>
</li>
<li><p><strong>Portainer</strong>: For easy container management</p>
</li>
<li><p><strong>Nginx Proxy Manager (NPM)</strong>: For handling reverse proxy and SSL stuff</p>
</li>
<li><p><strong>AdGuard Home</strong>: For network-wide ad blocking</p>
</li>
</ul>
<p>The migration was surprisingly smooth - Docker makes it easy to export/import settings between systems, which saved me tons of time!</p>
<h2 id="heading-3-my-current-homelab-architecture">3. My Current Homelab Architecture</h2>
<p>So now my homelab has evolved into something a bit more complex but way more capable:</p>
<ol>
<li><p><strong>Internet Router/Firewall</strong>: The usual gateway to the internet</p>
</li>
<li><p><strong>Lenovo M920 Tiny running Proxmox VE</strong>: The new powerhouse of my setup</p>
<ul>
<li><p><strong>Main Home Server VM (Ubuntu 24.04)</strong>: Running with all these services:</p>
<ul>
<li><p>Homepage, AdGuard Home, Nginx Proxy Manager, Stirling PDF</p>
</li>
<li><p>N8n, Kavita, YTB Audio (one of the web applications I built), , SnippetBox</p>
</li>
<li><p>Kuma, PaperLess, Wallos, Watchower</p>
</li>
</ul>
</li>
<li><p><strong>Kali Linux VM</strong>: Because why not have a security testing playground?</p>
</li>
</ul>
</li>
<li><p><strong>Raspberry Pi 4 (8GB RAM)</strong>: Now dedicated to running just one thing:</p>
<ul>
<li><strong>Immich</strong>: My self-hosted Google Photos alternative that I talked about in Part 3</li>
</ul>
</li>
</ol>
<p>I've included a diagram of the setup below to make it easier to visualize:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747906225665/e048c171-6c5d-440b-b007-c3e611c47924.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-31-why-keep-the-raspberry-pi-for-immich">3.1 Why Keep the Raspberry Pi for Immich?</h3>
<p>You might be wondering why I kept Immich running on the Raspberry Pi instead of moving it to the new server. A few reasons:</p>
<ol>
<li><p><strong>It's working perfectly</strong>: As I mentioned in Part 3, I went through the whole process of setting up Immich with an external HDD on the Pi, and it's been running flawlessly</p>
</li>
<li><p><strong>Dedicated photo machine</strong>: Having a separate device just for photo backups means I can tinker with the main server without worrying about my precious photos</p>
</li>
</ol>
<h2 id="heading-4-future-plans-amp-services">4. Future Plans &amp; Services</h2>
<p>Since upgrading to more powerful hardware, I've been able to add several new services that weren't practical on the Raspberry Pi. I won't go into depth about each one here (that would make this post way too long!), but I'll be writing detailed articles about the most interesting ones in future posts.</p>
<p>I've been particularly excited about exploring things like <strong>Paperless-ngx</strong> for document management, N8n for automating workflows, and diving into security testing with the Kali Linux VM.</p>
<p>As my homelab journey continues, I'm already thinking about next steps:</p>
<ol>
<li><p><strong>Setting up proper backups</strong>: Right now it's a bit manual</p>
</li>
<li><p><strong>Network segregation</strong>: Maybe some VLANs to separate things?</p>
</li>
<li><p><strong>Home automation</strong>: Home Assistant is tempting me...</p>
</li>
</ol>
<p>This homelab adventure continues to be both educational and fun - there's always something new to learn and try out. As I explore more services and configurations, I'll be sharing my experiences in future articles.</p>
<p>Have you made a similar upgrade in your homelab? Or still thinking about taking the plunge beyond Raspberry Pi? Let me know in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[Tool Builders: Why Creating Developer Libraries Accelerates Learning and Collaboration]]></title><description><![CDATA[As developers, we're surrounded by an abundance of tools and libraries designed to make our lives easier. From React component libraries to code generators, these tools save us countless hours of repetitive work. But have you ever built one yourself?...]]></description><link>https://blog.yaffalab.com/tool-builders-why-creating-developer-libraries-accelerates-learning-and-collaboration</link><guid isPermaLink="true">https://blog.yaffalab.com/tool-builders-why-creating-developer-libraries-accelerates-learning-and-collaboration</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Self Improvement ]]></category><category><![CDATA[Collaboration]]></category><category><![CDATA[tools]]></category><category><![CDATA[Career]]></category><category><![CDATA[career advice]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Tue, 18 Mar 2025 10:42:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741961648748/b51ac61c-664a-4c6e-996a-f6a656055540.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As developers, we're surrounded by an abundance of tools and libraries designed to make our lives easier. From React component libraries to code generators, these tools save us countless hours of repetitive work. But have you ever built one yourself? I did—and it transformed the way I code, collaborate, and think as a developer.</p>
<p>When I first started building custom libraries and generators, I wasn't just trying to speed up my workflow—I was unknowingly embarking on a journey that dramatically leveled up my skills. Creating tools forces you to think about reusability, design patterns, and maintainability in ways that day-to-day project work often doesn't require.</p>
<p>After working on numerous collaborative projects throughout my career, I felt compelled to share the invaluable lessons I've learned. My latest collaboration involves a ReactJS library that perfectly exemplifies why I'm so passionate about the power of working together. These experiences building developer tools inspired me to write this article and share these insights with both beginners and professionals alike.</p>
<p>In this article, I'll share insights from my experience developing two tools that have reshaped my development workflow: JERYO (a ReactJS component library built in collaboration with <a target="_blank" href="https://github.com/DevDeNiro">@Jérémy</a>) and IyaGenerator (a Laravel &amp; InertiaJS package). Both started as solutions to specific problems but evolved into comprehensive toolkits. They not only saved time but also became vehicles for learning and collaboration.</p>
<p>Whether you're a seasoned developer looking to optimize your workflow or a student eager to deepen your understanding of software design, I hope my experiences will inspire you to consider building tools—not just as a means to an end, but as an invaluable learning opportunity.</p>
<hr />
<h2 id="heading-accelerating-your-learning-through-tool-creation">Accelerating Your Learning Through Tool Creation</h2>
<p>Building your own developer tools isn’t just about solving immediate problems—it’s one of the most effective ways to deepen your technical knowledge and programming skills. Tools like <strong>Jeryo</strong> and <strong>IyaGenerator</strong> provided learning opportunities that regular project work couldn’t match.</p>
<h3 id="heading-mastering-reusability">Mastering Reusability</h3>
<p>One of the first challenges you’ll face when building a tool is deciding what should be configurable versus hardcoded. For <strong>Jeryo</strong>, we had to identify common dashboard patterns that could be reused across different projects. This forced us to think beyond the immediate use case and consider:</p>
<ul>
<li><p>Which components need to be customizable?</p>
</li>
<li><p>What default behaviors make sense for most scenarios?</p>
</li>
<li><p>How can we make our API intuitive while maintaining flexibility?</p>
</li>
</ul>
<p>This process of abstraction is a fundamental skill. You start seeing patterns where before you only saw one-off solutions. That shift in thinking has improved the quality and consistency of my code across all projects.</p>
<h3 id="heading-design-patterns-in-practice">Design Patterns in Practice</h3>
<p>Building libraries pushes you to implement design patterns you might have only read about. When developing <strong>Jeryo's</strong> form composers, we naturally gravitated toward the <strong>Composite Pattern</strong>, which allowed us to treat individual form fields and groups of fields uniformly. This made handling complex nested forms much easier.</p>
<p>For <strong>IyaGenerator</strong>, I implemented a <strong>Template Method Pattern</strong>. It let me define the overall structure of code generation while allowing for customization of specific sections. This ensured that each generated component followed a consistent structure, yet remained flexible for different use cases.</p>
<p>These weren’t academic exercises—they were practical, hands-on solutions to real problems. Implementing design patterns in tools you’ll actually use makes abstract concepts concrete and unforgettable.</p>
<h3 id="heading-thinking-about-developer-experience">Thinking About Developer Experience</h3>
<p>Perhaps most importantly, building tools shifts your perspective from user experience to developer experience. You start asking:</p>
<ul>
<li><p>Is this API intuitive for other developers?</p>
</li>
<li><p>How comprehensive should the documentation be?</p>
</li>
<li><p>What are the common pitfalls users might encounter?</p>
</li>
</ul>
<p>This empathy for fellow developers pushes you to write clearer, more maintainable code in all contexts—not just when building libraries.</p>
<h2 id="heading-two-minds-are-better-than-one">Two Minds Are Better Than One</h2>
<p>One of the most valuable lessons I've learned in my development journey is that collaboration brings perspectives you simply can't see with your own eyes. When working with others, you gain access to different approaches, techniques, and solutions that might never have occurred to you working alone.</p>
<p>This became especially apparent when collaborating on our ReactJS/TypeScript library designed to facilitate dashboard building. What began as a straightforward project evolved when we discovered we had different visions for its direction. Rather than seeing this as an obstacle, we recognized it as an opportunity. These divergent perspectives ultimately led us to a much bigger idea that we're now excited to pursue together.</p>
<p>The benefits of this collaborative approach extended far beyond the technical aspects. Working with another developer pushed me to:</p>
<ul>
<li><p>Articulate my design choices more clearly</p>
</li>
<li><p>Consider edge cases I hadn't encountered individually</p>
</li>
<li><p>Question assumptions I might have otherwise missed</p>
</li>
<li><p>Find elegant compromises that satisfied multiple needs</p>
</li>
</ul>
<p>These different viewpoints created a natural system of checks and balances that improved the final product in ways that would have been impossible working solo. The library became more robust, flexible, and ultimately more useful because it incorporated multiple perspectives from the start.</p>
<h2 id="heading-tools-that-solve-real-problems">Tools That Solve Real Problems</h2>
<p>While learning is a valuable byproduct, it’s usually the <strong>practical benefits</strong> that spark the initial development of custom tools. They shine in scenarios where off-the-shelf solutions fall short or require too much customization.</p>
<h3 id="heading-standardizing-best-practices">Standardizing Best Practices</h3>
<p>When working in teams, maintaining consistent coding standards and architectural patterns can be challenging. Custom tools can bake these best practices directly into generated code or reusable components.</p>
<p>With <strong>Jeryo</strong>, we embedded our team’s preferred approaches to:</p>
<ul>
<li><p>Form validation</p>
</li>
<li><p>Error handling</p>
</li>
<li><p>State management</p>
</li>
<li><p>Component composition</p>
</li>
</ul>
<p>Similarly, <strong>IyaGenerator</strong> produces code that follows our established architectural patterns. This ensures consistency, even as team members come and go.</p>
<h3 id="heading-addressing-domain-specific-requirements">Addressing Domain-Specific Requirements</h3>
<p>Generic tools often struggle with industry- or domain-specific needs. Custom tools, on the other hand, can directly incorporate specialized knowledge.</p>
<p>For example, <strong>IyaGenerator</strong> includes templates for common business entities like users, products, and orders, complete with relationships and validation rules. This domain expertise makes it particularly valuable for certain types of applications.</p>
<h3 id="heading-quick-iteration-and-deployment">Quick Iteration and Deployment</h3>
<p>The most immediate benefit of custom tools? Speed.</p>
<p>What used to take days can now take hours—or even minutes. This acceleration enables:</p>
<ul>
<li><p>Faster prototyping</p>
</li>
<li><p>More time for testing and refinement</p>
</li>
<li><p>Quicker response to changing requirements</p>
</li>
<li><p>Lower development costs</p>
</li>
</ul>
<p>With <strong>IyaGenerator</strong>, I can deploy a complete admin interface with full CRUD capabilities for a new entity in under five minutes—including database migrations, backend logic, and frontend components. This level of efficiency has transformed my development workflow.</p>
<hr />
<h2 id="heading-growing-through-collaboration">Growing Through Collaboration</h2>
<p>Building tools isn't just a technical endeavor—it's a social one. Collaborating on developer tools revealed the unique benefits of working together on these projects.</p>
<h4 id="heading-real-world-benefits-for-every-developer">Real-World Benefits for Every Developer</h4>
<p>For junior developers, collaboration offers a window into the real world of professional development. It challenges you to:</p>
<ul>
<li><p>Explain and defend your code choices</p>
</li>
<li><p>Understand different coding styles</p>
</li>
<li><p>Learn industry best practices</p>
</li>
<li><p>Develop crucial communication skills</p>
</li>
<li><p>Build a professional network</p>
</li>
</ul>
<p>For seasoned professionals, collaboration continues to provide value through:</p>
<ul>
<li><p>Cross-pollination of ideas</p>
</li>
<li><p>Accountability and motivation</p>
</li>
<li><p>Shared workload during complex projects</p>
</li>
<li><p>Continuous learning through knowledge exchange</p>
</li>
</ul>
<h4 id="heading-diverse-perspectives-lead-to-better-design">Diverse Perspectives Lead to Better Design</h4>
<p>When multiple developers bring different experiences and preferences to the table, these diverse perspectives push the project forward:</p>
<ul>
<li><p>Architectural decisions become more thoroughly vetted</p>
</li>
<li><p>UX considerations become more comprehensive</p>
</li>
<li><p>Documentation improves as different learning styles are considered</p>
</li>
<li><p>Edge cases are identified earlier in the development process</p>
</li>
</ul>
<h4 id="heading-momentum-and-accountability">Momentum and Accountability</h4>
<p>Solo projects often stall when life gets busy. Collaboration creates a natural accountability system:</p>
<ul>
<li><p>Regular check-ins keep things moving forward</p>
</li>
<li><p>Shared ownership increases commitment</p>
</li>
<li><p>Different strengths help overcome obstacles</p>
</li>
<li><p>One person's enthusiasm can reignite motivation in the other</p>
</li>
</ul>
<h4 id="heading-community-building">Community Building</h4>
<p>Even small tools can build communities. My libraries started as tools just for my teams, but once shared with colleagues, they sparked valuable conversations and contributions.</p>
<p>That small community provided:</p>
<ul>
<li><p>Helpful feedback</p>
</li>
<li><p>Feature suggestions we hadn't considered</p>
</li>
<li><p>Edge case identification</p>
</li>
<li><p>A sense of shared purpose</p>
</li>
</ul>
<p>When developed collaboratively, even simple tools can become focal points for ongoing learning and professional connection.</p>
<hr />
<h2 id="heading-building-your-path-forward">Building Your Path Forward</h2>
<p>The journey of creating custom tools like <strong>Jeryo</strong> and <strong>IyaGenerator</strong> has been transformative. Yes, they save time—but the deeper benefits of <strong>learning</strong>, <strong>collaboration</strong>, and <strong>standardization</strong> have proven even more valuable.</p>
<h3 id="heading-key-takeaways">Key Takeaways</h3>
<p>Building your own developer tools offers multiple layers of value:</p>
<ul>
<li><p><strong>Learning acceleration</strong>: You’ll master reusability, design patterns, and advanced language features in a practical context.</p>
</li>
<li><p><strong>Workflow optimization</strong>: Custom tools tailored to your workflow eliminate repetitive work and standardize best practices.</p>
</li>
<li><p><strong>Collaborative growth</strong>: Working with others creates opportunities for knowledge sharing and builds stronger professional relationships.</p>
</li>
</ul>
<h3 id="heading-start-small-think-big">Start Small, Think Big</h3>
<p>You don’t have to build a framework to get started. Begin with a small utility that solves a specific pain point in your workflow. As you gain confidence and see the benefits, you’ll naturally expand its capabilities—or create complementary tools.</p>
<p>The <strong>Jeryo</strong> library started as a simple collection of form components before evolving into a complete dashboard solution. <strong>IyaGenerator</strong> began with basic CRUD generation and grew to handle complex relationships and custom deployments.</p>
<hr />
<h3 id="heading-your-next-tool-might-be-the-one-you-build-yourself">Your Next Tool Might Be the One You Build Yourself</h3>
<p>If you’ve ever found yourself thinking <em>“there has to be a better way,”</em> that’s the perfect place to start. Your next favorite tool might be the one you build—so why not get started today?</p>
]]></content:encoded></item><item><title><![CDATA[My Homelab Journey with Raspberry Pi: Immich for Photo Backup - Part 3]]></title><description><![CDATA[Hello again!
After setting up my Raspberry Pi with various services and getting everything to run smoothly, another need popped into my mind—creating a reliable photo backup solution at home.
Sure, we all rely on cloud services to store our photos an...]]></description><link>https://blog.yaffalab.com/my-homelab-journey-with-raspberry-pi-immich-for-photo-backup-part-3</link><guid isPermaLink="true">https://blog.yaffalab.com/my-homelab-journey-with-raspberry-pi-immich-for-photo-backup-part-3</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[Immich]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[Portainer]]></category><category><![CDATA[harddrive]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 16 Dec 2024 08:00:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734284978075/4196645d-8792-4fa0-bc0c-c6638f0608e3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello again!</p>
<p>After setting up my Raspberry Pi with various services and getting everything to run smoothly, another need popped into my mind—creating a reliable photo backup solution at home.</p>
<p>Sure, we all rely on cloud services to store our photos and files, but let’s be honest—those costs climb as your storage needs grow. I’m not quite ready to ditch the cloud entirely (yet 😄), but having an additional local backup sounded like a smart move.</p>
<p>For this experiment, I repurposed an old external HDD that I wasn’t using anymore, thanks to a shiny new SSD. Then, I discovered <strong>Immich</strong>, a self-hosted photo and video management solution. Spoiler alert: it’s pretty awesome! You can check it out here: <a target="_blank" href="https://immich.app/">Immich</a>.</p>
<p>Let me walk you through how I set everything up, from prepping the hardware to deploying Immich.</p>
<h2 id="heading-preparing-the-external-hdd">Preparing the External HDD</h2>
<p>To start, I connected my external HDD to the Raspberry Pi via the blue USB port. Typically, these drives won’t mount automatically, so let’s confirm its status. I ran the command:</p>
<pre><code class="lang-bash">df -h
</code></pre>
<p>As expected, the drive wasn’t listed. So, I checked all block devices with:</p>
<pre><code class="lang-bash">lsblk
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734283822746/8d38c1ff-fff4-41e0-9209-eb82089be980.png" alt class="image--center mx-auto" /></p>
<p>Here’s where the HDD showed up as <code>/dev/sda</code>. Now, before proceeding, I decided to format the drive. (Quick tip: <strong>Make sure you’ve backed up any important data on the disk first!</strong>)</p>
<h3 id="heading-formatting-the-drive">Formatting the Drive</h3>
<p>Using <code>fdisk</code>, I created a new partition table and formatted the drive:</p>
<ol>
<li><p>Opened the disk utility:</p>
<pre><code class="lang-bash">  fdisk /dev/sda
</code></pre>
</li>
<li><p>Used the following commands within <code>fdisk</code>:</p>
<ul>
<li><p><code>o</code> to create a new DOS (MBR) disk label.</p>
</li>
<li><p><code>n</code> for a new partition, choosing <code>p</code> for primary, and leaving the other options as default.</p>
</li>
<li><p><code>w</code> to write the changes.</p>
</li>
</ul>
</li>
<li><p>Formatted the new partition to the <code>ext4</code> filesystem:</p>
<pre><code class="lang-bash"> sudo mkfs -t ext4 /dev/sda1
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734283891394/6ce72f16-35a0-4837-8782-c8712ae85b0e.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-mounting-the-drive-on-the-raspberry-pi">Mounting the Drive on the Raspberry Pi</h3>
<p>With the external HDD formatted and ready, the next step was mounting it to the Raspberry Pi.</p>
<h4 id="heading-step-1-create-a-mount-point">Step 1: Create a Mount Point</h4>
<p>First, I created a folder where the drive would be mounted:</p>
<pre><code class="lang-bash">sudo mkdir -p /mnt/home-data
</code></pre>
<p>Then, I assigned the folder permissions to my user:</p>
<pre><code class="lang-bash">sudo chown -R ayub:ayub /mnt/home-data
</code></pre>
<p>(<em>Ps: Replace</em> <code>ayub</code> with your actual username.)</p>
<h4 id="heading-step-2-automatically-mount-the-drive">Step 2: Automatically Mount the Drive</h4>
<p>To ensure the drive mounts automatically every time the Raspberry Pi boots up, I gathered the necessary details using:</p>
<pre><code class="lang-bash">sudo blkid /dev/sda1
</code></pre>
<p>This command returned something like this:</p>
<pre><code class="lang-bash">/dev/sda1: UUID=<span class="hljs-string">"xxxxx-xxxx-xxxx-xxxx"</span> BLOCK_SIZE=<span class="hljs-string">"4096"</span> TYPE=<span class="hljs-string">"ext4"</span> PARTUUID=<span class="hljs-string">"b12bb460-01"</span>
</code></pre>
<p>I noted the <code>UUID</code> and <code>TYPE</code> values because we’d need them for the next step.</p>
<h4 id="heading-step-3-modify-the-fstab-file">Step 3: Modify the <code>fstab</code> File</h4>
<p>The <code>fstab</code> file controls how drives are mounted in Linux. I edited it with the following command:</p>
<pre><code class="lang-bash">sudo nano /etc/fstab
</code></pre>
<p>At the bottom of the file, I added this line:</p>
<pre><code class="lang-bash">UUID=xxxxx-xxxx-xxxx-xxxx /mnt/home-data ext4 defaults,auto,users,rw,nofail,noatime 0 0
</code></pre>
<p>(<em>Replace</em> <code>xxxxx-xxxx-xxxx-xxxx</code> with your UUID and <code>ext4</code> with your filesystem type.)</p>
<p>After saving the file (CTRL + X, then Y, and ENTER), I unmounted the drive to apply the changes:</p>
<pre><code class="lang-bash">sudo umount /dev/sda1
</code></pre>
<p>Finally, I re-mounted it with:</p>
<pre><code class="lang-bash">sudo mount -a
</code></pre>
<p>The drive was now ready to use, automatically mounting on every boot.</p>
<hr />
<h2 id="heading-installing-immich">Installing Immich</h2>
<p><img src="https://immich.app/img/logomark-light.svg" alt class="image--center mx-auto" /></p>
<p>With the hard drive set up, I moved on to installing Immich using Portainer. Immich is a fantastic self-hosted photo and video management solution with a sleek interface and powerful features.</p>
<p>Here’s how I set it up:</p>
<h4 id="heading-step-1-deploy-immich-via-portainer">Step 1: Deploy Immich via Portainer</h4>
<ol>
<li><p>Opened Portainer and navigated to "<strong>Stacks</strong>" in the sidebar.</p>
</li>
<li><p>Clicked on "<strong>Add Stack</strong>" and gave it the name <strong>immich</strong>.</p>
</li>
<li><p>Selected "<strong>Web Editor</strong>" as the build method.</p>
</li>
<li><p>Copied the content of the <code>docker-compose.yml</code> file from the official <a target="_blank" href="https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml">Immich GitHub repository</a> and pasted it into the editor.</p>
</li>
<li><p>Updated the environment variables:</p>
<ul>
<li><p>Switched <code>.env</code> references to <code>stack.env</code>.</p>
</li>
<li><p>Pasted the contents of <code>example.env</code> from the same GitHub repository into the <strong>Environment Variables</strong> editor.</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://immich.app/assets/images/dot-env-9d5e65f0228d512fff2efd9841a18d3c.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-step-2-configure-environment-variables">Step 2: Configure Environment Variables</h4>
<p><img src="https://immich.app/assets/images/env-1-4e444bbed7269766444b939211014f7c.png" alt class="image--center mx-auto" /></p>
<p><img src="https://immich.app/assets/images/env-2-ee71f97989cf9c0d4b8f93ec182c6820.png" alt class="image--center mx-auto" /></p>
<p>In <strong>Simple Mode</strong>, I made the following changes:</p>
<ul>
<li><p>Set a secure <code>DB_PASSWORD</code>.</p>
</li>
<li><p>Updated <code>DB_DATA_LOCATION</code> and <code>UPLOAD_LOCATION</code> <strong><em>(in our case is /mnt/home-data)</em></strong> paths to point to folders on the external HDD.</p>
</li>
</ul>
<h4 id="heading-step-3-deploy-the-stack">Step 3: Deploy the Stack</h4>
<p>Finally, I clicked on "<strong>Deploy the Stack</strong>."</p>
<p>(<em>Note: If you encounter an error like</em> <code>can't set healthcheck.start_interval as feature requires Docker Engine v25 or later</code>, you can fix it by commenting out the <code>start_interval: 30s</code> line in the <code>docker-compose.yml</code> file.)</p>
<h2 id="heading-creating-a-proxy-host-for-immich">Creating a Proxy Host for Immich</h2>
<p>Once Immich was up and running, it was time to make it accessible through a neat, custom domain name. For this, I used Nginx Proxy Manager.</p>
<h4 id="heading-step-1-connect-immich-to-the-nginx-proxy-in-portainer">Step 1: Connect Immich to the Nginx Proxy in Portainer</h4>
<p>Since Immich was running inside Docker via Portainer, I needed to link it to my Nginx Proxy container. Here’s how I did it:</p>
<ol>
<li><p>In Portainer, I made sure Immich was part of the same Docker network as the Nginx Proxy Manager. To do this:</p>
<ul>
<li><p>I opened Portainer and went to the <strong>Immich</strong> container settings.</p>
</li>
<li><p>Under the <strong>Networks</strong> section, I added the <strong>Nginx Proxy</strong> network (the same network Nginx Proxy Manager was using).</p>
</li>
</ul>
</li>
<li><p>Next, I created a <strong>Proxy Host</strong> in Nginx Proxy Manager, assigning a clean, easy-to-remember domain name (e.g., <code>photos.domaine.com</code>).</p>
</li>
<li><p>I pointed the domain to Immich’s internal container IP and port, and added SSL for extra security.</p>
</li>
</ol>
<p>Now, Immich was accessible through the domain!</p>
<hr />
<h2 id="heading-immich-in-action">Immich in Action</h2>
<p>After everything was set up, I visited the domain URL and was welcomed by the Immich interface. I created my user account and started exploring. One of the coolest features is the mobile app, which allows you to automatically back up your photos. Simply download the Immich app on your phone, enter the URL of your Immich server, and your photos will be backed up automatically, keeping everything synced across your devices. The app works seamlessly and makes it so much easier to manage your photo library on the go!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734284618609/76e78259-3e04-4e6a-8abb-38474334b7de.png" alt class="image--center mx-auto" /></p>
<p>This project was a great way to enhance my home lab with a local photo backup solution. While I’m not ditching the cloud just yet, having a backup I control feels pretty awesome.</p>
<p>Stay tuned for more updates as my homelab journey continues!</p>
]]></content:encoded></item><item><title><![CDATA[My Homelab Journey: Step-by-Step Guide with Raspberry Pi - Part 2]]></title><description><![CDATA[Welcome back! After setting up Docker and Docker Compose in the first part, it’s time to move on to the fun stuff: Portainer, Nginx Proxy Manager (NPM), and AdGuard Home. These tools will take my homelab to the next level, helping me manage container...]]></description><link>https://blog.yaffalab.com/my-homelab-journey-step-by-step-guide-with-raspberry-pi-part-2</link><guid isPermaLink="true">https://blog.yaffalab.com/my-homelab-journey-step-by-step-guide-with-raspberry-pi-part-2</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[server]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Portainer]]></category><category><![CDATA[adguardhome]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Thu, 12 Dec 2024 08:00:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733693044587/2ef181fd-fdab-4f98-aeb5-bfecf7e42155.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back! After setting up Docker and Docker Compose in the first part, it’s time to move on to the fun stuff: <strong>Portainer</strong>, <strong>Nginx Proxy Manager (NPM)</strong>, and <strong>AdGuard Home</strong>. These tools will take my homelab to the next level, helping me manage containers, secure services with SSL, and handle DNS issues like a pro. Let me walk you through what I did.</p>
<h2 id="heading-portainer-simplifying-docker-management"><strong>Portainer: Simplifying Docker Management</strong></h2>
<p>I love how Docker makes managing containers easy, but typing commands every time can get tedious. That’s where <strong>Portainer</strong> comes in! It’s a web-based tool that lets me manage Docker with just a few clicks.</p>
<hr />
<h3 id="heading-setting-up-portainer">Setting Up Portainer</h3>
<p>First, let’s create a volume to store Portainer’s database:</p>
<p><code>docker volume create portainer_data</code></p>
<p>Next, download and install the Portainer Server container:</p>
<pre><code class="lang-bash">docker run -d \
  -p 8000:8000 \
  -p 9443:9443 \
  -p 9000:9000 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:2.21.4
</code></pre>
<p>Once done, open your browser and navigate to:</p>
<p><code>https://&lt;your-pi-address-or-hostname&gt;:9443</code></p>
<p>In my case, I used <code>https://pifour.local:9000</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733597485517/3203e535-8830-496a-ae40-3be888c2f230.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-setting-up-nginx-proxy-manager-for-ssl-certificates">Setting Up Nginx Proxy Manager for SSL Certificates</h2>
<p>After getting Portainer set up, I turned my attention to <strong>Nginx Proxy Manager (NPM)</strong>. Why? Because I wanted to access my services with nice, clean URLs (like <a target="_blank" href="http://app.localdomain.com"><code>app.localdomain.com</code></a>) and secure them with SSL certificates.</p>
<hr />
<h3 id="heading-why-a-domain-name">Why a Domain Name?</h3>
<p>I bought a domain name specifically for local use. The domain is mapped to the Raspberry Pi’s local IP address. This setup allows me to generate SSL certificates and bypass potential DNS rebinding issues.</p>
<h4 id="heading-why-npm">Why NPM?</h4>
<p>Here’s why I’m loving NPM:</p>
<ul>
<li><p><strong>Custom Domains</strong>: No more memorizing IPs and ports.</p>
</li>
<li><p><strong>SSL Made Easy</strong>: Let’s Encrypt certificates with a few clicks.</p>
</li>
<li><p><strong>Simple UI</strong>: Setting up reverse proxies is super straightforward.</p>
</li>
</ul>
<h3 id="heading-installing-nginx-proxy-manager">Installing Nginx Proxy Manager</h3>
<p>We’ll create a stack from the Portainer dashboard:</p>
<ol>
<li><p><strong>Go to Portainer Dashboard.</strong></p>
</li>
<li><p><strong>Click "Add Stack."</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733597510658/611e6a9d-6cad-4758-a702-8e24cfb66e54.png" alt class="image--center mx-auto" /></p>
<p> <strong>Add the</strong> <code>docker-compose.yml</code> content from <a target="_blank" href="https://nginxproxymanager.com/setup/">Nginx Proxy Manager’s setup page</a>. Here’s the content for reference:</p>
</li>
</ol>
<pre><code class="lang-dockerfile">services:
  app:
    image: <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span>
    restart: unless-stopped
    ports:
      - <span class="hljs-string">'80:80'</span>    <span class="hljs-comment"># HTTP</span>
      - <span class="hljs-string">'443:443'</span>  <span class="hljs-comment"># HTTPS</span>
      - <span class="hljs-string">'81:81'</span>    <span class="hljs-comment"># Admin UI</span>
    environment:
      DB_MYSQL_HOST: <span class="hljs-string">"db"</span>
      DB_MYSQL_PORT: <span class="hljs-number">3306</span>
      DB_MYSQL_USER: <span class="hljs-string">"npm"</span>
      DB_MYSQL_PASSWORD: <span class="hljs-string">"npm"</span>
      DB_MYSQL_NAME: <span class="hljs-string">"npm"</span>
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: <span class="hljs-string">'yobasystems/alpine-mariadb:latest'</span>
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: <span class="hljs-string">'npm'</span>
      MYSQL_DATABASE: <span class="hljs-string">'npm'</span>
      MYSQL_USER: <span class="hljs-string">'npm'</span>
      MYSQL_PASSWORD: <span class="hljs-string">'npm'</span>
    volumes:
      - ./mysql:/var/lib/mysql
</code></pre>
<p><strong>Note:</strong> The <code>yobasystems/alpine-mariadb</code> image is used here because it supports ARM devices like the Raspberry Pi.</p>
<p>After deploying the stack, visit: <code>http://&lt;your-pi-address-or-hostname&gt;:81</code></p>
<p><strong>Default Credentials:</strong></p>
<ul>
<li><p><strong>Email:</strong> admin@example.com</p>
</li>
<li><p><strong>Password:</strong> changeme</p>
</li>
</ul>
<p>Update your details and password after logging in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733689715814/1c5cf959-6627-4465-9d12-4cab11f6c55e.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-configuring-ssl-and-fixing-dns-rebinding">Configuring SSL and Fixing DNS Rebinding</h3>
<p>One challenge I faced was <strong>DNS rebinding</strong>. This happens when trying to access local domains, and some routers block them for security reasons. To fix this and improve my network’s DNS, I installed <strong>AdGuard Home</strong>.</p>
<hr />
<h2 id="heading-setting-up-adguard-home">Setting Up AdGuard Home</h2>
<p>AdGuard Home is fantastic for blocking ads and resolving DNS issues like rebinding protection.</p>
<p>Here’s how I set it up:</p>
<ol>
<li><strong>Create a New Stack in Portainer</strong><br /> Add a new stack with the following <code>docker-compose.yml</code> configuration:</li>
</ol>
<pre><code class="lang-dockerfile">services:
  adguardhome:
    image: adguard/adguardhome
    container_name: adguardhome
    restart: unless-stopped
    ports:
      - <span class="hljs-string">"8080:80/tcp"</span>  <span class="hljs-comment"># HTTP interface (use alternative ports to avoid conflicts)</span>
      - <span class="hljs-string">"8443:443/tcp"</span> <span class="hljs-comment"># HTTPS interface (use alternative ports to avoid conflicts)</span>
    volumes:
      - /data/adguard-home/work:/opt/adguardhome/work
      - /data/adguard-home/confdir:/opt/adguardhome/conf
</code></pre>
<p>Since <strong>Nginx Proxy Manager</strong> is already using ports 80 and 443, I mapped AdGuard Home to <strong>8080</strong> and <strong>8443</strong>.</p>
<ol start="2">
<li><p><strong>Visit AdGuard’s Setup Page</strong><br /> After deploying the stack, visit <code>http://&lt;your-pi-address-or-hostname&gt;:8080</code> to complete the initial setup.</p>
</li>
<li><p><strong>Set AdGuard as Your Router’s DNS:</strong><br /> Update your router’s DHCP settings to use AdGuard for DNS. For instance, with Freebox OS, you can easily configure the DNS settings in the router's admin panel. The process will be similar for most routers.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733597897142/33e6f5a3-7cbd-47a2-9b4a-83376abdf404.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-fixing-dns-rebinding">Fixing DNS Rebinding</h3>
<p> To resolve DNS rebinding issues and ensure smooth access to your local services, follow these steps:</p>
<ol>
<li><p><strong>Configure DNS Rewrites in AdGuard</strong><br /> In AdGuard Home, go to <strong>Filters → DNS Rewrites</strong>, and add your domain along with the Pi’s local IP address. This will ensure local access to your services without DNS rebinding errors.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733690200867/c017acc4-0ee2-410f-bd4d-8f7f803ceee5.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733690205727/ddd97925-ec3e-40a1-9639-d82a8338793a.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>SSL Setup for Local Domains</strong><br /> To securely use your local domains, I used <strong>Cloudflare</strong> to manage SSL certificates.you can follow this topic that explain well this <a target="_blank" href="https://medium.com/@life-is-short-so-enjoy-it/homelab-nginx-proxy-manager-setup-ssl-certificate-with-domain-name-in-cloudflare-dns-732af64ddc0b">step</a> sHere’s a summary of the steps:</p>
<ul>
<li><p>Buy a domain from a registrar (I used Namecheap).</p>
</li>
<li><p>Set up <strong>DNS</strong> in <strong>Cloudflare</strong> to point to the Raspberry Pi’s local IP address.</p>
</li>
</ul>
</li>
</ol>
</li>
</ol>
<hr />
<h3 id="heading-adding-ssl-certificates-and-proxy-hosts-in-npm">Adding SSL Certificates and Proxy Hosts in NPM</h3>
<h4 id="heading-step-1-connect-adguard-to-npm"><strong>Step 1: Connect AdGuard to NPM</strong></h4>
<p>Before setting up SSL for AdGuard (or any other service), ensure it’s connected to NPM:</p>
<ol>
<li><p>Go to <strong>Portainer → Containers</strong> and find the <code>nginx_proxy_manager-app-1</code> container (or your NPM container name).</p>
</li>
<li><p>Scroll down to the <strong>Connected Networks</strong> section.</p>
</li>
<li><p>Join the AdGuard network so NPM can communicate with it.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733691667996/9df941d7-4566-43db-b80c-a1e1c00f1d1e.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-step-2-add-an-ssl-certificate"><strong>Step 2: Add an SSL Certificate</strong></h4>
<ol>
<li><p>Open <strong>Nginx Proxy Manager (NPM)</strong>.</p>
</li>
<li><p>Go to <strong>SSL Certificates</strong> and click <strong>Add SSL Certificate</strong>.</p>
</li>
<li><p>Use <strong>Let's Encrypt</strong> with <strong>Cloudflare DNS</strong> to validate the certificate.</p>
<ul>
<li>Make sure your domain &amp; subdomain is already set up in Cloudflare and points to your local IP.</li>
</ul>
</li>
</ol>
<h4 id="heading-step-3-create-a-proxy-host"><strong>Step 3: Create a Proxy Host</strong></h4>
<ol>
<li><p>In NPM, navigate to <strong>Hosts → Proxy Hosts</strong> and click <strong>Add Proxy Host</strong>.</p>
</li>
<li><p>Fill in the details:</p>
<ul>
<li><p><strong>Domain Name</strong>: Enter the domain or subdomain (e.g., <a target="_blank" href="http://adguard.localdomain.com">adguard.localdomain.com</a>).</p>
</li>
<li><p><strong>Forward Hostname/IP</strong>: Enter the local IP of the service (e.g., your Raspberry Pi’s IP).</p>
</li>
<li><p><strong>Forward Port</strong>: Enter the port where the service is running.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733691751345/390b07bb-e965-4161-b347-5722f8b05f1d.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p>Go to the <strong>SSL Tab</strong> and select the SSL certificate you created earlier.</p>
</li>
<li><p>Enable the following options for better security and performance:</p>
<ul>
<li><p><strong>Force SSL</strong> (redirects all traffic to HTTPS).</p>
</li>
<li><p><strong>HTTP/2 Support</strong> (makes things faster and smoother).</p>
</li>
</ul>
</li>
<li><p>Save your settings.</p>
</li>
</ol>
<h4 id="heading-step-4-test-your-setup"><strong>Step 4: Test Your Setup</strong></h4>
<p>Open your browser and visit the domain or subdomain you just set up. You should see the service running securely over HTTPS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733691765345/06947181-e221-496d-9675-80f9b7d9ccea.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-step-5-repeat-for-other-services"><strong>Step 5: Repeat for Other Services</strong></h4>
<p>For each additional service, follow the same process:</p>
<ul>
<li><p>Make sure the service is connected to NPM’s network in Portainer.</p>
</li>
<li><p>Add a proxy host in NPM with the correct domain, IP, port, and SSL certificate.</p>
</li>
</ul>
<p>That’s it! Your services are now secured and easily accessible with clean, HTTPS-enabled URLs. 🎉</p>
<hr />
<h2 id="heading-trying-out-an-app-wallos">Trying Out an App: Wallos</h2>
<p>With everything set up, it’s time to have some fun and test an app! I decided to try <strong>Wallos</strong>, a handy tool for managing subscriptions. Here’s how I got it up and running:</p>
<h4 id="heading-step-1-add-wallos-in-portainer"><strong>Step 1: Add Wallos in Portainer</strong></h4>
<p>In Portainer, create a new stack and use the following configuration:</p>
<pre><code class="lang-dockerfile">yamlCopier le codeservices:
  wallos:
    container_name: wallos
    image: bellamy/wallos:latest
    ports:
      - <span class="hljs-string">"8282:80"</span>
    volumes:
      - <span class="hljs-string">'./db:/var/www/html/db'</span>
      - <span class="hljs-string">'./logos:/var/www/html/images/uploads/logos'</span>
    restart: unless-stopped
</code></pre>
<h4 id="heading-step-2-connect-wallos-to-npm"><strong>Step 2: Connect Wallos to NPM</strong></h4>
<p>Once the stack is deployed, connect Wallos to the Nginx Proxy Manager network:</p>
<ol>
<li><p>Go to <strong>Portainer → Containers</strong> and locate the <code>wallos</code> container.</p>
</li>
<li><p>Scroll to the <strong>Connected Networks</strong> section and join the network used by Nginx Proxy Manager.</p>
</li>
</ol>
<h4 id="heading-step-3-create-a-proxy-host-for-wallos"><strong>Step 3: Create a Proxy Host for Wallos</strong></h4>
<p>In NPM, set up a new proxy host:</p>
<ul>
<li><p>Enter your <strong>domain name</strong> (e.g., <a target="_blank" href="http://wallos.localdomain.com"><code>wallos.localdomain.com</code></a>).</p>
</li>
<li><p>Set the <strong>local IP</strong> and <strong>port</strong> (<code>8282</code>) for the Wallos container.</p>
</li>
<li><p>Select your <strong>SSL certificate</strong>, enable <strong>Force SSL</strong>, and save.</p>
</li>
</ul>
<h4 id="heading-step-4-test-wallos"><strong>Step 4: Test Wallos</strong></h4>
<p>Now, open your browser and visit the domain you set up. You should see Wallos ready to manage your subscriptions securely and efficiently! 🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733598089569/f0197804-bce3-4435-9ad6-0ad62bd710e2.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733598099727/c74db07f-ad45-4c02-a73d-108f7ccf2303.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>This journey has been an incredible learning experience. From fixing tricky DNS rebinding issues to configuring SSL for local domains, every step was a mix of challenges and triumphs. Each error pushed me to explore new concepts, and the solutions taught me skills I never thought I’d need.</p>
<p>But this is just the beginning! There’s so much more to discover in the world of homelabs, and I can’t wait to see where this adventure takes me next. 🚀</p>
]]></content:encoded></item><item><title><![CDATA[My Homelab Journey: Step-by-Step Guide with Raspberry Pi - Part 1]]></title><description><![CDATA[A few months ago, I got into DIY electronics, 3D modeling, and 3D printing, and one thing led to another—I discovered homelabs! It’s a whole world of tinkering with servers, networking, and running self-hosted applications.
I decided to take baby ste...]]></description><link>https://blog.yaffalab.com/my-homelab-journey-step-by-step-guide-with-raspberry-pi-part-1</link><guid isPermaLink="true">https://blog.yaffalab.com/my-homelab-journey-step-by-step-guide-with-raspberry-pi-part-1</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[Docker]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 09 Dec 2024 08:00:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733692877438/3bc52f47-47a9-4bea-bcc9-7dcb54336f48.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few months ago, I got into DIY electronics, 3D modeling, and 3D printing, and one thing led to another—I discovered homelabs! It’s a whole world of tinkering with servers, networking, and running self-hosted applications.</p>
<p>I decided to take baby steps into this world with my <strong>Raspberry Pi 4 (8GB)</strong>. At first, it was just for testing out things like <strong>Docker</strong>, <strong>Portainer</strong>, and <strong>AdGuard</strong> (to block ads and solve a DNS issue I’ll explain later). I also set up <strong>Nginx Proxy Manager</strong> for managing SSL and reverse proxies. I won’t lie—the learning curve was tough. But I ended up learning so much about networking, system configs, and other cool stuff that I didn’t expect to get into.</p>
<p>Now, I’m upgrading my setup with a larger microSD card and thought, why not document the process and share it? Hopefully, this helps someone else get started too!</p>
<h3 id="heading-step-1-getting-started-with-raspberry-pi">Step 1: Getting Started with Raspberry Pi</h3>
<p>First things first—you need a Raspberry Pi. Whether it’s a Pi 3, Pi 4, Pi 5, or even a Pi Zero 2W, any of them will work depending on what you’re planning to do. I went with the <strong>Raspberry Pi 4 (8GB)</strong> for its power and versatility.</p>
<p>For the operating system, I used <strong>Raspberry Pi Imager</strong>, which makes the setup super easy. It even lets you configure things like the root user, Wi-Fi, and hostname right out of the box.</p>
<p>Here’s what I did:</p>
<ol>
<li><p><strong>Download Raspberry Pi Imager</strong><br /> You can grab it from <a target="_blank" href="https://www.raspberrypi.com/software/">Raspberry Pi Software</a>.</p>
</li>
<li><p><strong>Set Up the OS</strong></p>
<ul>
<li><p>Choose your Raspberry Pi model.</p>
</li>
<li><p>Select the operating system—I went with <strong>Raspberry Pi OS Lite (64-bit)</strong> because I didn’t need the desktop version.</p>
</li>
<li><p>Insert your microSD card and select it as the storage.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733595331425/13de04e2-f92d-423f-812c-635600476f7c.png" alt /></p>
</li>
</ul>
</li>
<li><p><strong>Configure Advanced Options</strong><br /> Before flashing, you can click the gear icon (Advanced Options) to set:</p>
<ul>
<li><p><strong>Hostname</strong>: I named mine <code>pifour.local</code> for easier access on the network.</p>
</li>
<li><p><strong>User credentials</strong>: Set your username and password.</p>
</li>
<li><p><strong>Wi-Fi credentials</strong>: I added these for initial setup, but later switched to Ethernet for better performance.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733595355293/eff0b1ec-238e-4e68-b4a7-c2050f8a1f57.png" alt /></p>
</li>
</ul>
</li>
<li><p><strong>Flash the Card</strong><br /> Once everything is configured, click “Write” and let it do its thing.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733595409554/3e955ac4-2fea-4487-9b1b-5a3cc8bdbc1b.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>When the process is done, pop the microSD card into your Pi, connect the power, and you’re ready to roll!</p>
<h3 id="heading-step-2-connecting-to-the-raspberry-pi">Step 2: Connecting to the Raspberry Pi</h3>
<p>Once your Raspberry Pi is powered up and running, it’s time to connect to it. I used <strong>SSH</strong> from my laptop to access it remotely. If you’re unsure of the IP address, you can check your router’s admin page or connect the Pi to a monitor and use the <code>hostname -I</code> command to find it.</p>
<p>I used <strong>PuTTY</strong> on Windows, but you can also use the terminal on macOS or Linux.</p>
<p>Here’s how I connected:</p>
<ol>
<li><p><strong>Launch PuTTY</strong> (or your terminal).</p>
<ol>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733595481747/7e03dfda-2667-4c6b-9a27-5a690b37527f.png" alt /></li>
</ol>
</li>
<li><p>Enter the Pi’s IP address as the Host Name and click <strong>Open</strong>.</p>
</li>
<li><p>Log in with the username and password you set up during the Raspberry Pi Imager configuration.</p>
</li>
</ol>
<p>Once logged in, the first thing I did was update the system. Keeping your Pi updated is crucial for security and performance:</p>
<p><code>sudo apt update &amp;&amp; sudo apt upgrade -y</code></p>
<h3 id="heading-step-3-setting-a-static-ip-address">Step 3: Setting a Static IP Address</h3>
<p>One of the first tweaks I made was setting a static IP address for the Pi. This prevents its IP from changing every time the router restarts, which is super helpful when you’re running services like Docker or AdGuard.</p>
<p>You can do this through your router’s DHCP settings (easier) or directly on the Pi. I went with the latter using <code>nmcli</code>.</p>
<h4 id="heading-steps-to-set-a-static-ip">Steps to Set a Static IP:</h4>
<ol>
<li><p><strong>Find the Network Interface Name</strong>:<br /> Run this command to see the connection name (usually <code>Wired connection 1</code> for Ethernet):</p>
<p> <code>sudo nmcli -p connection show</code></p>
</li>
<li><p><strong>Assign a Static IP</strong>:<br /> Replace <code>192.168.1.6</code> with your desired IP, and <code>192.168.1.254</code> with your gateway/DNS address:</p>
<p> <code>sudo nmcli c mod "Wired connection 1" ipv4.addresses 192.168.1.6/24 ipv4.method manual sudo nmcli c mod "Wired connection 1" ipv4.gateway 192.168.1.254 sudo nmcli c mod "Wired connection 1" ipv4.dns 192.168.1.254</code></p>
</li>
<li><p><strong>Restart the Connection</strong>:</p>
<p> <code>sudo nmcli c down "Wired connection 1" &amp;&amp; sudo nmcli c up "Wired connection 1"</code></p>
</li>
</ol>
<p>Now your Raspberry Pi will always have the same IP address, making it much easier to access and manage!</p>
<h3 id="heading-step-4-securing-the-raspberry-pi-with-ssh-keys">Step 4: Securing the Raspberry Pi with SSH Keys</h3>
<p>I wanted to make my Raspberry Pi more secure and simplify the login process, so I set up <strong>SSH key-based authentication</strong>. This way, I could log in without entering my password, and it’s much harder for someone else to gain unauthorized access.</p>
<p>If you're new to SSH keys and want a detailed guide, I recommend this excellent tutorial from <a target="_blank" href="https://pimylifeup.com/raspberry-pi-ssh-keys">Pi My Life Up</a>. It breaks down the process step-by-step and is perfect for beginners.</p>
<p>In case you prefer a quick overview, here’s what I did:</p>
<h4 id="heading-steps-to-set-up-ssh-keys">Steps to Set Up SSH Keys:</h4>
<ol>
<li><p><strong>Generate an SSH Key Pair on Your Laptop</strong>:<br /> On Linux or macOS, run the following command in your terminal:</p>
<p> <code>ssh-keygen -t rsa -b 4096</code></p>
<p> This generates a public and private key pair. You’ll find them in <code>~/.ssh/id_rsa</code> (private) and <code>~/.ssh/id_</code><a target="_blank" href="http://rsa.pub"><code>rsa.pub</code></a> (public).</p>
<p> If you’re on Windows, you can use <strong>PuTTYgen</strong> to create the keys.</p>
</li>
<li><p><strong>Set Up the SSH Directory on the Pi</strong>:<br /> After connecting to your Pi via SSH, create the <code>.ssh</code> directory and set the right permissions:</p>
<p> <code>mkdir -p ~/.ssh chmod 700 ~/.ssh</code></p>
</li>
<li><p><strong>Copy the Public Key to the Pi</strong>:<br /> From your laptop, run:</p>
<p> <code>ssh-copy-id pi@192.168.1.6</code></p>
<p> Replace <code>pi</code> with your username and <code>192.168.1.6</code> with your Pi’s IP address.</p>
<p> If <code>ssh-copy-id</code> isn’t available, you can manually copy the key:</p>
<p> <code>cat ~/.ssh/id_</code><a target="_blank" href="http://rsa.pub"><code>rsa.pub</code></a> <code>| ssh pi@192.168.1.6 "cat &gt;&gt; ~/.ssh/authorized_keys" chmod 600 ~/.ssh/authorized_keys</code></p>
</li>
<li><p><strong>Disable Password Login</strong>:<br /> To improve security, I disabled password-based SSH logins:</p>
<p> <code>sudo nano /etc/ssh/sshd_config</code></p>
<p> Find and change the line:</p>
<p> <code>#PasswordAuthentication yes</code></p>
<p> To:</p>
<p> <code>PasswordAuthentication no</code></p>
<p> Save the file (<code>Ctrl + O</code>, then <code>Ctrl + X</code>) and restart SSH:</p>
<p> <code>sudo systemctl restart ssh</code></p>
</li>
</ol>
<p>Now I could log in securely without a password, making my Raspberry Pi much safer and easier to access.</p>
<h3 id="heading-step-5-setting-up-a-firewall-with-ufw">Step 5: Setting Up a Firewall with UFW</h3>
<p>To add an extra layer of security, I installed <strong>UFW</strong> (Uncomplicated Firewall). It’s a simple tool to manage firewall rules and block unwanted access.</p>
<h4 id="heading-installing-and-configuring-ufw">Installing and Configuring UFW:</h4>
<ol>
<li><p><strong>Install UFW</strong>:</p>
<p> <code>sudo apt install ufw -y</code></p>
</li>
<li><p><strong>Allow SSH Connections</strong>:<br /> Before enabling the firewall, make sure SSH is allowed so you don’t lock yourself out:</p>
<p> <code>sudo ufw allow 22</code></p>
</li>
<li><p><strong>Allow Additional Ports (Optional)</strong>:<br /> If you plan to use HTTP or HTTPS for web services, allow those ports as well:</p>
<p> <code>sudo ufw allow 80 sudo ufw allow 443</code></p>
</li>
<li><p><strong>Enable Rate Limiting for SSH</strong>:<br /> To prevent brute force attacks, limit SSH connection attempts:</p>
<p> <code>sudo ufw limit 22</code></p>
</li>
<li><p><strong>Enable the Firewall</strong>:</p>
<p> <code>sudo ufw enable</code></p>
</li>
<li><p><strong>Check the Firewall Status</strong>:</p>
<p> <code>sudo ufw status verbose</code></p>
</li>
</ol>
<p>That’s it! My Raspberry Pi was now more secure, with SSH keys and a firewall protecting it.</p>
<h3 id="heading-step-6-installing-docker-on-the-raspberry-pi">Step 6: Installing Docker on the Raspberry Pi</h3>
<p>With my Raspberry Pi secured and ready, I moved on to installing <strong>Docker</strong>—a must-have tool for running and managing containerized applications. Docker simplifies deploying apps by isolating them in containers, and it’s perfect for a homelab setup.</p>
<h4 id="heading-step-1-update-the-system">Step 1: Update the System</h4>
<p>First, I made sure my Raspberry Pi was up-to-date:</p>
<p><code>sudo apt update &amp;&amp; sudo apt upgrade -y</code></p>
<h4 id="heading-step-2-install-docker">Step 2: Install Docker</h4>
<p>Docker provides an official installation script that handles everything for you. I used the following command to install it:</p>
<p><code>curl -fsSL</code> <a target="_blank" href="https://get.docker.com"><code>https://get.docker.com</code></a> <code>| sudo sh</code></p>
<p>This script downloads and installs Docker, making it a straightforward process.</p>
<h4 id="heading-step-3-verify-the-installation">Step 3: Verify the Installation</h4>
<p>Once the installation was complete, I checked whether Docker was running:</p>
<p><code>sudo systemctl status docker</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733596092216/790fbf0b-47c6-42b1-8387-e5b6b89926ed.png" alt class="image--center mx-auto" /></p>
<p>The output confirmed that Docker was active and running.</p>
<h4 id="heading-step-4-add-my-user-to-the-docker-group">Step 4: Add My User to the Docker Group</h4>
<p>To avoid typing <code>sudo</code> for every Docker command, I added my user to the <code>docker</code> group:</p>
<p><code>sudo usermod -aG docker pi</code></p>
<p>Replace <code>pi</code> with your username if it's different.</p>
<p>For the changes to take effect, I logged out and back in. I then confirmed my user was part of the <code>docker</code> group:</p>
<p><code>groups</code></p>
<h4 id="heading-step-5-test-docker">Step 5: Test Docker</h4>
<p>To ensure Docker was set up correctly, I ran:</p>
<p><code>docker version</code></p>
<p>The command displayed the installed Docker version and confirmed everything was working as expected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733596111221/252b1247-b6ee-4f91-abe7-346252c60b42.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-step-7-installing-docker-compose">Step 7 : Installing Docker Compose</h3>
<p>While Docker is great for managing individual containers, <strong>Docker Compose</strong> simplifies managing multi-container applications, which is essential for more complex setups like running Portainer.</p>
<h4 id="heading-step-1-install-docker-compose">Step 1: Install Docker Compose</h4>
<p>I installed Docker Compose with this simple command:</p>
<p><code>sudo apt install docker-compose -y</code></p>
<h4 id="heading-step-2-verify-docker-compose">Step 2: Verify Docker Compose</h4>
<p>To confirm it was installed, I checked the version:</p>
<p><code>docker-compose --version</code></p>
<h3 id="heading-wrapping-it-up">Wrapping It Up</h3>
<p>Congrats on getting your Raspberry Pi up and running! You’ve laid the foundation for a powerful homelab setup. In the next article, we’ll dive into installing Portainer, setting up AdGuard, configuring Nginx Proxy Manager, and running other exciting self-hosted apps. Stay tuned—your homelab journey is just getting started!</p>
]]></content:encoded></item><item><title><![CDATA[Efficiently Bypassing CORS in Next.js 14: A Practical Guide]]></title><description><![CDATA[As a frontend developer, you've likely encountered CORS (Cross-Origin Resource Sharing) issues when working with external APIs. This guide will walk you through a straightforward solution to bypass CORS in Next.js 14, using a real-world example of fe...]]></description><link>https://blog.yaffalab.com/efficiently-bypassing-cors-in-nextjs-14-a-practical-guide</link><guid isPermaLink="true">https://blog.yaffalab.com/efficiently-bypassing-cors-in-nextjs-14-a-practical-guide</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[yaffalab]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CORS]]></category><category><![CDATA[rss-parser]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Wed, 11 Sep 2024 14:54:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726065239957/2f717c3d-f3dc-4067-b9ce-df2b7e2199fa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a frontend developer, you've likely encountered CORS (Cross-Origin Resource Sharing) issues when working with external APIs. This guide will walk you through a straightforward solution to bypass CORS in Next.js 14, using a real-world example of fetching an RSS feed.</p>
<h3 id="heading-understanding-cors">Understanding CORS</h3>
<p>CORS is a security mechanism that restricts web pages from making requests to a different domain than the one serving the web page. While crucial for web security, it can become a hurdle during development, especially when working with third-party APIs or services.</p>
<h3 id="heading-the-challenge">The Challenge</h3>
<p>Recently, while developing a portfolio with Next.js 14, I needed to fetch and display an RSS feed from a blog. However, I faced a CORS error because I was working solely on the frontend and didn't have control over the server hosting the RSS feed.</p>
<h3 id="heading-a-simple-solution-in-nextjs-14">A Simple Solution in Next.js 14</h3>
<p>Next.js 14 provides an elegant solution by allowing us to create an internal API route that acts as a proxy. Here's how to implement it:</p>
<ol>
<li><strong>Create an API Route</strong></li>
</ol>
<p>First, create a file named <code>route.ts</code> in the <code>app/api/rss</code> directory:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> Parser <span class="hljs-keyword">from</span> <span class="hljs-string">"rss-parser"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">request: Request</span>) </span>{
  <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> Parser();
  <span class="hljs-keyword">const</span> feed = <span class="hljs-keyword">await</span> parser.parseURL(<span class="hljs-string">'https://blog.example.com/rss.xml'</span>);
  <span class="hljs-keyword">const</span> items = feed.items.slice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>).map(<span class="hljs-function">(<span class="hljs-params">item: <span class="hljs-built_in">any</span></span>) =&gt;</span> ({
    title: item.title || <span class="hljs-string">""</span>,
    link: item.link || <span class="hljs-string">""</span>,
    source: <span class="hljs-string">"Example Blog"</span>,
    contentSnippet: item.contentSnippet || <span class="hljs-string">""</span>,
    content: item.content || <span class="hljs-string">""</span>,
    image: feed.image?.url,
    publishedAt: item.pubDate
  }));

  <span class="hljs-keyword">return</span> NextResponse.json(items);
}
</code></pre>
<p>This API route fetches the RSS feed, parses it, and returns the first three items as JSON. By doing this server-side, we avoid CORS issues entirely.</p>
<ol start="2">
<li><strong>Implement the Frontend Component</strong></li>
</ol>
<p>Now, let's create a React component to display the fetched articles using the <code>useSWR</code> hook for data fetching and caching:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> useSWR <span class="hljs-keyword">from</span> <span class="hljs-string">'swr'</span>;
<span class="hljs-keyword">import</span> PostItem <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/post-item"</span>;

<span class="hljs-keyword">const</span> fetcher = <span class="hljs-function">(<span class="hljs-params">url: <span class="hljs-built_in">string</span></span>) =&gt;</span> fetch(url).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.json());

<span class="hljs-keyword">interface</span> Article {
  title: <span class="hljs-built_in">string</span>;
  link: <span class="hljs-built_in">string</span>;
  source: <span class="hljs-built_in">string</span>;
  contentSnippet?: <span class="hljs-built_in">string</span>;
  content?: <span class="hljs-built_in">string</span>;
  image?: <span class="hljs-built_in">string</span>;
  publishedAt?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Articles</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { data, error } = useSWR(<span class="hljs-string">"/api/rss"</span>, fetcher, {
    revalidateOnFocus: <span class="hljs-literal">false</span>,
    revalidateOnReconnect: <span class="hljs-literal">false</span>
  });

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> &lt;div&gt;Failed to load articles&lt;/div&gt;;

  <span class="hljs-keyword">return</span> (
    &lt;section&gt;
      &lt;h2 className=<span class="hljs-string">"text-xl font-semibold mb-4"</span>&gt;Latest Articles&lt;/h2&gt;
      &lt;div className=<span class="hljs-string">"space-y-4"</span>&gt;
        {data ? (
          data.map(<span class="hljs-function">(<span class="hljs-params">item: Article, index: <span class="hljs-built_in">number</span></span>) =&gt;</span> (
            &lt;PostItem key={index} {...item} /&gt;
          ))
        ) : (
          &lt;p className=<span class="hljs-string">"text-sm text-gray-600"</span>&gt;
            Loading articles...
          &lt;/p&gt;
        )}
      &lt;/div&gt;
    &lt;/section&gt;
  );
}
</code></pre>
<p>Explanation of <code>useSWR</code> and the Fetch Function</p>
<p>The <code>useSWR</code> hook is a powerful tool for data fetching in React applications:</p>
<ol>
<li><p>SWR (Stale-While-Revalidate): SWR is a React Hooks library for data fetching, created by the Vercel team. It's named after the HTTP Cache-Control extension "stale-while-revalidate". You can find more information <a target="_blank" href="https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration">Here</a></p>
</li>
<li><p>The <code>fetcher</code> function is a wrapper around the native <code>fetch</code> API, making a GET request and parsing the response as JSON.</p>
</li>
<li><p><code>useSWR</code> takes the API route ("/api/rss") as its key and the <code>fetcher</code> function as its second argument.</p>
</li>
<li><p>It returns an object with <code>data</code> and <code>error</code> properties, handling loading and error states automatically.</p>
</li>
</ol>
<h3 id="heading-benefits-of-this-approach"><strong>Benefits of this Approach</strong></h3>
<ol>
<li><p>CORS Bypass: The Next.js API route acts as a proxy, fetching data server-side where CORS restrictions don't apply.</p>
</li>
<li><p>Efficient Data Fetching: SWR provides automatic revalidation, caching, and error handling.</p>
</li>
<li><p>Clean Implementation: This solution requires minimal setup and integrates smoothly with Next.js projects.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This method offers a practical solution for bypassing CORS issues in Next.js 14 applications. It's particularly useful for scenarios where you need to fetch data from external sources without modifying the backend.</p>
<p>While effective for many use cases, always consider the implications of proxying requests through your server, especially for high-traffic applications. In some scenarios, implementing proper CORS headers on your API might be more appropriate.</p>
<p>By leveraging Next.js API routes and SWR, you can create robust, CORS-free data fetching solutions in your Next.js applications.</p>
]]></content:encoded></item><item><title><![CDATA[From Moroccan Coder to Global .NET Guru: The Inspiring Journey of Khalid]]></title><description><![CDATA[In the dynamic world of software development, few stories are as inspiring as that of Khalid . A .NET developer turned global educator, Khalid has revolutionized Arabic-language programming education with his YouTube channel (Khalid's YouTube Channel...]]></description><link>https://blog.yaffalab.com/from-moroccan-coder-to-global-net-guru-the-inspiring-journey-of-khalid</link><guid isPermaLink="true">https://blog.yaffalab.com/from-moroccan-coder-to-global-net-guru-the-inspiring-journey-of-khalid</guid><category><![CDATA[interview]]></category><category><![CDATA[.NET]]></category><category><![CDATA[books]]></category><category><![CDATA[engineering]]></category><category><![CDATA[yaffalab]]></category><category><![CDATA[ASP.NET]]></category><category><![CDATA[Arabic ]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Microsoft]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 02 Sep 2024 08:28:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725005185531/4305c2e7-e21d-4af2-ab43-30cc454d6c5a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the dynamic world of software development, few stories are as inspiring as that of Khalid . A .NET developer turned global educator, Khalid has revolutionized Arabic-language programming education with his YouTube channel (<a target="_blank" href="https://www.youtube.com/@EssaadaniTV/videos">Khalid's YouTube Channel</a>), boasting over <strong>844 tutorials</strong>. But his impact doesn't stop there – he's also a prolific author with <strong>10+ books</strong>, a <strong>Microsoft MVP</strong>, and a developer who's taken his skills from freelancing in Morocco to a full-time role in Belgium. For those interested in connecting with Khalid, you can find him on Facebook at <a target="_blank" href="https://www.facebook.com/EssaadaniPage">Khalid's Facebook</a>.</p>
<p>This interview explores Khalid's remarkable journey, offering insights into the challenges of content creation, the evolution of .NET, and the personal growth that comes with pursuing one's passion on a global stage.</p>
<h2 id="heading-career-journey">Career Journey:</h2>
<ul>
<li><p><strong>Q: What was the pivotal moment that led you to create Arabic-language .NET tutorials on YouTube?</strong></p>
<ul>
<li><strong><em>Khalid</em></strong>: My journey into creating YouTube tutorials began after I published my first book on C# in 2010. Initially, I aimed to diversify the educational resources available to the Arabic-speaking audience, understanding that some learners prefer reading while others prefer visual learning through videos. However, the true turning point came when I was scammed by an educational institution that didn't pay me for a series of courses I had recorded. Instead of letting the content go to waste, I decided to share it on my YouTube channel, which opened up a new path for me in content creation.</li>
</ul>
</li>
<li><p><strong>Q: How has being a non-native English speaker influenced your approach to teaching global programming concepts?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> Being a non-native English speaker hasn't been a barrier for me. My strength lies in my command of Arabic and my ability to articulate complex programming concepts in the language. While English is undeniably important in the programming world, I've successfully adapted and localized content to make it accessible to Arabic speakers. My approach involves simplifying, localizing, and gradually explaining concepts, which has resonated well with my audience.</li>
</ul>
</li>
<li><p><strong>Q: Can you share a specific challenge you faced when writing your books that was different from creating video content?</strong></p>
<ul>
<li><strong><em>Khalid</em></strong>: Writing books presents a unique set of challenges, particularly when it comes to explaining practical concepts. In videos, it's straightforward to demonstrate ideas by recording the screen, but in a book, you have to be creative with words to paint a clear picture in the reader's mind. This task of translating visual concepts into written form has been one of the most difficult yet rewarding challenges for me.</li>
</ul>
</li>
<li><p><strong>Q: What was the most unexpected lesson you learned transitioning from freelancing in Morocco to a full-time role in Belgium?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> Transitioning to a full-time role in Belgium taught me the importance of preparation. I knew that adapting to a new environment would be challenging, so I invested time in learning new tools and techniques that would ease the transition. This foresight helped me adjust smoothly to the new work culture.</li>
</ul>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725265585599/0a5f3bb1-fe76-45f7-8fae-e9bcf145495b.jpeg" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Q: How has the Microsoft MVP award changed your perspective on community involvement in tech?</strong></p>
<ul>
<li><strong><em>Khalid</em>:</strong> The MVP Award significantly shifted my approach to creating technical content. After receiving the award, I became more focused on delivering high-quality, rare, and valuable content. The responsibility that comes with the title makes you more cautious in answering questions because people look to you as an expert, and any mistake can have serious implications for them.</li>
</ul>
</li>
</ul>
<h2 id="heading-technical-expertise">Technical Expertise:</h2>
<ul>
<li><p><strong>Q: What emerging .NET technology are you most excited about, and why?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> Recently, I have been focused on Xamarin.Forms, but with Microsoft's decision to phase it out, I've redirected my attention to <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core. My choice is pragmatic—<a target="_blank" href="http://ASP.NET">ASP.NET</a> Core aligns with our projects, and I'm not entirely convinced by .<strong>NET MAUI</strong>. My interest here is guided by both my personal preferences and the needs of the company I work for.</li>
</ul>
</li>
<li><p><strong>Q: How do you stay current with the rapid changes in .NET development while managing your content creation schedule?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> In recent years, my content production has slowed due to time constraints and other commitments. However, I’ve maintained my habit of exploring new developments in coding and keeping up with emerging techniques and tools, ensuring that I stay informed even when I'm not actively creating content.</li>
</ul>
</li>
<li><p><strong>Q: Can you describe a complex .NET concept you've found particularly challenging to teach, and how you overcame that challenge?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> One of the most challenging topics I've tackled is the SOLID principles, especially when writing about them in Arabic. The complexity and the number of concepts involved made it difficult to ensure coherence. To overcome this, I focused on developing a single example that clearly illustrated each principle in detail. By making the writing process enjoyable, I was able to overcome these challenges and produce a successful book.</li>
</ul>
</li>
</ul>
<h2 id="heading-content-creation">Content Creation:</h2>
<ul>
<li><p><strong>Q: How do you decide what topics to cover in your videos and books?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> The topics I cover are largely driven by the requests I receive from learners. When a particular topic is frequently requested, I make it a priority to explain it, either through video or in writing.</li>
</ul>
</li>
<li><p><strong>Q: What strategies have you found most effective for growing your YouTube</strong> <strong>audience?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> The key to growing a YouTube audience in technical fields is mastery of the subject matter. People can quickly recognize expertise, and when they see that you are specialized and proficient, they spread the word. This word-of-mouth promotion helps your audience grow organically, often without requiring any direct effort on your part.</li>
</ul>
</li>
<li><p><strong>Q: How do you balance creating content for beginners versus more advanced</strong> <strong>developers?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> I strive to maintain a balance between content for beginners and more advanced developers. My mission is to help as many people as possible, rather than focusing solely on expanding my audience. This approach ensures that I cater to a wide range of learners, from novices to professionals.</li>
</ul>
</li>
</ul>
<h2 id="heading-personal-growth">Personal Growth:</h2>
<ul>
<li><p><strong>Q: How has your journey as a content creator changed your approach to your own learning and development?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> Teaching has been one of the most effective ways to deepen my own understanding of programming concepts. When I'm asked to explain something I don't fully understand, it motivates me to learn it thoroughly. Creating technical content has therefore been instrumental in my personal growth as a developer.</li>
</ul>
</li>
<li><p><strong>Q: What's been the most rewarding feedback you've received from a viewer or reader?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> The most rewarding feedback comes from those who have secured jobs thanks to my lessons. When I receive messages filled with gratitude from people whose lives have been positively impacted by my content, I feel a deep sense of pride and accomplishment.</li>
</ul>
</li>
<li><p><strong>Q: If you could go back in time and give advice to yourself when you were just starting out, what would you say?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> If I could go back, I would advise myself to start with a strong foundation in data structures, algorithms, and mathematics. While math isn't a strict requirement for programming, it opens up many avenues and opportunities for a programmer.</li>
</ul>
</li>
</ul>
<h2 id="heading-future-aspirations">Future Aspirations:</h2>
<ul>
<li><p><strong>Q: Are there any new content formats or platforms you're considering exploring?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> Absolutely. I'm currently working on a significant educational project that is still in its early stages. I hope this project will bring about substantial changes in coding education across the Arab world.</li>
</ul>
</li>
<li><p><strong>Q: How do you envision the role of multilingual content creators in shaping the future of global tech education?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> I believe content creators will have an even greater impact if they contribute to the development of technology itself, rather than just creating content about it. When we move from explaining to innovating, our influence on the tech industry will be far more profound.</li>
</ul>
</li>
<li><p><strong>Q: What's one ambitious goal you have for your impact on the .NET community in the next five years?</strong></p>
<ul>
<li><strong><em>Khalid:</em></strong> My ambition is to be more involved in the development of technology itself, beyond just content creation. I believe this will allow me to have a more significant and lasting impact on the .NET community.</li>
</ul>
</li>
</ul>
<p>Khalid's journey from a local developer to a global .NET influencer is a testament to the power of passion, perseverance, and knowledge sharing. His story not only inspires aspiring developers but also highlights the growing importance of diverse voices in shaping the future of technology education worldwide.</p>
]]></content:encoded></item><item><title><![CDATA[Broadening Horizons: Why Software Engineers Should Explore Beyond Their Code Editors]]></title><description><![CDATA[As a software engineer, I spend most of my days buried in lines of code, debugging errors, and architecting systems. It's easy to get caught up in the endless cycle of learning new programming languages, frameworks, and tools. But recently, I stumble...]]></description><link>https://blog.yaffalab.com/broadening-horizons-why-software-engineers-should-explore-beyond-their-code-editors</link><guid isPermaLink="true">https://blog.yaffalab.com/broadening-horizons-why-software-engineers-should-explore-beyond-their-code-editors</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Life lessons]]></category><category><![CDATA[Mindset]]></category><category><![CDATA[yaffalab]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Thu, 22 Aug 2024 08:07:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724313669514/ee194575-8b63-4024-83d8-c7ad253a7192.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a software engineer, I spend most of my days buried in lines of code, debugging errors, and architecting systems. It's easy to get caught up in the endless cycle of learning new programming languages, frameworks, and tools. But recently, I stumbled upon something that completely changed my perspective on learning and problem-solving: 3D modeling with Fusion 360.</p>
<h2 id="heading-stepping-out-of-the-comfort-zone">Stepping Out of the Comfort Zone</h2>
<p>At first, the idea of learning 3D modeling seemed completely unrelated to my day job. I mean, what does creating virtual objects have to do with writing efficient algorithms? But curiosity got the better of me, and I dove in headfirst.</p>
<p>The learning curve was steep. I fumbled through the interface, struggling with concepts like extrusion, chamfering, and Boolean operations. It was frustrating, but also exhilarating. For the first time in a while, I felt like a complete novice again.</p>
<h2 id="heading-a-new-way-of-thinking">A New Way of Thinking</h2>
<p>As I progressed, something interesting happened. I started to notice parallels between 3D modeling and software engineering. Both require breaking down complex problems into smaller, manageable parts. Both involve thinking about how different components interact with each other.</p>
<p>But 3D modeling also introduced me to new ways of thinking. I had to visualize objects from multiple angles, considering not just functionality but also aesthetics and physical constraints. This spatial reasoning was a muscle I rarely flexed in my coding work.</p>
<h2 id="heading-expanding-vocabulary-and-concepts">Expanding Vocabulary and Concepts</h2>
<p>Learning 3D modeling also exposed me to a whole new lexicon. Terms like "filleting," "lofting," and "parametric modeling" entered my vocabulary. While these words might not come up in a coding meeting, the concepts behind them – smoothing transitions, creating complex shapes from simple profiles, and building flexible, adjustable models – have surprising applications in software design.</p>
<h2 id="heading-cross-pollination-of-ideas">Cross-Pollination of Ideas</h2>
<p>The most unexpected benefit came when I returned to my regular coding tasks. I found myself approaching problems with a fresh perspective. The visualization skills I developed helped me better understand and design complex software architectures. The emphasis on user experience in 3D modeling made me more attuned to creating intuitive interfaces in my applications.</p>
<h2 id="heading-embracing-the-unknown">Embracing the Unknown</h2>
<p>So, to my fellow software engineers, I say this: don't be afraid to venture outside your comfort zone. Pick up a paintbrush, try your hand at woodworking, learn a musical instrument, or yes, give 3D modeling a shot. The skills and perspectives you gain might not be directly applicable to your day-to-day coding, but they'll enrich your problem-solving toolkit in ways you never expected.</p>
<p>Remember, some of the most innovative solutions come from connecting seemingly unrelated ideas. By broadening your horizons, you're not just becoming a more well-rounded individual – you're potentially unlocking new ways to approach the challenges in your primary field.</p>
<p>So go ahead, close your code editor for a bit, and explore something new. Your future self (and your code) will thank you for it.</p>
]]></content:encoded></item><item><title><![CDATA[Advanced TypeScript Optimization: Balancing Performance and Maintainability]]></title><description><![CDATA[In our previous discussion on code optimization in TypeScript, we explored various techniques to improve performance while maintaining readability. Building on those foundations, this article delves into more advanced optimization patterns and their ...]]></description><link>https://blog.yaffalab.com/advanced-typescript-optimization-balancing-performance-and-maintainability</link><guid isPermaLink="true">https://blog.yaffalab.com/advanced-typescript-optimization-balancing-performance-and-maintainability</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[clean code]]></category><category><![CDATA[optimization]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 15 Jul 2024 07:40:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/BfrQnKBulYQ/upload/01db00d469f682076c474d57a88a2252.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In our previous discussion on code optimization in TypeScript, we explored various techniques to improve performance while maintaining readability. Building on those foundations, this article delves into more advanced optimization patterns and their implications for code maintainability. We'll examine how to push the boundaries of TypeScript's capabilities to achieve peak performance without sacrificing the clarity and robustness of your codebase.</p>
<h2 id="heading-1-leveraging-conditional-types-for-performance">1. Leveraging Conditional Types for Performance</h2>
<p>Conditional types in TypeScript can be used not just for type safety, but also for performance optimization. By using conditional types, we can create more specific and efficient code paths:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> IsArray&lt;T&gt; = T <span class="hljs-keyword">extends</span> <span class="hljs-built_in">any</span>[] ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processData</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">data: T</span>): <span class="hljs-title">IsArray</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-title">extends</span> <span class="hljs-title">true</span> ? <span class="hljs-title">number</span>[] : <span class="hljs-title">number</span> </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(data)) {
        <span class="hljs-keyword">return</span> data.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item * <span class="hljs-number">2</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> (data <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>) * <span class="hljs-number">2</span>;
    }
}

<span class="hljs-keyword">const</span> result1 = processData([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]); <span class="hljs-comment">// Type: number[]</span>
<span class="hljs-keyword">const</span> result2 = processData(<span class="hljs-number">4</span>); <span class="hljs-comment">// Type: number</span>
</code></pre>
<p>This approach allows the compiler to optimize the code based on the input type, potentially leading to better performance. However, it's crucial to balance this with readability, as complex conditional types can be challenging to understand and maintain.</p>
<h2 id="heading-2-using-typescript-decorators-for-memoization">2. Using TypeScript Decorators for Memoization</h2>
<p>Decorators can be a powerful tool for adding memoization to methods without cluttering the main code:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Memoize</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">target: <span class="hljs-built_in">any</span>, propertyKey: <span class="hljs-built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>{
        <span class="hljs-keyword">const</span> originalMethod = descriptor.value;
        <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();

        descriptor.value = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) </span>{
            <span class="hljs-keyword">const</span> key = <span class="hljs-built_in">JSON</span>.stringify(args);
            <span class="hljs-keyword">if</span> (cache.has(key)) {
                <span class="hljs-keyword">return</span> cache.get(key);
            }
            <span class="hljs-keyword">const</span> result = originalMethod.apply(<span class="hljs-built_in">this</span>, args);
            cache.set(key, result);
            <span class="hljs-keyword">return</span> result;
        };

        <span class="hljs-keyword">return</span> descriptor;
    };
}

<span class="hljs-keyword">class</span> ExpensiveCalculations {
    <span class="hljs-meta">@Memoize</span>()
    fibonacci(n: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">number</span> {
        <span class="hljs-keyword">if</span> (n &lt;= <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> n;
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.fibonacci(n - <span class="hljs-number">1</span>) + <span class="hljs-built_in">this</span>.fibonacci(n - <span class="hljs-number">2</span>);
    }
}
</code></pre>
<p>This approach separates the memoization logic from the business logic, improving both performance and maintainability.</p>
<h2 id="heading-3-optimizing-object-creation-with-typescripts-utility-types">3. Optimizing Object Creation with TypeScript's Utility Types</h2>
<p>TypeScript's utility types can be leveraged to create optimized object structures:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> User = {
    id: <span class="hljs-built_in">number</span>;
    name: <span class="hljs-built_in">string</span>;
    email: <span class="hljs-built_in">string</span>;
    preferences: {
        theme: <span class="hljs-string">'light'</span> | <span class="hljs-string">'dark'</span>;
        notifications: <span class="hljs-built_in">boolean</span>;
    };
};

<span class="hljs-keyword">type</span> UserUpdateParams = Partial&lt;Omit&lt;User, <span class="hljs-string">'id'</span>&gt;&gt;;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateUser</span>(<span class="hljs-params">id: <span class="hljs-built_in">number</span>, params: UserUpdateParams</span>) </span>{
    <span class="hljs-comment">// Implementation here</span>
}

updateUser(<span class="hljs-number">1</span>, { name: <span class="hljs-string">'John Doe'</span>, preferences: { theme: <span class="hljs-string">'dark'</span> } });
</code></pre>
<p>This pattern allows for flexible and type-safe object updates while preventing unnecessary property assignments, potentially improving performance in large-scale applications.</p>
<h2 id="heading-4-using-typescript-generics-for-compile-time-optimizations">4. Using TypeScript Generics for Compile-Time Optimizations</h2>
<p>Generics can be used to create highly optimized functions that the TypeScript compiler can inline:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>&lt;<span class="hljs-title">T</span>, <span class="hljs-title">U</span>&gt;(<span class="hljs-params">tuple: [T, U]</span>): [<span class="hljs-title">U</span>, <span class="hljs-title">T</span>] </span>{
    <span class="hljs-keyword">return</span> [tuple[<span class="hljs-number">1</span>], tuple[<span class="hljs-number">0</span>]];
}

<span class="hljs-keyword">const</span> result = swap([<span class="hljs-number">1</span>, <span class="hljs-string">'two'</span>]); <span class="hljs-comment">// Type: [string, number]</span>
</code></pre>
<p>While this might seem simple, for complex operations, such compile-time optimizations can lead to significant performance improvements, especially when used extensively throughout a large codebase.</p>
<h2 id="heading-5-optimizing-asynchronous-operations-with-custom-promises">5. Optimizing Asynchronous Operations with Custom Promises</h2>
<p>Creating custom Promise implementations can lead to more efficient asynchronous operations:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> FastPromise&lt;T&gt; <span class="hljs-keyword">implements</span> PromiseLike&lt;T&gt; {
    <span class="hljs-keyword">private</span> result?: T;
    <span class="hljs-keyword">private</span> error?: <span class="hljs-built_in">any</span>;
    <span class="hljs-keyword">private</span> resolve?: <span class="hljs-function">(<span class="hljs-params">value: T | PromiseLike&lt;T&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    <span class="hljs-keyword">private</span> reject?: <span class="hljs-function">(<span class="hljs-params">reason?: <span class="hljs-built_in">any</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;

    <span class="hljs-keyword">constructor</span>(<span class="hljs-params">executor: (resolve: (value: T | PromiseLike&lt;T&gt;) =&gt; <span class="hljs-built_in">void</span>, reject: (reason?: <span class="hljs-built_in">any</span>) =&gt; <span class="hljs-built_in">void</span>) =&gt; <span class="hljs-built_in">void</span></span>) {
        <span class="hljs-keyword">try</span> {
            executor(
                <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
                    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.resolve) <span class="hljs-built_in">this</span>.resolve(value);
                    <span class="hljs-keyword">else</span> <span class="hljs-built_in">this</span>.result = value;
                },
                <span class="hljs-function"><span class="hljs-params">reason</span> =&gt;</span> {
                    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.reject) <span class="hljs-built_in">this</span>.reject(reason);
                    <span class="hljs-keyword">else</span> <span class="hljs-built_in">this</span>.error = reason;
                }
            );
        } <span class="hljs-keyword">catch</span> (e) {
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.reject) <span class="hljs-built_in">this</span>.reject(e);
            <span class="hljs-keyword">else</span> <span class="hljs-built_in">this</span>.error = e;
        }
    }

    then&lt;TResult1 = T, TResult2 = <span class="hljs-built_in">never</span>&gt;(
        onfulfilled?: (<span class="hljs-function">(<span class="hljs-params">value: T</span>) =&gt;</span> TResult1 | PromiseLike&lt;TResult1&gt;) | <span class="hljs-literal">undefined</span> | <span class="hljs-literal">null</span>,
        onrejected?: (<span class="hljs-function">(<span class="hljs-params">reason: <span class="hljs-built_in">any</span></span>) =&gt;</span> TResult2 | PromiseLike&lt;TResult2&gt;) | <span class="hljs-literal">undefined</span> | <span class="hljs-literal">null</span>
    ): PromiseLike&lt;TResult1 | TResult2&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> FastPromise(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> wrappedResolve = <span class="hljs-function">(<span class="hljs-params">value: T | PromiseLike&lt;T&gt;</span>) =&gt;</span> {
                <span class="hljs-keyword">try</span> {
                    resolve(onfulfilled ? onfulfilled(value <span class="hljs-keyword">as</span> T) : value <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>);
                } <span class="hljs-keyword">catch</span> (e) {
                    reject(e);
                }
            };

            <span class="hljs-keyword">const</span> wrappedReject = <span class="hljs-function">(<span class="hljs-params">reason: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
                <span class="hljs-keyword">if</span> (onrejected) {
                    <span class="hljs-keyword">try</span> {
                        resolve(onrejected(reason));
                    } <span class="hljs-keyword">catch</span> (e) {
                        reject(e);
                    }
                } <span class="hljs-keyword">else</span> {
                    reject(reason);
                }
            };

            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.result !== <span class="hljs-literal">undefined</span>) wrappedResolve(<span class="hljs-built_in">this</span>.result);
            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.error !== <span class="hljs-literal">undefined</span>) wrappedReject(<span class="hljs-built_in">this</span>.error);
            <span class="hljs-keyword">else</span> {
                <span class="hljs-built_in">this</span>.resolve = wrappedResolve;
                <span class="hljs-built_in">this</span>.reject = wrappedReject;
            }
        });
    }
}
</code></pre>
<p>This custom Promise implementation can be more efficient for certain use cases, particularly when dealing with a high volume of rapidly resolving promises. However, it's important to note that such optimizations should be used judiciously, as they can increase code complexity and potentially introduce bugs if not implemented correctly.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>While these advanced optimization techniques can significantly boost performance, they often come at the cost of increased complexity. It's crucial to apply them judiciously, always considering the trade-offs between performance gains and code maintainability.</p>
<p>Remember these key points when applying advanced optimizations:</p>
<ol>
<li><p><strong>Profile First</strong>: Always measure performance before and after optimization to ensure the complexity is justified.</p>
</li>
<li><p><strong>Document Thoroughly</strong>: Complex optimizations should be well-documented, explaining both the how and the why.</p>
</li>
<li><p><strong>Encapsulate Complexity</strong>: Where possible, encapsulate complex optimizations within well-named functions or classes to preserve the overall readability of your codebase.</p>
</li>
<li><p><strong>Consider the Team</strong>: Think about how these optimizations will affect your team's ability to understand and maintain the code.</p>
</li>
<li><p><strong>Stay Updated</strong>: TypeScript and JavaScript are evolving languages. Keep an eye out for new features that might offer simpler ways to achieve the same performance gains.</p>
</li>
</ol>
<p>By carefully balancing these advanced optimization techniques with code maintainability, you can create TypeScript applications that are both high-performing and sustainable in the long term.</p>
]]></content:encoded></item><item><title><![CDATA[Code Optimization Techniques: Balancing Readability and Performance in TypeScript]]></title><description><![CDATA[In the world of software development, writing code that is both efficient and easy to understand is a constant challenge. This is especially true when working with TypeScript, a statically typed superset of JavaScript that adds optional types to the ...]]></description><link>https://blog.yaffalab.com/code-optimization-techniques-balancing-readability-and-performance-in-typescript</link><guid isPermaLink="true">https://blog.yaffalab.com/code-optimization-techniques-balancing-readability-and-performance-in-typescript</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[clean code]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Wed, 10 Jul 2024 08:45:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720601087575/8f377dd0-8756-4adf-8159-0c839a3a8a5a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of software development, writing code that is both efficient and easy to understand is a constant challenge. This is especially true when working with TypeScript, a statically typed superset of JavaScript that adds optional types to the language. While TypeScript offers many benefits in terms of code quality and maintainability, it's important to strike a balance between writing readable code and optimizing for performance.</p>
<p>In this article, we'll explore various code optimization techniques for TypeScript, discussing how to improve performance without sacrificing readability. We'll cover a range of topics, from basic optimizations to more advanced techniques, always keeping in mind the importance of maintaining clear and understandable code.</p>
<h2 id="heading-understanding-the-balance">Understanding the Balance</h2>
<p>Before diving into specific techniques, it's crucial to understand the balance between readability and performance. Readable code is easier to maintain, debug, and extend, which can save time and reduce errors in the long run. On the other hand, performant code ensures that your application runs efficiently, providing a better user experience and potentially reducing resource usage.</p>
<p>The key is to find the sweet spot where your code is both readable and performant. In many cases, you can achieve both goals simultaneously, but sometimes you may need to make trade-offs. When you do, it's important to document your decisions and explain the reasoning behind any optimizations that might make the code less immediately understandable.</p>
<h2 id="heading-basic-optimization-techniques">Basic Optimization Techniques</h2>
<p>Let's start with some basic optimization techniques that can improve performance without significantly impacting readability.</p>
<h3 id="heading-1-use-appropriate-data-structures">1. Use Appropriate Data Structures</h3>
<p>Choosing the right data structure for your task can have a significant impact on performance. For example, using a Set instead of an Array for checking the existence of elements can be much faster for large collections:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Less efficient for large collections</span>
<span class="hljs-keyword">const</span> array: <span class="hljs-built_in">number</span>[] = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>];
<span class="hljs-keyword">const</span> exists = array.includes(<span class="hljs-number">3</span>); <span class="hljs-comment">// O(n) time complexity</span>

<span class="hljs-comment">// More efficient for large collections</span>
<span class="hljs-keyword">const</span> set: <span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">number</span>&gt; = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]);
<span class="hljs-keyword">const</span> exists = set.has(<span class="hljs-number">3</span>); <span class="hljs-comment">// O(1) time complexity</span>
</code></pre>
<h3 id="heading-2-avoid-unnecessary-computations">2. Avoid Unnecessary Computations</h3>
<p>Look for opportunities to avoid redundant calculations. For example, you can cache the length of an array if you're using it multiple times in a loop:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Less efficient</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
  <span class="hljs-comment">// Do something</span>
}

<span class="hljs-comment">// More efficient</span>
<span class="hljs-keyword">const</span> length = array.length;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; length; i++) {
  <span class="hljs-comment">// Do something</span>
}
</code></pre>
<h3 id="heading-3-use-typescripts-type-system">3. Use TypeScript's Type System</h3>
<p>Leverage TypeScript's type system to catch errors at compile-time and provide better editor support. This can lead to fewer runtime errors and more efficient code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Less safe and potentially less performant</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">a: <span class="hljs-built_in">any</span>, b: <span class="hljs-built_in">any</span></span>) </span>{
  <span class="hljs-keyword">return</span> a + b;
}

<span class="hljs-comment">// More safe and potentially more performant</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">a: <span class="hljs-built_in">number</span>, b: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> </span>{
  <span class="hljs-keyword">return</span> a + b;
}
</code></pre>
<h3 id="heading-4-use-lazy-evaluation">4. Use Lazy Evaluation</h3>
<p>Lazy evaluation can improve performance by delaying the evaluation of an expression until its value is needed. This can be particularly useful for expensive computations:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Eager evaluation</span>
<span class="hljs-keyword">const</span> expensiveValue = computeExpensiveValue();
<span class="hljs-keyword">if</span> (condition) {
  use(expensiveValue);
}

<span class="hljs-comment">// Lazy evaluation</span>
<span class="hljs-keyword">let</span> expensiveValue: <span class="hljs-built_in">number</span> | <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">if</span> (condition) {
  expensiveValue = computeExpensiveValue();
  use(expensiveValue);
}
</code></pre>
<h3 id="heading-5-optimize-loops">5. Optimize Loops</h3>
<p>When working with loops, there are several optimizations you can apply:</p>
<p>a. Use <code>for...of</code> for arrays and <code>for...in</code> for object properties:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Less efficient for arrays</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
  <span class="hljs-keyword">const</span> item = array[i];
  <span class="hljs-comment">// Do something with item</span>
}

<span class="hljs-comment">// More efficient for arrays</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> item <span class="hljs-keyword">of</span> array) {
  <span class="hljs-comment">// Do something with item</span>
}
</code></pre>
<p>b. Avoid modifying arrays while iterating over them. Instead, use <code>filter</code> or <code>reduce</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Less efficient and potentially error-prone</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
  <span class="hljs-keyword">if</span> (someCondition(array[i])) {
    array.splice(i, <span class="hljs-number">1</span>);
    i--;
  }
}

<span class="hljs-comment">// More efficient and safer</span>
array = array.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> !someCondition(item));
</code></pre>
<h3 id="heading-6-use-memoization-for-expensive-functions">6. Use Memoization for Expensive Functions</h3>
<p>Memoization is a technique where you cache the results of expensive function calls and return the cached result when the same inputs occur again:</p>
<pre><code class="lang-typescript">memoize&lt;T <span class="hljs-keyword">extends</span> (...args: <span class="hljs-built_in">any</span>[]) =&gt; <span class="hljs-built_in">any</span>&gt;(fn: T): T {
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
  <span class="hljs-keyword">return</span> (<span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> key = <span class="hljs-built_in">JSON</span>.stringify(args);
    <span class="hljs-keyword">if</span> (cache.has(key)) {
      <span class="hljs-keyword">return</span> cache.get(key);
    }
    <span class="hljs-keyword">const</span> result = fn(...args);
    cache.set(key, result);
    <span class="hljs-keyword">return</span> result;
  }) <span class="hljs-keyword">as</span> T;
}

<span class="hljs-keyword">const</span> expensiveFunction = memoize(<span class="hljs-function">(<span class="hljs-params">a: <span class="hljs-built_in">number</span>, b: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-comment">// Expensive computation here</span>
  <span class="hljs-keyword">return</span> a + b;
});
</code></pre>
<h3 id="heading-7-use-typescripts-advanced-types">7. Use TypeScript's Advanced Types</h3>
<p>TypeScript's advanced types can help you write more performant code by catching errors at compile-time and providing better type inference:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Using mapped types for efficient object transformation</span>
<span class="hljs-keyword">type</span> Getters&lt;T&gt; = {
  [K <span class="hljs-keyword">in</span> keyof T <span class="hljs-keyword">as</span> <span class="hljs-string">`get<span class="hljs-subst">${Capitalize&lt;<span class="hljs-built_in">string</span> &amp; K&gt;}</span>`</span>]: <span class="hljs-function">() =&gt;</span> T[K]
};

<span class="hljs-keyword">interface</span> Person {
  name: <span class="hljs-built_in">string</span>;
  age: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">type</span> PersonGetters = Getters&lt;Person&gt;;
<span class="hljs-comment">// Resulting type:</span>
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//   getName: () =&gt; string;</span>
<span class="hljs-comment">//   getAge: () =&gt; number;</span>
<span class="hljs-comment">// }</span>
</code></pre>
<h3 id="heading-8-optimize-memory-usage">8. Optimize Memory Usage</h3>
<p>Be mindful of memory usage, especially when working with large data structures:</p>
<p>a. Use <code>WeakMap</code> and <code>WeakSet</code> for objects that should be garbage collected when no longer referenced:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> <span class="hljs-built_in">WeakMap</span>();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getExpensiveData</span>(<span class="hljs-params">obj: <span class="hljs-built_in">object</span></span>) </span>{
  <span class="hljs-keyword">if</span> (cache.has(obj)) {
    <span class="hljs-keyword">return</span> cache.get(obj);
  }
  <span class="hljs-keyword">const</span> expensiveData = computeExpensiveData(obj);
  cache.set(obj, expensiveData);
  <span class="hljs-keyword">return</span> expensiveData;
}
</code></pre>
<p>b. Use generators for working with large sequences of data:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">fibonacci</span>(<span class="hljs-params"></span>): <span class="hljs-title">Generator</span>&lt;<span class="hljs-title">number</span>&gt; </span>{
  <span class="hljs-keyword">let</span> [a, b] = [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>];
  <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">yield</span> a;
    [a, b] = [b, a + b];
  }
}

<span class="hljs-keyword">const</span> fib = fibonacci();
<span class="hljs-built_in">console</span>.log(fib.next().value); <span class="hljs-comment">// 0</span>
<span class="hljs-built_in">console</span>.log(fib.next().value); <span class="hljs-comment">// 1</span>
<span class="hljs-built_in">console</span>.log(fib.next().value); <span class="hljs-comment">// 1</span>
<span class="hljs-built_in">console</span>.log(fib.next().value); <span class="hljs-comment">// 2</span>
</code></pre>
<p>In this example:</p>
<ul>
<li><p><strong>Generator Function</strong>: The <code>fibonacci</code> function is a generator that uses the <code>function*</code> syntax.</p>
</li>
<li><p><strong>Yield</strong>: The <code>yield</code> keyword allows the function to pause execution and return a value, resuming where it left off when <code>next()</code> is called again.</p>
</li>
<li><p><strong>Memory Efficiency</strong>: By generating Fibonacci numbers on-demand, we avoid storing the entire sequence in memory, which is particularly useful if we only need a few numbers at a time.</p>
</li>
</ul>
<p><strong>Benefits of Using Generators</strong>:</p>
<ul>
<li><p><strong>Lazy Evaluation</strong>: Values are computed as needed, which saves memory and processing time.</p>
</li>
<li><p><strong>Infinite Sequences</strong>: Generators can produce potentially infinite sequences without running out of memory.</p>
</li>
</ul>
<h3 id="heading-9-use-web-workers-for-intensive-computations">9. Use Web Workers for Intensive Computations</h3>
<p>For computationally intensive tasks that might block the main thread, consider using Web Workers:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.ts</span>
<span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'worker.js'</span>);

worker.postMessage({ data: <span class="hljs-string">'some data'</span> });

worker.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Received result:'</span>, event.data);
};

<span class="hljs-comment">// worker.ts</span>
self.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> result = performExpensiveComputation(event.data);
  self.postMessage(result);
};
</code></pre>
<h2 id="heading-balancing-optimization-and-readability">Balancing Optimization and Readability</h2>
<p>While these optimization techniques can significantly improve your code's performance, it's crucial to maintain a balance with readability. Here are some tips to help you strike that balance:</p>
<ol>
<li><p><strong>Comment your optimizations</strong>: When you apply an optimization that might not be immediately obvious, add a comment explaining what you've done and why.</p>
</li>
<li><p><strong>Use meaningful variable and function names</strong>: Even when optimizing, continue to use clear and descriptive names that explain the purpose of your code.</p>
</li>
<li><p><strong>Extract complex optimizations into separate functions</strong>: If an optimization makes a piece of code harder to understand, consider extracting it into a well-named function.</p>
</li>
<li><p><strong>Write tests</strong>: Comprehensive tests can help ensure that your optimizations don't introduce bugs and can serve as documentation for how the optimized code should behave.</p>
</li>
<li><p><strong>Profile before optimizing</strong>: Use TypeScript's source maps with browser developer tools or Node.js profiling tools to identify actual bottlenecks before applying optimizations.</p>
</li>
<li><p><strong>Consider the trade-offs</strong>: Always weigh the performance gain against the cost in terms of code complexity and maintainability.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Optimizing TypeScript code while maintaining readability is a balancing act that requires skill, experience, and careful consideration. By applying the techniques discussed in this article judiciously and always keeping code clarity in mind, you can write TypeScript code that is both performant and maintainable.</p>
<p>Remember that premature optimization can lead to unnecessary complexity, so always measure and profile your code to identify true bottlenecks. When you do optimize, strive to do so in a way that preserves or even enhances the readability of your code.</p>
<p>By following these principles and techniques, you'll be well on your way to writing TypeScript code that is not only fast and efficient but also clear, maintainable, and a joy to work with.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering TypeORM: Solutions to Limiting Relation Join and Agregation Operation]]></title><description><![CDATA[TypeORM is a powerful Object-Relational Mapping (ORM) library for TypeScript and JavaScript applications. It simplifies the interaction between your application and the underlying database, allowing you to work with entities and relationships instead...]]></description><link>https://blog.yaffalab.com/mastering-typeorm-solutions-to-limiting-relation-join-and-agregation-operation</link><guid isPermaLink="true">https://blog.yaffalab.com/mastering-typeorm-solutions-to-limiting-relation-join-and-agregation-operation</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[nestjs]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[typeorm]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[orm]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 17 Jun 2024 07:00:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1717507452120/e23d29b9-d70b-4909-896e-ff04f98c01df.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TypeORM is a powerful Object-Relational Mapping (ORM) library for TypeScript and JavaScript applications. It simplifies the interaction between your application and the underlying database, allowing you to work with entities and relationships instead of raw SQL queries. However, as your application grows in complexity, you may encounter scenarios where you need to perform more advanced queries or manipulations.</p>
<p>In this article, we'll explore two common questions from Stack Overflow related to TypeORM and provide detailed explanations and solutions to help you master these scenarios.</p>
<h2 id="heading-question-1-how-to-limit-children-while-left-joining-in-typeorm">Question 1: How to Limit Children While Left Joining in TypeORM?</h2>
<p>One common requirement in many applications is to fetch data from multiple related entities while limiting the number of child entities returned. This is especially useful when dealing with scenarios like displaying a limited number of comments for a blog post or showing a preview of recent activities.</p>
<p>Here's a solution to this problem using TypeORM:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// 1. Fetch all users</span>
<span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepository
  .createQueryBuilder(<span class="hljs-string">"user"</span>)
  .getMany();

<span class="hljs-comment">// 2. Loop through users and fetch photos with limit</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> user <span class="hljs-keyword">of</span> users) {
  user.photos = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.photoRepository
    .createQueryBuilder(<span class="hljs-string">"photo"</span>)
    .where(<span class="hljs-string">"photo.userId = :userId"</span>, { userId: user.id })
    .limit(<span class="hljs-number">5</span>) <span class="hljs-comment">// Set the desired limit here</span>
    .getMany();
}

<span class="hljs-comment">// Return users with limited photos</span>
</code></pre>
<p>In this example, we first fetch all users using the <code>userRepository</code>. Then, we loop through each user and fetch the associated photos using the <code>photoRepository</code>. The key step here is the <code>.limit(5)</code> clause, which limits the number of photos returned to 5 for each user.</p>
<p>This approach involves two separate queries: one to fetch all users, and another to fetch the limited photos for each user. While this solution works, it may not be the most efficient approach, especially if you have a large number of users or photos.</p>
<p>An alternative approach could be to use a single query with a subquery to fetch the limited photos for each user:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.userRepository
  .createQueryBuilder(<span class="hljs-string">"user"</span>)
  .leftJoinAndSelect(
    <span class="hljs-string">"user.photos"</span>,
    <span class="hljs-string">"photo"</span>,
    <span class="hljs-string">"photo.id IN (SELECT p.id FROM Photo p WHERE p.userId = user.id ORDER BY p.createdAt DESC LIMIT 5)"</span>
  )
  .getMany();
</code></pre>
<p>In this example, we use the <code>leftJoinAndSelect</code> method to fetch the users and their associated photos in a single query. The subquery <code>SELECT</code> <a target="_blank" href="http://p.id"><code>p.id</code></a> <code>FROM Photo p WHERE p.userId =</code> <a target="_blank" href="http://user.id"><code>user.id</code></a> <code>ORDER BY p.createdAt DESC LIMIT 5</code> selects the top 5 most recent photos for each user based on the <code>createdAt</code> column. The <code>IN</code> clause then filters the photos to only include those returned by the subquery.</p>
<p>This approach can be more efficient as it reduces the number of database queries, but it may be more complex to write and maintain, especially for more intricate scenarios.</p>
<h2 id="heading-question-2-how-to-use-typeorms-sum-operator-on-a-relations-field">Question 2: How to Use TypeORM's SUM Operator on a Relation's Field?</h2>
<p>Another common requirement in applications is to perform aggregations, such as calculating the sum, average, or count of values from related entities. For example, you might want to calculate the total duration of all songs in a playlist.</p>
<p>Here's how you can achieve this using TypeORM:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> playlist = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.playlistRepository
  .createQueryBuilder(<span class="hljs-string">"playlist"</span>)
  .where(<span class="hljs-string">"playlist.id = :playlistId"</span>, { playlistId })
  .leftJoinAndSelect(<span class="hljs-string">"playlist.songs"</span>, <span class="hljs-string">"songs"</span>)
  .addSelect(<span class="hljs-string">"SUM(songs.duration)"</span>, <span class="hljs-string">"totalDuration"</span>)
  .getRawOne();

<span class="hljs-built_in">console</span>.log(playlist.totalDuration);
</code></pre>
<p>In this example, we use the <code>createQueryBuilder</code> to build a query for the <code>playlistRepository</code>. We start by filtering for a specific playlist using the <code>where</code> clause.</p>
<p>Next, we use the <code>leftJoinAndSelect</code> method to include the related <code>songs</code> entities in the query. This allows us to access the <code>duration</code> field of each song.</p>
<p>The key step here is the <code>addSelect("SUM(songs.duration)", "totalDuration")</code> clause. This tells TypeORM to calculate the sum of the <code>duration</code> field from all related songs and alias the result as <code>totalDuration</code>.</p>
<p>Finally, we use the <code>getRawOne</code> method to execute the query and retrieve the result as a raw object. The <code>totalDuration</code> property of this object will contain the calculated sum of all song durations for the specified playlist.</p>
<p>It's important to note that <code>getRawOne</code> and <code>getRawMany</code> are used when you need to perform aggregations or other advanced SQL operations that cannot be easily represented using TypeORM's entity model.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>TypeORM is a powerful tool that simplifies database interactions in TypeScript and JavaScript applications. However, as your application's requirements grow more complex, you may encounter scenarios where you need to perform advanced queries or manipulations.</p>
<p>In this article, we explored two common questions from Stack Overflow related to TypeORM: limiting the number of child entities returned while left joining, and using the SUM operator on a relation's field. We provided detailed explanations and solutions to help you master these scenarios and unlock the full potential of TypeORM in your applications.</p>
<p>Remember, while TypeORM abstracts away much of the complexities of working with databases, it's still essential to have a solid understanding of SQL and query optimization techniques. By combining the power of TypeORM with your SQL knowledge, you can build robust and efficient applications that meet even the most demanding requirements.</p>
]]></content:encoded></item><item><title><![CDATA[The Paradox of Choice: Why Limiting Your Tech Stack Matters]]></title><description><![CDATA[As developers, we live in an era of unprecedented technological abundance. Every day, new libraries, frameworks, and tools emerge, promising to revolutionize the way we build software. While this wealth of options can be exciting, it also presents a ...]]></description><link>https://blog.yaffalab.com/the-paradox-of-choice-why-limiting-your-tech-stack-matters</link><guid isPermaLink="true">https://blog.yaffalab.com/the-paradox-of-choice-why-limiting-your-tech-stack-matters</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[yaffalab]]></category><category><![CDATA[Mindset]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 10 Jun 2024 07:00:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1717506155173/f2a62511-feff-4bcc-aaba-cdf609cd29d5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As developers, we live in an era of unprecedented technological abundance. Every day, new libraries, frameworks, and tools emerge, promising to revolutionize the way we build software. While this wealth of options can be exciting, it also presents a significant challenge: the paradox of choice.</p>
<p>The paradox of choice refers to the psychological phenomenon where too many choices can lead to decision paralysis, anxiety, and dissatisfaction. In the context of software development, constantly chasing the latest and greatest technologies can be a double-edged sword – it can fuel innovation, but it can also hinder productivity and progress.</p>
<h2 id="heading-the-allure-of-the-new">The Allure of the New</h2>
<p>It's easy to get caught up in the hype surrounding new technologies. Developers are naturally curious, and we tend to gravitate towards shiny new tools that promise to make our lives easier. However, this constant pursuit of novelty can come at a cost.</p>
<p>Firstly, learning and adopting new technologies takes time and effort. Every new library or framework you introduce into your project comes with its own learning curve, documentation, and potential compatibility issues. This overhead can quickly add up, slowing down your development process and increasing the cognitive load on your team.</p>
<p>Secondly, the rapidly evolving tech landscape can lead to a sense of FOMO (Fear of Missing Out). We may feel pressured to stay on top of every new trend, constantly questioning our existing tech stack and wondering if we're falling behind. This mindset can breed anxiety and uncertainty, ultimately undermining our confidence in the tools we're using.</p>
<h2 id="heading-the-value-of-stability-and-consistency">The Value of Stability and Consistency</h2>
<p>While staying up-to-date with emerging technologies is essential, it's equally important to recognize the value of stability and consistency in your tech stack. By limiting the number of libraries and frameworks you use, you can reap several benefits:</p>
<ol>
<li><p><strong>Increased Productivity</strong>: Familiarity with a well-defined set of tools allows your team to work more efficiently, reducing the time spent on learning and onboarding new technologies.</p>
</li>
<li><p><strong>Improved Maintainability</strong>: A consistent tech stack makes it easier to maintain and refactor your codebase over time, as your team develops a deep understanding of the tools and their interdependencies.</p>
</li>
<li><p><strong>Enhanced Collaboration</strong>: When everyone on your team is working with the same set of tools, communication and collaboration become more seamless, reducing the potential for misunderstandings and conflicting approaches.</p>
</li>
<li><p><strong>Focused Learning and Expertise</strong>: By concentrating your efforts on a limited number of technologies, your team can develop deeper expertise and become true masters of those tools, rather than spreading themselves thin across a vast array of options.</p>
</li>
</ol>
<h2 id="heading-finding-the-right-balance">Finding the Right Balance</h2>
<p>Of course, this doesn't mean that you should completely ignore new technologies. Innovation is vital in the software development world, and staying too rigid can leave you behind. The key is to strike a balance between embracing innovation and maintaining stability.</p>
<p>One approach is to periodically set aside dedicated time for exploring and evaluating new technologies. This could be a regular "tech exploration day" or a dedicated team tasked with researching and experimenting with emerging tools and libraries. This controlled approach allows you to stay informed without disrupting your core development process.</p>
<p>Additionally, it's essential to establish clear criteria for evaluating and adopting new technologies. Consider factors such as:</p>
<ul>
<li><p><strong>Maturity and Stability</strong>: Is the new technology well-established, with an active community and long-term support?</p>
</li>
<li><p><strong>Compatibility</strong>: How well does the new technology integrate with your existing tech stack and infrastructure?</p>
</li>
<li><p><strong>Learning Curve</strong>: How steep is the learning curve, and how much effort will be required to upskill your team?</p>
</li>
<li><p><strong>Maintenance and Support</strong>: Are there adequate resources and documentation available for ongoing maintenance and support?</p>
</li>
</ul>
<p>By carefully weighing the pros and cons of new technologies against your established criteria, you can make informed decisions about when and what to adopt, minimizing the risk of disruption and maximizing the potential benefits.</p>
<h2 id="heading-embracing-intentional-constraint">Embracing Intentional Constraint</h2>
<p>In the end, the key to navigating the paradox of choice is to embrace intentional constraint. By consciously limiting your tech stack and being selective about the technologies you adopt, you can avoid the pitfalls of constant churn and indecision.</p>
<p>This doesn't mean stifling innovation or ignoring new developments – it means being intentional and strategic about how you incorporate new tools and libraries into your workflow. By striking the right balance between stability and innovation, you can create a tech stack that is both flexible and maintainable, allowing your team to focus on delivering high-quality software without being constantly distracted by the latest shiny object.</p>
<p>Remember, the goal is not to simply accumulate technologies, but to build robust, scalable, and maintainable systems that meet the needs of your users and your business. By embracing intentional constraint and carefully curating your tech stack, you can achieve that goal while avoiding the paralysis of too many choices.</p>
]]></content:encoded></item><item><title><![CDATA[The Mediator Design Pattern: Facilitating Object Communication]]></title><description><![CDATA[In software design, objects often need to communicate and collaborate with each other to achieve complex functionality. However, direct communication between objects can lead to tight coupling, making the system difficult to maintain and extend. This...]]></description><link>https://blog.yaffalab.com/the-mediator-design-pattern-facilitating-object-communication</link><guid isPermaLink="true">https://blog.yaffalab.com/the-mediator-design-pattern-facilitating-object-communication</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[capsule]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 03 Jun 2024 07:00:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716284773248/63c0f2cb-27d2-449b-a9e8-593263f748c7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In software design, objects often need to communicate and collaborate with each other to achieve complex functionality. However, direct communication between objects can lead to tight coupling, making the system difficult to maintain and extend. This is where the Mediator design pattern comes into play. The Mediator pattern promotes loose coupling by introducing a mediator object that handles the communication between other objects, known as colleagues.</p>
<h2 id="heading-what-is-the-mediator-design-pattern">What is the Mediator Design Pattern?</h2>
<p>The Mediator design pattern is a behavioral pattern that defines an object (the mediator) that encapsulates how a set of objects (colleagues) interact with each other. Instead of direct communication between colleagues, all communication is channeled through the mediator, which acts as a central hub for coordinating the interactions.</p>
<p>The pattern involves three main components:</p>
<ol>
<li><p><strong>Mediator</strong>: The central object that handles communication between colleagues.</p>
</li>
<li><p><strong>Colleague</strong>: The objects that need to communicate with each other.</p>
</li>
<li><p><strong>ConcreteMediator</strong>: The concrete implementation of the Mediator interface, which defines the communication logic between colleagues.</p>
</li>
</ol>
<p>Here's how the Mediator pattern works:</p>
<ol>
<li><p>Each colleague knows about the mediator object and communicates with it instead of directly communicating with other colleagues.</p>
</li>
<li><p>When a colleague needs to interact with another colleague, it sends a request to the mediator.</p>
</li>
<li><p>The mediator receives the request and decides how to handle it, potentially involving other colleagues as needed.</p>
</li>
<li><p>The mediator then coordinates the appropriate actions or responses among the relevant colleagues.</p>
</li>
</ol>
<p>By centralizing the communication logic in the mediator, this pattern promotes loose coupling between colleagues, making the system more maintainable, extensible, and testable.</p>
<h2 id="heading-real-world-use-cases">Real-World Use Cases</h2>
<p>The Mediator design pattern is commonly used in various scenarios where objects need to communicate with each other in a decoupled and organized manner. Here are some real-world examples:</p>
<ol>
<li><p><strong>User Interface (UI) Components</strong>: In GUI applications, mediators can coordinate the communication between different UI components, such as buttons, text fields, and menus.</p>
</li>
<li><p><strong>Chat Applications</strong>: Chat applications often use a mediator to handle the communication between multiple participants and manage the message routing and delivery.</p>
</li>
<li><p><strong>Middleware Systems</strong>: Mediators are commonly used in middleware systems to facilitate the communication between different components or services in a distributed environment.</p>
</li>
<li><p><strong>Workflow Management Systems</strong>: In workflow management systems, a mediator can coordinate the execution of tasks and manage the flow of data between different processes or activities.</p>
</li>
</ol>
<h2 id="heading-example">Example</h2>
<p>Here's a TypeScript example that demonstrates the implementation of the Mediator pattern in a simplified chat application:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Mediator interface</span>
<span class="hljs-keyword">interface</span> Mediator {
  sendMessage(message: <span class="hljs-built_in">string</span>, sender: Colleague): <span class="hljs-built_in">void</span>;
}

<span class="hljs-comment">// Colleague interface</span>
<span class="hljs-keyword">interface</span> Colleague {
  name: <span class="hljs-built_in">string</span>;
  mediator: Mediator;
  receiveMessage(message: <span class="hljs-built_in">string</span>, sender: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span>;
}

<span class="hljs-comment">// Concrete Mediator</span>
<span class="hljs-keyword">class</span> ChatRoomMediator <span class="hljs-keyword">implements</span> Mediator {
  <span class="hljs-keyword">private</span> colleagues: Colleague[] = [];

  addColleague(colleague: Colleague): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.colleagues.push(colleague);
    colleague.mediator = <span class="hljs-built_in">this</span>;
  }

  sendMessage(message: <span class="hljs-built_in">string</span>, sender: Colleague): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> colleague <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.colleagues) {
      <span class="hljs-keyword">if</span> (colleague !== sender) {
        colleague.receiveMessage(message, sender.name);
      }
    }
  }
}

<span class="hljs-comment">// Concrete Colleague</span>
<span class="hljs-keyword">class</span> User <span class="hljs-keyword">implements</span> Colleague {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> name: <span class="hljs-built_in">string</span>, <span class="hljs-keyword">private</span> mediator: Mediator</span>) {
    <span class="hljs-built_in">this</span>.mediator = mediator;
  }

  sendMessage(message: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.mediator.sendMessage(message, <span class="hljs-built_in">this</span>);
  }

  receiveMessage(message: <span class="hljs-built_in">string</span>, sender: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${sender}</span> to <span class="hljs-subst">${<span class="hljs-built_in">this</span>.name}</span>: <span class="hljs-subst">${message}</span>`</span>);
  }
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">const</span> mediator = <span class="hljs-keyword">new</span> ChatRoomMediator();

<span class="hljs-keyword">const</span> ayoub = <span class="hljs-keyword">new</span> User(<span class="hljs-string">'Ayoub'</span>, mediator);
<span class="hljs-keyword">const</span> jeremy = <span class="hljs-keyword">new</span> User(<span class="hljs-string">'Jérémy'</span>, mediator);
<span class="hljs-keyword">const</span> bob = <span class="hljs-keyword">new</span> User(<span class="hljs-string">'Bob'</span>, mediator);

mediator.addColleague(ayoub);
mediator.addColleague(jeremy);
mediator.addColleague(bob);

ayoub.sendMessage(<span class="hljs-string">'Hello, everyone!'</span>);
jeremy.sendMessage(<span class="hljs-string">'Hi, Ayoub! How are you?'</span>);
</code></pre>
<p>In this example, we have a <code>Mediator</code> interface that defines the <code>sendMessage</code> method for sending messages to colleagues. The <code>Colleague</code> interface defines the <code>receiveMessage</code> method for receiving messages from other colleagues.</p>
<p>The <code>ChatRoomMediator</code> class is a concrete implementation of the <code>Mediator</code> interface, responsible for managing the list of colleagues and forwarding messages to the appropriate recipients.</p>
<p>The <code>User</code> class is a concrete implementation of the <code>Colleague</code> interface, representing a user in the chat application. Users can send messages through the mediator and receive messages from other users.</p>
<p>In the usage example, we create a <code>ChatRoomMediator</code> instance, three <code>User</code> instances (John, Jane, and Bob), and add them as colleagues to the mediator. John sends a message, which is broadcasted to Jane and Bob through the mediator. Jane then sends a message back to John, also via the mediator.</p>
<h2 id="heading-benefits-of-the-mediator-pattern">Benefits of the Mediator Pattern</h2>
<p>Using the Mediator design pattern offers several benefits:</p>
<ol>
<li><p><strong>Loose Coupling</strong>: By introducing a mediator, the colleagues become loosely coupled, as they no longer need to know about each other's implementation details or communicate directly.</p>
</li>
<li><p><strong>Centralized Communication Logic</strong>: The communication logic is centralized in the mediator, making it easier to maintain and modify the communication logic without affecting the colleague objects.</p>
</li>
<li><p><strong>Extensibility</strong>: New colleagues can be added or removed without affecting the existing colleagues, as long as they follow the established communication protocol with the mediator.</p>
</li>
<li><p><strong>Reusability</strong>: The mediator and colleague objects can be reused in different contexts or applications, as they are decoupled from each other.</p>
</li>
<li><p><strong>Simplified Communication</strong>: Complex communication workflows between multiple colleagues can be simplified by using a mediator to coordinate the interactions.</p>
</li>
</ol>
<p>However, it's important to remember that introducing a mediator can also add complexity to the system, especially if the mediator becomes too complex or takes on too many responsibilities. As with any design pattern, it's crucial to evaluate the trade-offs and apply the Mediator pattern judiciously based on the specific requirements of your project.</p>
<p>The Mediator design pattern is a powerful tool for facilitating communication between objects while promoting loose coupling and maintainability. By centralizing the communication logic in a mediator object, this pattern helps to create more modular and extensible software systems.</p>
]]></content:encoded></item><item><title><![CDATA[SOLID Principles: The Building Blocks of Clean Code]]></title><description><![CDATA[Have you ever felt frustrated when trying to make changes to an existing codebase? Or maybe you've struggled to understand code written by someone else? If so, chances are that the code wasn't following the SOLID principles. These principles are like...]]></description><link>https://blog.yaffalab.com/solid-principles-the-building-blocks-of-clean-code</link><guid isPermaLink="true">https://blog.yaffalab.com/solid-principles-the-building-blocks-of-clean-code</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Sun, 26 May 2024 22:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716217481002/6642b272-76fb-4c8b-b4e8-b87a1f57a4a9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever felt frustrated when trying to make changes to an existing codebase? Or maybe you've struggled to understand code written by someone else? If so, chances are that the code wasn't following the SOLID principles. These principles are like the golden rules for writing clean, maintainable, and scalable code. They can help you avoid headaches and make your life as a developer much easier.</p>
<h2 id="heading-what-are-the-solid-principles">What are the SOLID Principles?</h2>
<p>SOLID is an acronym that stands for five fundamental principles of object-oriented programming and design:</p>
<ol>
<li><p><strong>S</strong>ingle Responsibility Principle</p>
</li>
<li><p><strong>O</strong>pen/Closed Principle</p>
</li>
<li><p><strong>L</strong>iskov Substitution Principle</p>
</li>
<li><p><strong>I</strong>nterface Segregation Principle</p>
</li>
<li><p><strong>D</strong>ependency Inversion Principle</p>
</li>
</ol>
<p>Let's dive into each of these principles and see how they can help you write better code!</p>
<h3 id="heading-single-responsibility-principle">Single Responsibility Principle</h3>
<p>The Single Responsibility Principle (SRP) states that a class should have only one reason to change. In other words, a class should have a single, well-defined responsibility or job. This principle helps to create classes that are focused, easy to understand, and easier to maintain.</p>
<p><strong>Example</strong>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Violates SRP</span>
<span class="hljs-keyword">class</span> Employee {
  calculatePayroll() { <span class="hljs-comment">/* ... */</span> }
  generateReport() { <span class="hljs-comment">/* ... */</span> }
  sendEmail() { <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-comment">// Follows SRP</span>
<span class="hljs-keyword">class</span> PayrollCalculator {
  calculatePayroll() { <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-keyword">class</span> ReportGenerator {
  generateReport() { <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-keyword">class</span> EmailSender {
  sendEmail() { <span class="hljs-comment">/* ... */</span> }
}
</code></pre>
<p>In the first example, the <code>Employee</code> class has multiple responsibilities, which violates the Single Responsibility Principle. In the second example, each class has a single responsibility, making the code more focused, easier to understand, and easier to maintain.</p>
<h3 id="heading-openclosed-principle">Open/Closed Principle</h3>
<p>The Open/Closed Principle (OCP) states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to add new features or behaviors without modifying the existing code. This principle promotes code reusability and makes it easier to add new functionality without breaking existing features.</p>
<p><strong>Example</strong>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Violates OCP</span>
<span class="hljs-keyword">class</span> ShapeCalculator {
  calculateArea(shape: Shape) {
    <span class="hljs-keyword">if</span> (shape <span class="hljs-keyword">instanceof</span> Rectangle) {
      <span class="hljs-comment">// Calculate area for Rectangle</span>
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (shape <span class="hljs-keyword">instanceof</span> Circle) {
      <span class="hljs-comment">// Calculate area for Circle</span>
    }
    <span class="hljs-comment">// ... and so on for other shapes</span>
  }
}

<span class="hljs-comment">// Follows OCP</span>
<span class="hljs-keyword">interface</span> Shape {
  calculateArea(): <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">class</span> Rectangle <span class="hljs-keyword">implements</span> Shape {
  calculateArea() {
    <span class="hljs-comment">// Calculate area for Rectangle</span>
  }
}

<span class="hljs-keyword">class</span> Circle <span class="hljs-keyword">implements</span> Shape {
  calculateArea() {
    <span class="hljs-comment">// Calculate area for Circle</span>
  }
}

<span class="hljs-comment">// You can now add new shapes without modifying the ShapeCalculator</span>
<span class="hljs-keyword">class</span> Triangle <span class="hljs-keyword">implements</span> Shape {
  calculateArea() {
    <span class="hljs-comment">// Calculate area for Triangle</span>
  }
}
</code></pre>
<p>In the first example, the <code>ShapeCalculator</code> class violates the Open/Closed Principle because you need to modify it every time you want to add support for a new shape. In the second example, by introducing an interface and concrete classes for each shape, you can extend the functionality by adding new shape classes without modifying the existing code.</p>
<h3 id="heading-liskov-substitution-principle">Liskov Substitution Principle</h3>
<p>The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, the subclass should not violate the behavior expected from its superclass.</p>
<p><strong>Example</strong>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Violates LSP</span>
<span class="hljs-keyword">class</span> Rectangle {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">protected</span> width: <span class="hljs-built_in">number</span>, <span class="hljs-keyword">protected</span> height: <span class="hljs-built_in">number</span></span>) {}

  setWidth(width: <span class="hljs-built_in">number</span>) {
    <span class="hljs-built_in">this</span>.width = width;
  }

  setHeight(height: <span class="hljs-built_in">number</span>) {
    <span class="hljs-built_in">this</span>.height = height;
  }

  area() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.width * <span class="hljs-built_in">this</span>.height;
  }
}

<span class="hljs-keyword">class</span> Square <span class="hljs-keyword">extends</span> Rectangle {
  setWidth(width: <span class="hljs-built_in">number</span>) {
    <span class="hljs-built_in">this</span>.width = width;
    <span class="hljs-built_in">this</span>.height = width; <span class="hljs-comment">// Violates the behavior of a Rectangle</span>
  }

  setHeight(height: <span class="hljs-built_in">number</span>) {
    <span class="hljs-built_in">this</span>.width = height;
    <span class="hljs-built_in">this</span>.height = height; <span class="hljs-comment">// Violates the behavior of a Rectangle</span>
  }
}

<span class="hljs-comment">// Follows LSP</span>
<span class="hljs-keyword">interface</span> Shape {
  area(): <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">class</span> Rectangle <span class="hljs-keyword">implements</span> Shape {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">protected</span> width: <span class="hljs-built_in">number</span>, <span class="hljs-keyword">protected</span> height: <span class="hljs-built_in">number</span></span>) {}

  area() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.width * <span class="hljs-built_in">this</span>.height;
  }
}

<span class="hljs-keyword">class</span> Square <span class="hljs-keyword">implements</span> Shape {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> side: <span class="hljs-built_in">number</span></span>) {}

  area() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.side * <span class="hljs-built_in">this</span>.side;
  }
}
</code></pre>
<p>In the first example, the <code>Square</code> class violates the Liskov Substitution Principle because it inherits from <code>Rectangle</code> but doesn't adhere to the expected behavior of a rectangle (where the width and height can be set independently). In the second example, by using an interface and separate classes for <code>Rectangle</code> and <code>Square</code>, we ensure that the behavior of each shape is respected and follows the Liskov Substitution Principle.</p>
<h3 id="heading-interface-segregation-principle">Interface Segregation Principle</h3>
<p>The Interface Segregation Principle (ISP) states that clients should not be forced to depend on interfaces they do not use. In other words, it's better to have many small, specific interfaces than one large, monolithic interface.</p>
<p><strong>Example</strong>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Violates ISP</span>
<span class="hljs-keyword">interface</span> Worker {
  work(): <span class="hljs-built_in">void</span>;
  eat(): <span class="hljs-built_in">void</span>;
  sleep(): <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">class</span> OfficeWorker <span class="hljs-keyword">implements</span> Worker {
  work() { <span class="hljs-comment">/* ... */</span> }
  eat() { <span class="hljs-comment">/* ... */</span> }
  sleep() { <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-keyword">class</span> Robot <span class="hljs-keyword">implements</span> Worker {
  work() { <span class="hljs-comment">/* ... */</span> }
  eat() { <span class="hljs-comment">/* Not applicable for a Robot */</span> }
  sleep() { <span class="hljs-comment">/* Not applicable for a Robot */</span> }
}

<span class="hljs-comment">// Follows ISP</span>
<span class="hljs-keyword">interface</span> Worker {
  work(): <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">interface</span> FeedableWorker <span class="hljs-keyword">extends</span> Worker {
  eat(): <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">interface</span> SleepingWorker <span class="hljs-keyword">extends</span> Worker {
  sleep(): <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">class</span> OfficeWorker <span class="hljs-keyword">implements</span> FeedableWorker, SleepingWorker {
  work() { <span class="hljs-comment">/* ... */</span> }
  eat() { <span class="hljs-comment">/* ... */</span> }
  sleep() { <span class="hljs-comment">/* ... */</span> }
}

<span class="hljs-keyword">class</span> Robot <span class="hljs-keyword">implements</span> Worker {
  work() { <span class="hljs-comment">/* ... */</span> }
}
</code></pre>
<p>In the first example, the <code>Worker</code> interface violates the Interface Segregation Principle because it forces clients like the <code>Robot</code> class to implement methods they don't need (<code>eat</code> and <code>sleep</code>). In the second example, by segregating the interfaces into smaller, more specific ones, we ensure that clients only depend on the interfaces they actually need, following the Interface Segregation Principle.</p>
<h3 id="heading-dependency-inversion-principle">Dependency Inversion Principle</h3>
<p>The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; both should depend on abstractions. Additionally, abstractions should not depend on details, but details should depend on abstractions.</p>
<p><strong>Example</strong>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Violates DIP</span>
<span class="hljs-keyword">class</span> DatabaseRepository {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> database: MySQLDatabase</span>) {}

  <span class="hljs-comment">// ...</span>
}

<span class="hljs-keyword">class</span> MySQLDatabase {
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-comment">// Follows DIP</span>
<span class="hljs-keyword">interface</span> Database {
  query(query: <span class="hljs-built_in">string</span>): Result;
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-keyword">class</span> MySQLDatabase <span class="hljs-keyword">implements</span> Database {
  query(query: <span class="hljs-built_in">string</span>): Result {
    <span class="hljs-comment">// MySQL-specific implementation</span>
  }
}

<span class="hljs-keyword">class</span> PostgreSQLDatabase <span class="hljs-keyword">implements</span> Database {
  query(query: <span class="hljs-built_in">string</span>): Result {
    <span class="hljs-comment">// PostgreSQL-specific implementation</span>
  }
}

<span class="hljs-keyword">class</span> DatabaseRepository {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> database: Database</span>) {}

  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>In the first example, the <code>DatabaseRepository</code> class directly depends on the <code>MySQLDatabase</code> class, which is a low-level module. If we want to switch to a different database system, we would need to modify the <code>DatabaseRepository</code> class. In the second example, by introducing an abstraction (<code>Database</code> interface), both the <code>DatabaseRepository</code> and the database implementations depend on this abstraction. This way, we can easily swap out the database implementation without modifying the <code>DatabaseRepository</code> class, following the Dependency Inversion Principle.</p>
<h2 id="heading-why-use-the-solid-principles">Why Use the SOLID Principles?</h2>
<p>Following the SOLID principles can bring numerous benefits to your codebase:</p>
<ol>
<li><p><strong>Maintainability</strong>: SOLID code is easier to understand, modify, and extend, making it more maintainable in the long run.</p>
</li>
<li><p><strong>Testability</strong>: Classes with single responsibilities and loose coupling are easier to unit test, leading to more reliable and robust code.</p>
<p> <strong>Example</strong>:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// Violates SRP and difficult to test</span>
 <span class="hljs-keyword">class</span> UserManager {
   <span class="hljs-keyword">private</span> users: User[] = [];

   addUser(user: User) {
     <span class="hljs-comment">// Validate user</span>
     <span class="hljs-comment">// Save user to database</span>
     <span class="hljs-comment">// Send welcome email</span>
   }

   <span class="hljs-comment">// ...</span>
 }

 <span class="hljs-comment">// Follows SRP and easier to test</span>
 <span class="hljs-keyword">class</span> UserValidator {
   validateUser(user: User): <span class="hljs-built_in">boolean</span> {
     <span class="hljs-comment">// Validate user</span>
   }
 }

 <span class="hljs-keyword">class</span> UserRepository {
   saveUser(user: User) {
     <span class="hljs-comment">// Save user to database</span>
   }
 }

 <span class="hljs-keyword">class</span> EmailSender {
   sendWelcomeEmail(user: User) {
     <span class="hljs-comment">// Send welcome email</span>
   }
 }

 <span class="hljs-keyword">class</span> UserManager {
   <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
     <span class="hljs-keyword">private</span> validator: UserValidator,
     <span class="hljs-keyword">private</span> repository: UserRepository,
     <span class="hljs-keyword">private</span> emailSender: EmailSender
   </span>) {}

   addUser(user: User) {
     <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.validator.validateUser(user)) {
       <span class="hljs-built_in">this</span>.repository.saveUser(user);
       <span class="hljs-built_in">this</span>.emailSender.sendWelcomeEmail(user);
     }
   }
 }
</code></pre>
<p> In the first example, the <code>UserManager</code> class has multiple responsibilities, making it difficult to test each responsibility in isolation. In the second example, by separating the responsibilities into different classes, we can easily unit test each class independently, leading to more reliable and robust code.</p>
</li>
<li><p><strong>Scalability</strong>: By adhering to the SOLID principles, your codebase becomes more flexible and scalable, allowing you to add new features or modify existing ones with minimal effort.</p>
</li>
<li><p><strong>Reusability</strong>: SOLID code promotes the creation of modular and reusable components, reducing duplication and increasing efficiency.</p>
<p> <strong>Example</strong>:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// Violates OCP and DIP</span>
 <span class="hljs-keyword">class</span> EmailSender {
   sendEmail(message: <span class="hljs-built_in">string</span>, recipient: <span class="hljs-built_in">string</span>) {
     <span class="hljs-comment">// Send email using a specific email service provider</span>
   }
 }

 <span class="hljs-comment">// Follows OCP and DIP</span>
 <span class="hljs-keyword">interface</span> EmailService {
   sendEmail(message: <span class="hljs-built_in">string</span>, recipient: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span>;
 }

 <span class="hljs-keyword">class</span> SMTPEmailService <span class="hljs-keyword">implements</span> EmailService {
   sendEmail(message: <span class="hljs-built_in">string</span>, recipient: <span class="hljs-built_in">string</span>) {
     <span class="hljs-comment">// Send email using SMTP</span>
   }
 }

 <span class="hljs-keyword">class</span> HTTPEmailService <span class="hljs-keyword">implements</span> EmailService {
   sendEmail(message: <span class="hljs-built_in">string</span>, recipient: <span class="hljs-built_in">string</span>) {
     <span class="hljs-comment">// Send email using HTTP API</span>
   }
 }

 <span class="hljs-keyword">class</span> EmailSender {
   <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> emailService: EmailService</span>) {}

   sendEmail(message: <span class="hljs-built_in">string</span>, recipient: <span class="hljs-built_in">string</span>) {
     <span class="hljs-built_in">this</span>.emailService.sendEmail(message, recipient);
   }
 }
</code></pre>
<p> In the first example, the <code>EmailSender</code> class is tightly coupled to a specific email service provider, making it difficult to switch providers or reuse the email sending functionality elsewhere. In the second example, by introducing an abstraction (<code>EmailService</code> interface) and implementing different email service providers, we can easily swap out the email service implementation or reuse the <code>EmailSender</code> class with different email services, promoting code reusability and extensibility.</p>
</li>
<li><p><strong>Collaboration</strong>: When working in a team, SOLID principles help ensure that the codebase is consistent, readable, and easier for multiple developers to work on simultaneously.</p>
<p> <strong>Example</strong>:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// Violates SRP and ISP</span>
 <span class="hljs-keyword">class</span> UserService {
   <span class="hljs-keyword">private</span> users: User[] = [];

   addUser(user: User) {
     <span class="hljs-comment">// Validate user</span>
     <span class="hljs-comment">// Save user to database</span>
     <span class="hljs-comment">// Send welcome email</span>
   }

   updateUser(userId: <span class="hljs-built_in">string</span>, updatedUser: User) {
     <span class="hljs-comment">// Find user by ID</span>
     <span class="hljs-comment">// Update user in database</span>
     <span class="hljs-comment">// Send update notification email</span>
   }

   deleteUser(userId: <span class="hljs-built_in">string</span>) {
     <span class="hljs-comment">// Find user by ID</span>
     <span class="hljs-comment">// Delete user from database</span>
     <span class="hljs-comment">// Send deletion confirmation email</span>
   }

   <span class="hljs-comment">// ...</span>
 }

 <span class="hljs-comment">// Follows SOLID principles</span>
 <span class="hljs-keyword">interface</span> UserRepository {
   addUser(user: User): <span class="hljs-built_in">void</span>;
   updateUser(userId: <span class="hljs-built_in">string</span>, updatedUser: User): <span class="hljs-built_in">void</span>;
   deleteUser(userId: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span>;
   <span class="hljs-comment">// ...</span>
 }

 <span class="hljs-keyword">interface</span> EmailService {
   sendWelcomeEmail(user: User): <span class="hljs-built_in">void</span>;
   sendUpdateNotificationEmail(user: User): <span class="hljs-built_in">void</span>;
   sendDeletionConfirmationEmail(userId: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span>;
 }

 <span class="hljs-keyword">class</span> UserService {
   <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
     <span class="hljs-keyword">private</span> userRepository: UserRepository,
     <span class="hljs-keyword">private</span> emailService: EmailService
   </span>) {}

   addUser(user: User) {
     <span class="hljs-built_in">this</span>.userRepository.addUser(user);
     <span class="hljs-built_in">this</span>.emailService.sendWelcomeEmail(user);
   }

   updateUser(userId: <span class="hljs-built_in">string</span>, updatedUser: User) {
     <span class="hljs-built_in">this</span>.userRepository.updateUser(userId, updatedUser);
     <span class="hljs-built_in">this</span>.emailService.sendUpdateNotificationEmail(updatedUser);
   }

   deleteUser(userId: <span class="hljs-built_in">string</span>) {
     <span class="hljs-built_in">this</span>.userRepository.deleteUser(userId);
     <span class="hljs-built_in">this</span>.emailService.sendDeletionConfirmationEmail(userId);
   }
 }
</code></pre>
<p> In the first example, the <code>UserService</code> class has multiple responsibilities and violates the Single Responsibility Principle and the Interface Segregation Principle, making it difficult for multiple developers to work on it simultaneously. In the second example, by separating responsibilities into different interfaces and classes, and adhering to the SOLID principles, the codebase becomes more consistent, readable, and easier for multiple developers to collaborate on.</p>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p> Remember, the SOLID principles are not rigid rules but rather guidelines to help you write better code. As you gain more experience, you'll develop a deeper understanding of when and how to apply these principles effectively.</p>
<p> So, the next time you're writing code, take a moment to think about the SOLID principles. Your future self (and your teammates) will thank you for it!</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Why Even Small Teams Should Embrace Agile Scrum]]></title><description><![CDATA[Whether you are a solo developer or part of a startup with just a couple of coders, adopting an agile project management methodology like Scrum can pay big dividends. While Scrum is often thought of as a framework for large, complex projects with mul...]]></description><link>https://blog.yaffalab.com/why-even-small-teams-should-embrace-agile-scrum</link><guid isPermaLink="true">https://blog.yaffalab.com/why-even-small-teams-should-embrace-agile-scrum</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[software development]]></category><category><![CDATA[agile development]]></category><category><![CDATA[Mindset]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Thu, 23 May 2024 07:00:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716239096219/03c9882d-2428-4f5d-a450-0a805371fd01.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Whether you are a solo developer or part of a startup with just a couple of coders, adopting an agile project management methodology like Scrum can pay big dividends. While Scrum is often thought of as a framework for large, complex projects with multiple teams, it offers substantial benefits even for the smallest software development efforts.</p>
<p>Enhanced Project Visibility and Control At its core, Scrum provides a lightweight process for managing iterative software delivery. It prescribes short development cycles called Sprints, timeboxed to 1-4 weeks, with specific roles, artifacts, and ceremonies to keep work on track. Even with just 2 developers, implementing these Scrum practices brings much-needed structure, focus, and transparency to what can otherwise quickly become a chaotic and ad-hoc process.</p>
<p>The Scrum Master role, often taken on by one of the developers for a small team, is tasked with facilitating key events like Daily Standups, Sprint Planning, and Sprint Retrospectives. These brief but critical checkpoints enable real-time communication of status, coordination of work, continuous improvement, and early mitigation of risks. Rather than developers working in a silo only to struggle to integrate later, Scrum ensures full visibility of what work is being tackled and what impediments exist.</p>
<p>Improved Management Expertise By adopting Scrum, even junior developers gain firsthand experience in core project management skills like setting specific and measurable goals (Sprint Goals), estimating and prioritizing work (Sprint Backlog), tracking progress incrementally, and constantly inspecting and adapting. These lean-agile management competencies are invaluable for any developer's long-term career growth.</p>
<p>Additionally, Scrum creates a natural incentive to learn specialized tools designed to support agile processes. Popular agile project management platforms like Jira, Azure DevOps, or VersionOne provide digital Scrum boards for visualizing Sprint Backlogs and Work in Progress. They offer seamless tracking of requirements, user stories, tasks, defects, and more across the development lifecycle. Being hands-on with these tools enhances modern tech skillsets.</p>
<p>Customer-Centric Mindset A key tenet of agile is delivering the highest value, and highest priority features first through ruthless prioritization of work. Scrum heavily emphasizes stakeholder involvement, frequent inspection of working software, and continuous re-planning to ensure alignment with evolving requirements. While this customer-centric focus is essential for any team, it is especially formative for small startups trying to build a minimum viable product and laser focus on what matters most to users.</p>
<p>Lasting Benefits Even if a development effort eventually transitions to Scrum at scale with multiple teams, having experienced core Scrum roles, artifacts, events, and principles firsthand provides a major head start and avoids a steep learning curve. More importantly, adopting lean techniques early inculcates practices for frequent delivery of working software, driving faster ROI and success.</p>
<p>In summary, while meant to be lightweight, implementing Scrum brings rigor, visibility, focus, and continuous improvement that can be game-changing even for the smallest development teams. It builds better products, and better developers, and lays the foundation for scaling agile practices as an organization grows.</p>
]]></content:encoded></item><item><title><![CDATA[The Memento Design Pattern: Capturing and Restoring Object States Made Easy]]></title><description><![CDATA[Have you ever played a video game and wished you could go back to a previous state before making a crucial decision or encountering a difficult challenge? Or maybe you've wanted to implement a handy "undo" feature in an application you're building. W...]]></description><link>https://blog.yaffalab.com/the-memento-design-pattern-capturing-and-restoring-object-states-made-easy</link><guid isPermaLink="true">https://blog.yaffalab.com/the-memento-design-pattern-capturing-and-restoring-object-states-made-easy</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 20 May 2024 13:48:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716212860231/f21e688f-9380-4c94-9533-aa6364224b69.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever played a video game and wished you could go back to a previous state before making a crucial decision or encountering a difficult challenge? Or maybe you've wanted to implement a handy "undo" feature in an application you're building. Well, the Memento design pattern is here to save the day!</p>
<h2 id="heading-what-is-the-memento-design-pattern">What is the Memento Design Pattern?</h2>
<p>The Memento pattern is like a time machine for objects. It allows you to capture the internal state of an object at a specific point in time, and then restore that state later if needed. This is particularly useful when you need to implement features like undo/redo operations, checkpointing, or transaction-based systems.</p>
<p>The pattern involves three main players:</p>
<ol>
<li><p><strong>Originator</strong>: The object whose state needs to be saved and restored. Think of it as the object you want to travel back in time for.</p>
</li>
<li><p><strong>Memento</strong>: A snapshot or memento of the Originator's internal state at a particular point in time. This is like a time capsule containing all the information needed to restore the object to that state.</p>
</li>
<li><p><strong>Caretaker</strong>: An object responsible for storing and retrieving Mementos. It's like a time-traveling storage facility that keeps all the time capsules safe.</p>
</li>
</ol>
<p>Here's how it works:</p>
<ol>
<li><p>The Originator creates a Memento, capturing its current internal state.</p>
</li>
<li><p>The Originator passes the Memento to the Caretaker for safekeeping.</p>
</li>
<li><p>When needed, the Originator can request the Caretaker to return the Memento, allowing it to restore its previous state.</p>
</li>
</ol>
<p>By separating the responsibilities of state capture, storage, and restoration, the Memento pattern adheres to the principles of encapsulation and information hiding, keeping the internal workings of the objects hidden from the outside world.</p>
<h2 id="heading-real-world-examples">Real-World Examples</h2>
<p>The Memento pattern is used in various applications and domains, making our lives a little easier:</p>
<ol>
<li><p><strong>Video Games</strong>: Many video games implement checkpoints or save points using the Memento pattern, allowing players to save their progress and resume from that point later, without having to start from the beginning every time.</p>
</li>
<li><p><strong>Undo/Redo Operations</strong>: Text editors, image editors, and other software applications often implement undo/redo functionality using the Memento pattern, allowing users to correct mistakes or explore different options easily.</p>
</li>
<li><p><strong>Transaction-based Systems</strong>: In database systems, the Memento pattern can be used to implement transaction rollbacks or savepoints, ensuring data integrity and consistency.</p>
</li>
<li><p><strong>Snapshots/Backups</strong>: Cloud platforms and virtualization tools frequently use the Memento pattern to capture and restore snapshots of virtual machine or container states, providing a safeguard against data loss or system failures.</p>
</li>
<li><p><strong>Version Control Systems</strong>: Version control systems like Git use a similar approach to record and restore changes to source code files, allowing developers to travel back in time and see how their code evolved.</p>
</li>
</ol>
<h2 id="heading-a-simple-example-in-typescript">A Simple Example in TypeScript</h2>
<p>To help you understand the Memento pattern better, let's look at a simple TypeScript example of a video game character:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Originator</span>
<span class="hljs-keyword">class</span> GameCharacter {
  <span class="hljs-keyword">private</span> health: <span class="hljs-built_in">number</span>;
  <span class="hljs-keyword">private</span> mana: <span class="hljs-built_in">number</span>;
  <span class="hljs-keyword">private</span> position: { x: <span class="hljs-built_in">number</span>; y: <span class="hljs-built_in">number</span> };

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">health: <span class="hljs-built_in">number</span>, mana: <span class="hljs-built_in">number</span>, x: <span class="hljs-built_in">number</span>, y: <span class="hljs-built_in">number</span></span>) {
    <span class="hljs-built_in">this</span>.health = health;
    <span class="hljs-built_in">this</span>.mana = mana;
    <span class="hljs-built_in">this</span>.position = { x, y };
  }

  <span class="hljs-keyword">public</span> takeHit(damage: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.health -= damage;
  }

  <span class="hljs-keyword">public</span> useMana(amount: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.mana -= amount;
  }

  <span class="hljs-keyword">public</span> move(x: <span class="hljs-built_in">number</span>, y: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.position.x = x;
    <span class="hljs-built_in">this</span>.position.y = y;
  }

  <span class="hljs-keyword">public</span> createMemento(): GameCharacterMemento {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GameCharacterMemento(<span class="hljs-built_in">this</span>.health, <span class="hljs-built_in">this</span>.mana, <span class="hljs-built_in">this</span>.position.x, <span class="hljs-built_in">this</span>.position.y);
  }

  <span class="hljs-keyword">public</span> restoreFromMemento(memento: GameCharacterMemento): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.health = memento.getHealth();
    <span class="hljs-built_in">this</span>.mana = memento.getMana();
    <span class="hljs-built_in">this</span>.position.x = memento.getX();
    <span class="hljs-built_in">this</span>.position.y = memento.getY();
  }
}

<span class="hljs-comment">// Memento</span>
<span class="hljs-keyword">class</span> GameCharacterMemento {
  <span class="hljs-keyword">private</span> health: <span class="hljs-built_in">number</span>;
  <span class="hljs-keyword">private</span> mana: <span class="hljs-built_in">number</span>;
  <span class="hljs-keyword">private</span> x: <span class="hljs-built_in">number</span>;
  <span class="hljs-keyword">private</span> y: <span class="hljs-built_in">number</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">health: <span class="hljs-built_in">number</span>, mana: <span class="hljs-built_in">number</span>, x: <span class="hljs-built_in">number</span>, y: <span class="hljs-built_in">number</span></span>) {
    <span class="hljs-built_in">this</span>.health = health;
    <span class="hljs-built_in">this</span>.mana = mana;
    <span class="hljs-built_in">this</span>.x = x;
    <span class="hljs-built_in">this</span>.y = y;
  }

  <span class="hljs-keyword">public</span> getHealth(): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.health;
  }

  <span class="hljs-keyword">public</span> getMana(): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.mana;
  }

  <span class="hljs-keyword">public</span> getX(): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.x;
  }

  <span class="hljs-keyword">public</span> getY(): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.y;
  }
}

<span class="hljs-comment">// Caretaker</span>
<span class="hljs-keyword">class</span> GameCharacterCaretaker {
  <span class="hljs-keyword">private</span> mementos: GameCharacterMemento[] = [];

  <span class="hljs-keyword">public</span> addMemento(memento: GameCharacterMemento): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.mementos.push(memento);
  }

  <span class="hljs-keyword">public</span> getMemento(index: <span class="hljs-built_in">number</span>): GameCharacterMemento {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.mementos[index];
  }
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">const</span> character = <span class="hljs-keyword">new</span> GameCharacter(<span class="hljs-number">100</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
<span class="hljs-keyword">const</span> caretaker = <span class="hljs-keyword">new</span> GameCharacterCaretaker();

caretaker.addMemento(character.createMemento());

character.takeHit(<span class="hljs-number">20</span>);
character.useMana(<span class="hljs-number">10</span>);
character.move(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>);

<span class="hljs-comment">// Encounter a difficult enemy, restore to previous state</span>
character.restoreFromMemento(caretaker.getMemento(<span class="hljs-number">0</span>));
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Health: <span class="hljs-subst">${character.getHealth()}</span>, Mana: <span class="hljs-subst">${character.getMana()}</span>, Position: (<span class="hljs-subst">${character.getX()}</span>, <span class="hljs-subst">${character.getY()}</span>)`</span>);
<span class="hljs-comment">// Output: Health: 100, Mana: 50, Position: (0, 0)</span>
</code></pre>
<p>In this example, the <code>GameCharacter</code> class is the Originator, responsible for creating and restoring Mementos. The <code>GameCharacterMemento</code> class represents the Memento, capturing the state (health, mana, and position) of the Originator. The <code>GameCharacterCaretaker</code> class acts as the Caretaker, storing and retrieving Mementos.</p>
<p>We start with an initial state for the game character, create a Memento, and store it in the Caretaker. Then, we simulate some gameplay actions, such as taking damage, using mana, and moving the character. When we encounter a difficult enemy, we use the Caretaker to retrieve the initial Memento and restore the character to its previous state, effectively undoing the actions taken since the last checkpoint.</p>
<h2 id="heading-why-use-the-memento-pattern">Why Use the Memento Pattern?</h2>
<p>The Memento pattern offers several benefits:</p>
<ol>
<li><p><strong>Encapsulation</strong>: The pattern ensures that the internal state of an object is not exposed, maintaining encapsulation and information hiding principles.</p>
</li>
<li><p><strong>Separation of Concerns</strong>: The responsibilities of state capture, storage, and restoration are separated into different components, promoting loose coupling and code organization.</p>
</li>
<li><p><strong>Undo/Redo Support</strong>: The pattern facilitates implementing undo/redo functionality in applications, providing a familiar and expected user experience.</p>
</li>
<li><p><strong>State Rollback</strong>: In transaction-based systems or long-running processes, the Memento pattern enables rollbacks or checkpointing, ensuring data integrity and consistency.</p>
</li>
<li><p><strong>Versioning and Backups</strong>: The pattern can be used to capture and restore snapshots or versions of objects, enabling features like version control or system backups.</p>
</li>
</ol>
<p>However, it's important to consider potential drawbacks, such as increased memory usage for storing Mementos and the need for additional code complexity to implement the pattern effectively.</p>
<p>The Memento design pattern is a powerful tool for capturing and restoring object states, enabling features like undo/redo operations, checkpointing, and transaction rollbacks. By adhering to encapsulation principles and separating concerns, it provides a flexible and maintainable solution for managing object states in various applications, making our lives a little easier, one time capsule at a time!</p>
]]></content:encoded></item><item><title><![CDATA[Bridging the Gap: How Design Thinking and TDD Can Supercharge Your Development Process]]></title><description><![CDATA[Software development is a constant dance between creative problem-solving and rigorous implementation. While design thinking excels at the former, Test-Driven Development (TDD) shines in the latter. But what if we could leverage the strengths of both...]]></description><link>https://blog.yaffalab.com/bridging-the-gap-how-design-thinking-and-tdd-can-supercharge-your-development-process</link><guid isPermaLink="true">https://blog.yaffalab.com/bridging-the-gap-how-design-thinking-and-tdd-can-supercharge-your-development-process</guid><category><![CDATA[software development]]></category><category><![CDATA[design thinking]]></category><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[agile development]]></category><category><![CDATA[yaffalab]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Mon, 22 Apr 2024 07:00:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713514348657/cdeed14e-e855-44df-9110-268dc273e655.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software development is a constant dance between creative problem-solving and rigorous implementation. While design thinking excels at the former, Test-Driven Development (TDD) shines in the latter. But what if we could leverage the strengths of both to create a more robust and efficient development process?</p>
<p><strong>Design Thinking: Understanding the User</strong></p>
<p>Design thinking is an iterative user-centered approach to problem-solving. It emphasizes empathy, understanding user needs, and brainstorming creative solutions. Here's the basic framework:</p>
<ol>
<li><p><strong>Empathize:</strong> Research your target users, their pain points, and desired outcomes.</p>
</li>
<li><p><strong>Define:</strong> Clearly define the core problem you're trying to solve for the user.</p>
</li>
<li><p><strong>Ideate:</strong> Generate a wide range of potential solutions through brainstorming and prototyping.</p>
</li>
<li><p><strong>Prototype:</strong> Develop low-fidelity, quick prototypes to test your ideas with real users.</p>
</li>
<li><p><strong>Test:</strong> Gather feedback from users and iterate on your prototypes based on their responses.</p>
</li>
</ol>
<p>Design thinking ensures the final product is not just technically sound but also addresses the user's needs effectively.</p>
<p><strong>TDD: Building with Confidence</strong></p>
<p>TDD, on the other hand, focuses on the technical implementation. It's a development approach where you write failing tests <strong>before</strong> writing any code. Here's the core cycle:</p>
<ol>
<li><p><strong>Red:</strong> Write a unit test that currently fails because the code you'll be writing doesn't exist yet.</p>
</li>
<li><p><strong>Green:</strong> Write the minimal amount of code necessary to make the failing test pass.</p>
</li>
<li><p><strong>Refactor:</strong> Refactor your code to improve readability, maintainability, and overall design without breaking the existing tests.</p>
</li>
</ol>
<p>This cycle ensures your code is constantly validated and working as intended. TDD promotes clean code design, early bug detection, and a strong safety net for refactoring.</p>
<p><strong>The Magic Intersection: User-Centric Design Meets Reliable Code</strong></p>
<p>So how can we combine these seemingly disparate approaches? Here's the beauty:</p>
<ul>
<li><p><strong>TDD Informs Design Thinking:</strong> By writing failing tests first in TDD, you're essentially defining the desired behavior of your code. This aligns perfectly with the "Define" stage of design thinking. For example, imagine you're designing a feature for a music streaming app. A failing TDD test might specify that "pressing the play button should initiate playback of the selected song." This directly translates to a core user need: "users want to easily play music." By identifying these functionalities upfront, TDD ensures your solution addresses the core user needs identified earlier.</p>
</li>
<li><p><strong>Design Thinking Guides TDD:</strong> Design thinking helps break down complex features into smaller, user-focused functionalities. This translates perfectly to writing focused and well-defined TDD tests that target specific user interactions. Let's revisit the music streaming app example. Design thinking might reveal that users want to quickly access their liked songs. This translates to a focused TDD test ensuring the "liked songs playlist loads correctly when selected."</p>
</li>
<li><p><strong>Rapid Prototyping with Confidence:</strong> The low-fidelity prototypes used in design thinking can be quickly transformed into functional prototypes using code written with TDD. The existing tests ensure the core functionality works as expected (playing music, loading playlists), allowing you to focus on the user interface and experience in the prototype. Imagine a basic prototype with buttons and a song list. Since the TDD tests guarantee the underlying functionality, you can concentrate on refining the user interface without worrying about breaking the core playback functionality.</p>
</li>
<li><p><strong>Iterative Refinement:</strong> Both design thinking and TDD are inherently iterative. Design thinking emphasizes testing prototypes with users and iterating based on feedback. TDD's "Red-Green-Refactor" cycle encourages continuous improvement of the code through refactoring. This combined approach fosters a development process that is constantly adapting to user needs (through design thinking iterations) and technical realities (through TDD's focus on clean, tested code).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713513652218/4c8ecba8-0baa-42c2-b242-6a583d230ec1.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>The Takeaway: A More Powerful Approach</strong></p>
<p>By combining design thinking's user focus with TDD's code-centric approach, you create a development process that's both creative and reliable. You can confidently explore solutions that address user needs while ensuring the underlying code is well-structured, tested, and easy to maintain. This empowers developers to experiment more freely, leading to more innovative and user-centric software solutions.</p>
<p><strong>Additional Considerations</strong></p>
<p>While this approach offers numerous benefits, it's important to acknowledge potential challenges:</p>
<ul>
<li><p><strong>Learning Curve:</strong> Both design thinking and TDD require investment in learning and practice.</p>
</li>
<li><p><strong>Team Dynamics:</strong> Successful implementation might require adjustments in team collaboration between designers and developers.</p>
</li>
</ul>
<p>However, the potential rewards in terms of user satisfaction, development efficiency, and code quality make this a worthwhile pursuit for development teams.</p>
]]></content:encoded></item><item><title><![CDATA[The Gatekeeper: Unveiling the Proxy Design Pattern for Controlled Object Access]]></title><description><![CDATA[Imagine entering a high-security building. You wouldn't expect to waltz right in, right? Instead, you approach a security guard (the middleman) who verifies your credentials and grants access to specific areas. In software development, the Proxy desi...]]></description><link>https://blog.yaffalab.com/the-gatekeeper-unveiling-the-proxy-design-pattern-for-controlled-object-access</link><guid isPermaLink="true">https://blog.yaffalab.com/the-gatekeeper-unveiling-the-proxy-design-pattern-for-controlled-object-access</guid><category><![CDATA[yaffalab]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Ayoub Touba]]></dc:creator><pubDate>Fri, 19 Apr 2024 07:00:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713344167778/bc2864a8-ffa1-4c1b-b447-b68fe1605422.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine entering a high-security building. You wouldn't expect to waltz right in, right? Instead, you approach a security guard (the middleman) who verifies your credentials and grants access to specific areas. In software development, the Proxy design pattern plays a similar role. It acts as an intermediary between a client (your code) and an object, controlling access and adding functionalities.</p>
<p><strong>The Players:</strong></p>
<ul>
<li><p><strong>Proxy:</strong> This is the middleman, providing an interface similar to the real object but potentially with additional logic or restrictions.</p>
</li>
<li><p><strong>Real Subject:</strong> This is the actual object that the client ultimately wants to interact with.</p>
</li>
<li><p><strong>Client:</strong> This is any part of your code that needs to access the real object. The client interacts with the proxy instead of the real subject directly.</p>
</li>
</ul>
<p><strong>Why Use the Proxy Pattern?</strong></p>
<p>The Proxy pattern offers several advantages:</p>
<ul>
<li><p><strong>Controlled Access:</strong> You can restrict access to the real object, ensuring only authorized clients can interact with it. This is useful for protecting sensitive data or functionalities.</p>
</li>
<li><p><strong>Enhanced Functionality:</strong> The proxy can intercept requests to the real object and add additional functionalities before or after delegating the call. This might involve logging, security checks, or performance optimizations.</p>
</li>
<li><p><strong>Lazy Loading:</strong> The proxy can delay the creation of the real object until it's actually needed. This can improve performance, especially for expensive objects or those that are not always required.</p>
</li>
<li><p><strong>Hiding Complexity:</strong> The proxy can shield the client from the complexities of the real object's interface. This simplifies the client code and makes it more maintainable.</p>
</li>
</ul>
<p><strong>Real-World Use Cases:</strong></p>
<p>The Proxy pattern has various applications across software development:</p>
<ul>
<li><p><strong>Security:</strong> A proxy can be used to authenticate users before granting access to protected resources.</p>
</li>
<li><p><strong>Caching:</strong> A proxy can cache frequently accessed data from the real object, improving performance.</p>
</li>
<li><p><strong>Remote Objects:</strong> When dealing with remote objects located on different servers, a local proxy can handle communication details, hiding network complexities from the client.</p>
</li>
<li><p><strong>Virtual Proxies:</strong> In image processing, a virtual proxy can represent a large image without loading it into memory until it's displayed, saving resources.</p>
</li>
</ul>
<p><strong>Here's an example:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Subject Interface (What the proxy stands in for)</span>

<span class="hljs-keyword">interface</span> ExpensiveObject {
  loadData(): <span class="hljs-built_in">string</span>;
}

<span class="hljs-comment">// Real Subject (The actual object with potentially expensive operations)</span>

<span class="hljs-keyword">class</span> RealExpensiveObject <span class="hljs-keyword">implements</span> ExpensiveObject {
  <span class="hljs-keyword">private</span> data: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;

  loadData(): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.data) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Loading data from external source...'</span>);
      <span class="hljs-built_in">this</span>.data = <span class="hljs-string">'This is some expensive data!'</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.data;
  }
}

<span class="hljs-comment">// Proxy Class (The intermediary)</span>

<span class="hljs-keyword">class</span> ExpensiveObjectProxy <span class="hljs-keyword">implements</span> ExpensiveObject {
  <span class="hljs-keyword">private</span> realObject: ExpensiveObject;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.realObject = <span class="hljs-keyword">new</span> RealExpensiveObject();
  }

  loadData(): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.shouldLoad()) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.realObject.loadData();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Using cached data'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-string">'Cached data'</span>; <span class="hljs-comment">// Simulate cached data</span>
    }
  }

  <span class="hljs-keyword">private</span> shouldLoad(): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-comment">// Implement logic to determine if data needs to be loaded again</span>
    <span class="hljs-comment">// Here, we simulate a simple check based on a flag</span>
    <span class="hljs-keyword">return</span> !<span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'expensiveDataLoaded'</span>);
  }
}

<span class="hljs-comment">// Usage Example</span>

<span class="hljs-keyword">const</span> expensiveProxy = <span class="hljs-keyword">new</span> ExpensiveObjectProxy();

<span class="hljs-built_in">console</span>.log(expensiveProxy.loadData()); <span class="hljs-comment">// Output: Loading data from external source..., This is some expensive data!</span>
<span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'expensiveDataLoaded'</span>, <span class="hljs-string">'true'</span>);

<span class="hljs-built_in">console</span>.log(expensiveProxy.loadData()); <span class="hljs-comment">// Output: Using cached data</span>
</code></pre>
<p>In this example, the <code>ExpensiveObjectProxy</code> acts as an intermediary for the <code>RealExpensiveObject</code>. The proxy implements the same interface (<code>ExpensiveObject</code>) but controls access to the real object's <code>loadData</code> method. Here, the proxy introduces caching behavior. It checks if the data has already been loaded (simulated with <code>localStorage</code>) and avoids calling the potentially expensive <code>loadData</code> method on the real object if unnecessary.</p>
<p>This demonstrates the core idea of the Proxy pattern: providing a controlled interface to another object, potentially adding functionalities like access control, caching, or lazy loading.</p>
<p><strong>Conclusion:</strong></p>
<p>The Proxy design pattern provides a flexible way to manage access and enhance functionalities when working with objects in your code. By introducing a middleman (the proxy), you gain more control over object interactions and improve the overall maintainability and security of your software.</p>
]]></content:encoded></item></channel></rss>