<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Faizullah</title>
    <description>The latest articles on DEV Community by Faizullah (@faizullahpk).</description>
    <link>https://dev.to/faizullahpk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978324%2F61cd9b2b-b98f-4612-a912-29d048760cca.png</url>
      <title>DEV Community: Faizullah</title>
      <link>https://dev.to/faizullahpk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faizullahpk"/>
    <language>en</language>
    <item>
      <title>Building a Website for a Language Institute: Designing for Trust, Not Just Clicks</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:13:21 +0000</pubDate>
      <link>https://dev.to/faizullahpk/building-a-website-for-a-language-institute-designing-for-trust-not-just-clicks-gnc</link>
      <guid>https://dev.to/faizullahpk/building-a-website-for-a-language-institute-designing-for-trust-not-just-clicks-gnc</guid>
      <description>&lt;p&gt;&lt;em&gt;By Faiz Ullah — Full-Stack Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Most websites I build need to convert a visitor into a click — sign up, add to cart, download. &lt;strong&gt;Chinova Institute&lt;/strong&gt;, a Chinese-language institute offering classes, HSK exam prep, translation, interpretation, and visa consultancy, needed something different: it needed to convert a visitor into &lt;em&gt;trust&lt;/em&gt; before they'd ever pick up the phone.&lt;/p&gt;

&lt;p&gt;That difference shaped almost every decision I made building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Education Sites Sell Credibility, Not Just Information
&lt;/h2&gt;

&lt;p&gt;An e-commerce site can lean on product photos and price tags to do the persuading. An institute site has to persuade with something harder to fake: &lt;strong&gt;evidence that the people behind it are real and qualified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's why the site is built around a dedicated &lt;strong&gt;founder page&lt;/strong&gt; rather than burying credentials in an "About Us" paragraph. A prospective student deciding whether to commit to months of language classes wants to know &lt;em&gt;who&lt;/em&gt; is teaching, what their background is, and why they're credible — not just a feature list of "course types offered."&lt;/p&gt;




&lt;h2&gt;
  
  
  Structuring Content for Multiple, Different Intents
&lt;/h2&gt;

&lt;p&gt;A single visitor to an institute site might be there for any of several very different reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A student wanting weekly Chinese classes&lt;/li&gt;
&lt;li&gt;Someone prepping specifically for the HSK proficiency exam&lt;/li&gt;
&lt;li&gt;A business needing a one-off translation or interpreter&lt;/li&gt;
&lt;li&gt;Someone navigating a visa process tied to travel or work in China&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cramming all four into one generic "Services" list undersells each one. Instead, I treated them as &lt;strong&gt;distinct service categories&lt;/strong&gt;, each described in its own terms — a language learner and a business needing certified translation are evaluating completely different things, and the copy needs to speak to each separately rather than using one generic pitch for everyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Open Graph Tags Matter More Than People Think
&lt;/h2&gt;

&lt;p&gt;Institute and consultancy websites get shared constantly — in WhatsApp groups, Facebook community pages, forwarded links between friends deciding where to study. If the link preview that shows up is broken or generic, that referral — often the highest-trust kind of lead there is — gets wasted.&lt;/p&gt;

&lt;p&gt;I made sure every key page had proper &lt;strong&gt;Open Graph metadata&lt;/strong&gt; with dedicated preview images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"og-image.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Chinova Institute — Learn Chinese in Islamabad"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The founder page even gets its &lt;strong&gt;own&lt;/strong&gt; preview image (&lt;code&gt;og-founder.png&lt;/code&gt;) distinct from the main site — because when someone shares "check out this guy's profile," the preview should show the person, not a generic logo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance as a Trust Signal
&lt;/h2&gt;

&lt;p&gt;For a site like this, I skipped frameworks and build tooling entirely — plain HTML, CSS, and a small amount of vanilla JavaScript. Partly for simplicity, but also because &lt;strong&gt;a slow-loading institute site quietly undermines its own credibility.&lt;/strong&gt; If the page that's supposed to convince someone you're a serious, professional operation takes three seconds to paint, that's working against you before a single word of copy is even read.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell Someone Building a Service/Consultancy Site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead with the people, not just the service list.&lt;/strong&gt; Trust-based businesses convert on credibility, not features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't force unrelated service types into one generic pitch.&lt;/strong&gt; Separate audiences need separate framing, even on the same page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat link-preview metadata as part of the conversion funnel&lt;/strong&gt;, not an afterthought — shared links are often your highest-trust traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the stack as light as the content allows.&lt;/strong&gt; Speed is itself a credibility signal for this category of site.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structure&lt;/td&gt;
&lt;td&gt;Semantic HTML5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Custom CSS3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interactivity&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO&lt;/td&gt;
&lt;td&gt;Sitemap, robots.txt, full Open Graph metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Full-Stack Developer · Founder of &lt;a href="https://dgtechnology.online" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk/chinova-institute" rel="noopener noreferrer"&gt;github.com/faizullahpk/chinova-institute&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building a site for a trust-based business — education, consultancy, or professional services? Follow along — I write about the practical decisions behind real client websites.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>showdev</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Full E-Commerce Store With Zero Frameworks — Here's What I Learned</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:10:23 +0000</pubDate>
      <link>https://dev.to/faizullahpk/i-built-a-full-e-commerce-store-with-zero-frameworks-heres-what-i-learned-3amp</link>
      <guid>https://dev.to/faizullahpk/i-built-a-full-e-commerce-store-with-zero-frameworks-heres-what-i-learned-3amp</guid>
      <description>&lt;p&gt;&lt;em&gt;By Faiz Ullah — Full-Stack Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every e-commerce build I'd done before reached for React almost by reflex. So when I built &lt;strong&gt;JKMart&lt;/strong&gt; — a grocery and pansar (spice/herb) store — I deliberately went the other way: &lt;strong&gt;no framework, no build step, no dependencies.&lt;/strong&gt; Just HTML, CSS, and vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;It forced me to relearn fundamentals that frameworks quietly hide from you. Here's what stood out.&lt;/p&gt;




&lt;h2&gt;
  
  
  State Management Without a Framework
&lt;/h2&gt;

&lt;p&gt;React trains you to think "state lives in a component, re-render on change." Without that abstraction, you have to build the same discipline by hand. I centralized everything — products, categories, cart, site settings — into one structured data store, and treated every UI update as &lt;em&gt;"read from the store, write back to the store, re-render the affected DOM section."&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt;
  &lt;span class="na"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nf"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;renderCart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// explicit, not automatic&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference from React: &lt;strong&gt;nothing happens automatically.&lt;/strong&gt; Every single re-render is a function you call yourself. It's more typing, but it also means there's no mystery about &lt;em&gt;why&lt;/em&gt; something on screen updated — you can trace it line by line.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cart Has to Survive a Refresh
&lt;/h2&gt;

&lt;p&gt;A cart that empties itself when the customer accidentally refreshes the page is a lost sale. With no backend session and no framework state layer, persistence comes down to &lt;code&gt;localStorage&lt;/code&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small thing, but it's the kind of detail that separates a demo from something that actually works the way a shopper expects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Search That Feels Instant
&lt;/h2&gt;

&lt;p&gt;Live product search needs to filter as the user types, not after they hit Enter. The naive version re-filters the &lt;em&gt;entire&lt;/em&gt; product list on every keystroke, which is fine for a small catalog but teaches a bad habit. I built it to debounce input and search across multiple fields (name, category, tags) at once, so it stayed fast and felt forgiving of partial or imprecise queries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Depth Without a 3D Library
&lt;/h2&gt;

&lt;p&gt;I wanted product cards to feel tactile — a subtle tilt that responds to the cursor, the kind of detail that makes a static catalog feel alive. Rather than reaching for a 3D library, this is just trigonometry on mouse position relative to the card's center:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`perspective(800px) rotateY(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;deg) rotateX(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;deg)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependency, no bundle size cost — just a few lines of CSS transform math.&lt;/p&gt;




&lt;h2&gt;
  
  
  An Admin Panel That's Also Just HTML
&lt;/h2&gt;

&lt;p&gt;The admin panel — product management, category editing, site settings — is built the exact same way as the storefront: vanilla JS reading and writing the same data store, just rendering a different UI on top of it. There's no separate "admin framework." It's the same toolkit, pointed at a different audience.&lt;/p&gt;




&lt;h2&gt;
  
  
  When &lt;em&gt;Not&lt;/em&gt; Reaching for a Framework Actually Makes Sense
&lt;/h2&gt;

&lt;p&gt;This isn't an argument that frameworks are bad — for an app with deep nested state and dozens of interacting components, React earns its complexity. But for a focused storefront like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero build step&lt;/strong&gt; means the entire site is editable and previewable by just opening the file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies&lt;/strong&gt; means no &lt;code&gt;npm audit&lt;/code&gt; surprises, no version upgrades breaking things a year later&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant load&lt;/strong&gt; — there's no JS bundle to download and parse before the page becomes interactive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a project of this scope, that trade-off was worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell Someone Building Their First Vanilla JS Project
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralize your state into one object&lt;/strong&gt;, even without a framework forcing you to. Scattered global variables are where bugs breed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persist anything the user would be annoyed to lose&lt;/strong&gt; — cart contents, form drafts — to &lt;code&gt;localStorage&lt;/code&gt; immediately, not just on submit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debounce expensive operations&lt;/strong&gt; like search filtering, even in a small app. It's a habit worth having by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume you need a library for interaction polish.&lt;/strong&gt; A lot of "wow" details are a few lines of math.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structure&lt;/td&gt;
&lt;td&gt;Semantic HTML5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Custom CSS3, gradients &amp;amp; animations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logic&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript (ES6+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Frontend Developer · Full-Stack Engineer · Founder of &lt;a href="https://dgtechnology.online" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk/ecommerce-store-demo" rel="noopener noreferrer"&gt;github.com/faizullahpk/ecommerce-store-demo&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curious about when to reach for a framework and when not to? Follow along — I write about practical, real-world full-stack decisions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>learning</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Multi-Vendor Marketplace From Scratch: Lessons From 30,000 Lines of React</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Fri, 19 Jun 2026 07:52:04 +0000</pubDate>
      <link>https://dev.to/faizullahpk/building-a-multi-vendor-marketplace-from-scratch-lessons-from-30000-lines-of-react-22ch</link>
      <guid>https://dev.to/faizullahpk/building-a-multi-vendor-marketplace-from-scratch-lessons-from-30000-lines-of-react-22ch</guid>
      <description>&lt;h1&gt;
  
  
  Building a Multi-Vendor Marketplace From Scratch: Lessons From 30,000 Lines of React
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Faiz Ullah — Full-Stack Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Most "build an e-commerce site" tutorials stop at a product list and a cart. They don't deal with the actual hard part: &lt;strong&gt;three different types of humans — customers, sellers, and admins — all needing their own secure space inside the same app, talking to each other in real time, without ever stepping on each other's data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what I set out to build with &lt;strong&gt;Ecommerce&lt;/strong&gt;, a multi-vendor marketplace that grew to over 30,000 lines of React. Here's what I learned engineering it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Challenge: Three Apps in One
&lt;/h2&gt;

&lt;p&gt;A single-vendor store is one application. A &lt;em&gt;multi-vendor&lt;/em&gt; marketplace is really three applications sharing a database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customers&lt;/strong&gt; browse, buy, and chat with sellers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sellers&lt;/strong&gt; manage their own storefront, fulfill orders, and request payouts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admins&lt;/strong&gt; oversee everyone — approving sellers, resolving disputes, releasing payouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The temptation is to bolt all three onto one &lt;code&gt;App.js&lt;/code&gt; with a bunch of &lt;code&gt;if (userType === 'admin')&lt;/code&gt; checks scattered everywhere. That gets unmanageable fast. Instead, I built three &lt;strong&gt;fully independent authentication systems&lt;/strong&gt;, each with its own protected route guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedCustomerRoute&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedSellerRoute&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedAdminRoute&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each guard checks its own session state independently. A seller session can never accidentally leak into the admin view, even if someone tries to manipulate the URL directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-Time Chat Without a Custom Server
&lt;/h2&gt;

&lt;p&gt;I wanted buyers and sellers to message each other live — no page refresh, no polling. Rather than standing up a WebSocket server, I leaned on &lt;strong&gt;Firestore's real-time listeners&lt;/strong&gt;, which turned out to be the right call for a project this size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messagesRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// UI updates instantly as new messages arrive&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single pattern powers chat, unread-message counts, and live presence — all without me managing a single socket connection.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Presence Problem
&lt;/h2&gt;

&lt;p&gt;Showing whether a seller is "online" sounds trivial until you actually build it. A simple &lt;code&gt;isOnline: true&lt;/code&gt; flag breaks the moment someone closes their laptop without logging out — they stay "online" forever.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;heartbeat pattern&lt;/strong&gt;: the seller's client writes a &lt;code&gt;lastSeen&lt;/code&gt; timestamp every few seconds while the tab is active, and stops the moment the tab closes or loses visibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;stopHeartbeat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;startHeartbeat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone viewing the seller's profile just checks: &lt;em&gt;was the last heartbeat recent?&lt;/em&gt; No server-side cron job needed, no stale "online" ghosts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Media at Scale: Don't Make Your Database Hold Images
&lt;/h2&gt;

&lt;p&gt;Early on I made the rookie mistake of storing image data directly. That doesn't scale — Firestore documents have size limits, and serving large base64 blobs kills load times.&lt;/p&gt;

&lt;p&gt;The fix was routing all uploads through &lt;strong&gt;Cloudinary&lt;/strong&gt;, using &lt;strong&gt;unsigned upload presets&lt;/strong&gt; so the API secret never has to live in client-side code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upload_preset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cloudinaryConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadPreset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.cloudinary.com/v1_1/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/upload`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudinary then handles resizing, format conversion, and CDN delivery — the database only ever stores a URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Payout Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Letting sellers &lt;em&gt;earn&lt;/em&gt; money is the easy half. Letting them &lt;em&gt;withdraw&lt;/em&gt; it safely is the half that actually matters. I built a dedicated &lt;code&gt;WithdrawalRequestsManager&lt;/code&gt; so that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A seller requests a withdrawal&lt;/li&gt;
&lt;li&gt;The request enters a &lt;strong&gt;pending&lt;/strong&gt; queue — funds are not released automatically&lt;/li&gt;
&lt;li&gt;An admin reviews and approves it manually before money moves&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This manual checkpoint is deliberate. Automating payouts sounds efficient until the first fraud attempt — a human review step at the money boundary is the cheapest fraud prevention you can build.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell Someone Building Their First Marketplace
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate your three user types from day one.&lt;/strong&gt; Retrofitting role isolation onto a single auth system later is painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use your database's real-time features before reaching for a custom server.&lt;/strong&gt; Firestore's listeners replaced what would have been a whole separate real-time service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never store binary media where structured data lives.&lt;/strong&gt; Offload it to dedicated media infrastructure immediately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put a human checkpoint wherever money actually leaves the system.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React, React Router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Material UI (MUI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Firebase Firestore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Firebase Authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realtime DB&lt;/td&gt;
&lt;td&gt;Firebase Realtime Database (presence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;Cloudinary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Full-Stack Developer · Founder of &lt;a href="https://dgtechnology.online" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk/multivendor-marketplace" rel="noopener noreferrer"&gt;github.com/faizullahpk/multivendor-marketplace&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're building something with multiple user roles and real-time data, I'd love to hear about it — follow along for more on shipping real-world full-stack systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactnative</category>
      <category>refactorit</category>
    </item>
    <item>
      <title>Building an E-Commerce Store That Installs Like an App (Without a Native App)</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Fri, 19 Jun 2026 07:50:19 +0000</pubDate>
      <link>https://dev.to/faizullahpk/building-an-e-commerce-store-that-installs-like-an-app-without-a-native-app-5515</link>
      <guid>https://dev.to/faizullahpk/building-an-e-commerce-store-that-installs-like-an-app-without-a-native-app-5515</guid>
      <description>&lt;p&gt;&lt;em&gt;By Faiz Ullah — Full-Stack Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every e-commerce founder eventually asks the same question: "Should we build a native app?" Native apps are expensive, slow to update, and most users won't bother installing one for a store they're not sure they'll buy from twice.&lt;/p&gt;

&lt;p&gt;When I built &lt;strong&gt;Yala Bazaar&lt;/strong&gt; — a multi-category electronics store — I went a different route: build a &lt;strong&gt;Progressive Web App&lt;/strong&gt; that gets 90% of the native-app experience (installable icon, offline support, app-like speed) for a fraction of the engineering cost.&lt;/p&gt;

&lt;p&gt;Here's how it actually works under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes a Website "Installable"
&lt;/h2&gt;

&lt;p&gt;The browser doesn't show an "Add to Home Screen" prompt for just any site. Two specific pieces unlock it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A web manifest&lt;/strong&gt; describing the app's identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yala Bazaar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icon-512.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512x512"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. A registered service worker&lt;/strong&gt; — a script that runs independently of any open tab and can intercept network requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;install&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CORE_ASSETS&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once both are present, browsers offer to install the site like a real app — same home-screen icon, same full-screen launch, no browser chrome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying Without Downtime (The Part Most Tutorials Skip)
&lt;/h2&gt;

&lt;p&gt;A live store can't go down mid-deploy — every minute offline is lost sales. I deployed Yala Bazaar to &lt;strong&gt;Cloudflare Pages&lt;/strong&gt;, which solves two problems most people don't think about until they hit them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic deploys.&lt;/strong&gt; The new version builds entirely on the side. Only once it's 100% ready does Cloudflare flip the live URL to it instantly. Visitors only ever see the fully-old or fully-new version — never a half-deployed broken state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content-addressed uploads.&lt;/strong&gt; Every file is fingerprinted. Change one HTML file, and only that one file re-uploads — not the whole site. A small content fix deploys in seconds, not minutes.&lt;/p&gt;

&lt;p&gt;This matters more than it sounds like it should: it's the difference between deploying fearlessly throughout the day versus batching changes because every deploy feels risky.&lt;/p&gt;




&lt;h2&gt;
  
  
  Live Chat Without Standing Up Chat Infrastructure
&lt;/h2&gt;

&lt;p&gt;Customers abandon carts when they have a question and nobody to ask. I wanted live chat support, but didn't want to run a dedicated chat server for what's fundamentally a small-to-medium store.&lt;/p&gt;

&lt;p&gt;The answer was the same pattern I'd use again and again: &lt;strong&gt;Firebase's real-time listeners&lt;/strong&gt; as a lightweight pub/sub system. A customer's message writes to Firestore; the support dashboard listens for new documents and the message appears with no polling and no custom backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;renderNewMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;docChanges&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a store at this scale, this replaces what would otherwise be a whole WebSocket service — for free, with infrastructure I don't have to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separating "Browse" From "Manage"
&lt;/h2&gt;

&lt;p&gt;A storefront has three very different audiences hitting it: shoppers browsing products, sellers managing their own listings, and admins running the whole operation. I kept these as &lt;strong&gt;separate page bundles&lt;/strong&gt; (&lt;code&gt;seller.html&lt;/code&gt;, &lt;code&gt;admin.html&lt;/code&gt;) rather than one giant single-page app trying to be everything to everyone.&lt;/p&gt;

&lt;p&gt;The benefit is concrete: a shopper's bundle never has to download seller/admin code they'll never use, keeping the actual storefront — the page that needs to load fastest — as lean as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell Someone Building Their First Online Store
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't default to a native app.&lt;/strong&gt; A well-built PWA covers most of the value at a fraction of the cost and maintenance burden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a host with atomic, fingerprinted deploys.&lt;/strong&gt; It changes how confidently you can ship small fixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use your database's real-time features for chat/support before building custom infrastructure.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep your storefront bundle lean&lt;/strong&gt; — don't make shoppers download code meant for sellers or admins.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;HTML5, CSS3, Vanilla JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installability&lt;/td&gt;
&lt;td&gt;Web Manifest + Service Worker (PWA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth &amp;amp; Chat&lt;/td&gt;
&lt;td&gt;Firebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Cloudflare Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Full-Stack Developer · Founder of &lt;a href="https://dgtechnology.online" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk/yala-bazaar-store" rel="noopener noreferrer"&gt;github.com/faizullahpk/yala-bazaar-store&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thinking about whether your next project needs a native app or not? Follow along — I write about practical, real-world decisions in shipping full-stack products.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>css</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Clean Architecture on Android: What I Learned Building a Wallpaper App From Scratch</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Fri, 19 Jun 2026 07:49:07 +0000</pubDate>
      <link>https://dev.to/faizullahpk/clean-architecture-on-android-what-i-learned-building-a-wallpaper-app-from-scratch-272m</link>
      <guid>https://dev.to/faizullahpk/clean-architecture-on-android-what-i-learned-building-a-wallpaper-app-from-scratch-272m</guid>
      <description>&lt;p&gt;&lt;em&gt;By Faiz Ullah — Android Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;A wallpaper app sounds simple: fetch images, show a grid, let the user tap "set wallpaper." I built exactly that — &lt;strong&gt;WallReddit&lt;/strong&gt;, a native Android app pulling images from Reddit — and used it as an excuse to do something I'd wanted to get right for a while: a genuinely clean, layered architecture instead of the "everything in one Activity" approach most small apps fall into.&lt;/p&gt;

&lt;p&gt;Here's what building it taught me.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Bother With Clean Architecture for "Just a Wallpaper App"
&lt;/h2&gt;

&lt;p&gt;It's tempting to skip architecture for a small app. But the moment you add offline favorites, a download manager, NSFW filtering, and a background auto-rotation feature, an unstructured app turns into a maze of interdependent code that's terrifying to touch.&lt;/p&gt;

&lt;p&gt;So I split the app into three layers with one hard rule: &lt;strong&gt;dependencies only point inward.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ui/        → talks to → domain/        → talks to → data/
(Compose)                (use cases,                (Retrofit, Room,
                          pure Kotlin)                DataStore)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payoff: the &lt;strong&gt;domain layer has zero Android imports.&lt;/strong&gt; No &lt;code&gt;Context&lt;/code&gt;, no &lt;code&gt;Activity&lt;/code&gt;, nothing Android-specific — just plain Kotlin business logic. That means it's trivially testable and would survive a total UI rewrite untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reactive Settings Without the Boilerplate
&lt;/h2&gt;

&lt;p&gt;Every setting in the app — dark mode, AMOLED mode, grid density, NSFW visibility — needed to update the UI instantly when changed, from anywhere in the app. Instead of manually plumbing callbacks everywhere, I exposed every preference as a Kotlin &lt;code&gt;Flow&lt;/code&gt; backed by Jetpack DataStore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isDarkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;DARK_MODE_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any screen can &lt;code&gt;collectAsState()&lt;/code&gt; on this and the UI updates the instant the value changes anywhere else in the app — no event bus, no manual notification, no stale state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fighting Reddit's Anti-Scraping Defenses
&lt;/h2&gt;

&lt;p&gt;Reddit's public JSON endpoints are great until you hit their CDN's bot protection — a default Retrofit client gets &lt;code&gt;403 Forbidden&lt;/code&gt; almost immediately. The fix is straightforward but easy to miss: send a real browser &lt;code&gt;User-Agent&lt;/code&gt; and proper timeout/retry configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"WallReddit/1.0 (Android)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a one-line fix, but without it the entire app silently fails to load any content — the kind of bug that's invisible until you actually test against the real network.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wallpapers Look Wrong Without Real Math
&lt;/h2&gt;

&lt;p&gt;The naive way to "set a wallpaper" is to just hand the bitmap to Android's &lt;code&gt;WallpaperManager&lt;/code&gt; and hope for the best. The result: stretched, cropped, or letterboxed images depending on the device's aspect ratio.&lt;/p&gt;

&lt;p&gt;The actual fix is computing the transform yourself before handing off the bitmap — comparing the image's aspect ratio to the screen's, then applying a &lt;code&gt;Matrix&lt;/code&gt; for center-crop or fit-to-screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;scale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screenWidth&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;bitmapWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;screenHeight&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;bitmapHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the difference between an app that &lt;em&gt;looks&lt;/em&gt; like an amateur project and one that looks production-grade — and it's invisible work nobody notices unless you get it wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background Work That Doesn't Drain the Battery
&lt;/h2&gt;

&lt;p&gt;Auto-rotating wallpapers on a schedule means running work when the app isn't even open. Android's background execution limits make naive approaches (a &lt;code&gt;Service&lt;/code&gt; that just runs forever) both unreliable and battery-hostile.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WorkManager&lt;/code&gt; is the right tool here specifically because it &lt;strong&gt;respects Doze mode and battery optimization automatically&lt;/strong&gt; — your job runs reliably without you having to reinvent Android's power management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PeriodicWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AutoWallpaperWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HOURS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConstraints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setRequiresBatteryNotLow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I'd Tell Someone Building Their Second Android App
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep your domain layer Android-free.&lt;/strong&gt; It's the single highest-leverage architectural decision you can make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expose settings as reactive streams&lt;/strong&gt;, not getter functions. Future-you will thank present-you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your networking against the real API&lt;/strong&gt;, not a mock — anti-bot measures only show up in production traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;WorkManager&lt;/code&gt; for anything recurring.&lt;/strong&gt; Don't fight the OS's power management; work with it.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Jetpack Compose, Material 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;MVVM + Clean Architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Networking&lt;/td&gt;
&lt;td&gt;Retrofit + OkHttp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local storage&lt;/td&gt;
&lt;td&gt;Room, DataStore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;Paging 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background work&lt;/td&gt;
&lt;td&gt;WorkManager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images/Video&lt;/td&gt;
&lt;td&gt;Coil, Media3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Android Developer · Full-Stack Engineer · Founder of &lt;a href="https://dgtechnology.online" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk/wallpaper-app-android" rel="noopener noreferrer"&gt;github.com/faizullahpk/wallpaper-app-android&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building something on Android and hitting architecture questions? Follow along — I write about real-world mobile and full-stack engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>api</category>
      <category>socket</category>
      <category>reddit</category>
    </item>
    <item>
      <title>How I Built a Real-Time Multiplayer Game with Socket.IO, Firebase &amp; Pakistani Payment Gateways</title>
      <dc:creator>Faizullah</dc:creator>
      <pubDate>Wed, 10 Jun 2026 20:03:49 +0000</pubDate>
      <link>https://dev.to/faizullahpk/how-i-built-a-real-time-multiplayer-game-with-socketio-firebase-pakistani-payment-gateways-426b</link>
      <guid>https://dev.to/faizullahpk/how-i-built-a-real-time-multiplayer-game-with-socketio-firebase-pakistani-payment-gateways-426b</guid>
      <description>&lt;h1&gt;
  
  
  How I Built a Real-Time Multiplayer Game with Socket.IO, Firebase &amp;amp; Pakistani Payment Gateways
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Faiz Ullah — Full-Stack Developer &amp;amp; Founder of DG Technology&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;When people think of multiplayer games, they imagine big studios and huge teams. But a real-time, money-handling, cheat-proof multiplayer platform can be built by one engineer who understands the architecture deeply. This is the story of &lt;strong&gt;Ludo Battle&lt;/strong&gt; — a real-time multiplayer Ludo tournament platform I built end-to-end, from the WebSocket game engine to the Android APK to the payment integration.&lt;/p&gt;

&lt;p&gt;I'll walk through the hard parts, the decisions that mattered, and the lessons that apply to &lt;em&gt;any&lt;/em&gt; real-time application — not just games.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Build a platform where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 to 4 players play live Ludo matches with sub-second synchronization&lt;/li&gt;
&lt;li&gt;Players deposit and withdraw real money through local payment gateways&lt;/li&gt;
&lt;li&gt;Nobody can cheat — not the dice, not the moves, not the outcomes&lt;/li&gt;
&lt;li&gt;It runs on the web &lt;strong&gt;and&lt;/strong&gt; as a native Android app&lt;/li&gt;
&lt;li&gt;It works on Pakistani mobile networks, where strict NATs and unstable connections are the norm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last constraint shaped a lot of decisions. Building for ideal conditions is easy. Building for real-world 4G in Pakistan is the actual engineering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decision #1: The Server Owns Everything
&lt;/h2&gt;

&lt;p&gt;The single most important principle in any real-money game: &lt;strong&gt;the client is a renderer, never a decision-maker.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the browser decides what the dice rolled, a cheater opens DevTools and rolls a six every time. So in Ludo Battle, the &lt;strong&gt;server is the source of truth&lt;/strong&gt; for every piece of game state. The client sends &lt;em&gt;intentions&lt;/em&gt; ("I want to roll", "I want to move this token"), and the server decides what actually happens.&lt;/p&gt;

&lt;p&gt;Here's the dice roll — it lives on the server and uses Node's cryptographically secure random generator, not &lt;code&gt;Math.random()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomInt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rollDice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;randomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 1–6, cryptographically secure&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; is predictable and can be exploited. &lt;code&gt;crypto.randomInt&lt;/code&gt; cannot. For real money, this difference matters.&lt;/p&gt;

&lt;p&gt;Every move is validated the same way: when a player sends &lt;code&gt;game:move&lt;/code&gt;, the server computes the &lt;strong&gt;legal moves&lt;/strong&gt; from the current board state and rejects anything illegal before applying it. The client literally cannot make an illegal or impossible move stick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decision #2: A Clean Real-Time Event Model
&lt;/h2&gt;

&lt;p&gt;The entire live experience runs over &lt;strong&gt;Socket.IO&lt;/strong&gt;. I designed the events around clear namespaces so the codebase stays readable as it grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;room:create   room:join   room:leave   room:spectate
game:roll     game:move
chat:send     chat:msg
voice:join    voice:signal   voice:leave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rooms map directly to Socket.IO rooms, so broadcasting game state or chat to exactly the right players is a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`room:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chat:msg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure means game logic, chat, and voice are cleanly separated but share the same connection — no extra sockets, no wasted overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decision #3: Voice Chat That Survives Real Networks
&lt;/h2&gt;

&lt;p&gt;I wanted players to talk during games. The naive approach — routing audio through the server — is expensive and laggy. Instead I used &lt;strong&gt;peer-to-peer WebRTC&lt;/strong&gt;, where the audio flows directly between players and the server only helps them find each other (signaling).&lt;/p&gt;

&lt;p&gt;The catch: most Pakistani mobile users are behind &lt;strong&gt;strict NATs&lt;/strong&gt; that block direct P2P connections. The fix is a &lt;strong&gt;TURN server&lt;/strong&gt; that relays audio when a direct connection isn't possible. The app fetches TURN credentials per session and falls back gracefully:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Try direct connection (STUN)&lt;/li&gt;
&lt;li&gt;If blocked → relay through TURN&lt;/li&gt;
&lt;li&gt;Either way → players can talk&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the difference between voice chat that "works on my machine" and voice chat that works for a farmer on a 4G connection in South Punjab.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decision #4: Payments Without the Liability
&lt;/h2&gt;

&lt;p&gt;Handling money means handling risk. I deliberately chose a &lt;strong&gt;hosted-redirect&lt;/strong&gt; payment flow for JazzCash and EasyPaisa so that &lt;strong&gt;no card or wallet credentials ever touch my server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User taps "Deposit"&lt;/li&gt;
&lt;li&gt;My backend builds a &lt;strong&gt;signed&lt;/strong&gt; payload and returns it&lt;/li&gt;
&lt;li&gt;The user is redirected to JazzCash/EasyPaisa's own secure page to approve&lt;/li&gt;
&lt;li&gt;The gateway sends a &lt;strong&gt;callback&lt;/strong&gt; to my server&lt;/li&gt;
&lt;li&gt;My server &lt;strong&gt;verifies the HMAC signature&lt;/strong&gt; before crediting a single rupee&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That HMAC verification step is critical — it's how the server knows a callback genuinely came from the gateway and wasn't forged by someone trying to fake a deposit. Skipping it is how platforms get drained.&lt;/p&gt;

&lt;p&gt;I also built a &lt;strong&gt;test mode&lt;/strong&gt;: when no gateway credentials are configured, deposits credit instantly. This let me build and test the entire economy locally without touching real money or waiting on merchant approval.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Decision #5: One Codebase, Two Platforms
&lt;/h2&gt;

&lt;p&gt;The frontend is &lt;strong&gt;React + TypeScript + Vite&lt;/strong&gt;. To ship it as a native Android app without rewriting everything, I used &lt;strong&gt;Capacitor&lt;/strong&gt;, which wraps the web build into a real APK that I can distribute directly — no Play Store gatekeeping required.&lt;/p&gt;

&lt;p&gt;One important real-world gotcha: &lt;strong&gt;Android blocks insecure WebSocket connections.&lt;/strong&gt; The app &lt;em&gt;must&lt;/em&gt; talk to an HTTPS backend or the sockets silently fail in the APK. Discovering and solving that kind of platform-specific issue is where real shipping experience comes from.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trust nothing from the client.&lt;/strong&gt; This one principle prevents an entire category of exploits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design for the worst network, not the best.&lt;/strong&gt; Building for Pakistani 4G made the app rock-solid everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security isn't a feature you add later&lt;/strong&gt; — &lt;code&gt;crypto.randomInt&lt;/code&gt;, HMAC verification, and server-authoritative logic had to be in the foundation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns scales.&lt;/strong&gt; Clean event namespaces and layered logic kept a complex real-time system maintainable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Stack, Summarized
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React, TypeScript, Vite, Tailwind, Zustand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Node.js, Express, Socket.IO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Firebase Phone OTP + JWT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;JazzCash, EasyPaisa (HMAC-verified)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice&lt;/td&gt;
&lt;td&gt;WebRTC + TURN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile&lt;/td&gt;
&lt;td&gt;Capacitor → Android APK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Ludo Battle taught me that the gap between a "demo" and a "product" is almost entirely in the parts users never see: the anti-cheat, the payment verification, the network resilience. Anyone can render a game board. Making it fair, secure, and reliable on real-world networks is the actual work.&lt;/p&gt;

&lt;p&gt;If you're building something real-time, money-handling, or mobile — or you just want to talk architecture — I'd love to connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faiz Ullah&lt;/strong&gt;&lt;br&gt;
Full-Stack Developer · Cybersecurity Engineer · Founder of &lt;a href="https://dgtechnology.ae" rel="noopener noreferrer"&gt;DG Technology&lt;/a&gt;&lt;br&gt;
🌐 &lt;a href="https://faizullah.pk" rel="noopener noreferrer"&gt;faizullah.pk&lt;/a&gt; · 📧 &lt;a href="mailto:work@faizullah.pk"&gt;work@faizullah.pk&lt;/a&gt; · 💻 &lt;a href="https://github.com/faizullahpk" rel="noopener noreferrer"&gt;github.com/faizullahpk&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, follow me here and on GitHub — I write about real-world full-stack engineering, building for emerging markets, and shipping products solo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>node</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
