<?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: Fahad Azhar</title>
    <description>The latest articles on DEV Community by Fahad Azhar (@fahadazhar1).</description>
    <link>https://dev.to/fahadazhar1</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%2F4228610%2F257097f4-a533-4be4-869f-054dd70d53b1.png</url>
      <title>DEV Community: Fahad Azhar</title>
      <link>https://dev.to/fahadazhar1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fahadazhar1"/>
    <language>en</language>
    <item>
      <title>I'm one engineer with an AI team. Here's how I ship agency-sized work alone</title>
      <dc:creator>Fahad Azhar</dc:creator>
      <pubDate>Thu, 05 Nov 2026 15:38:09 +0000</pubDate>
      <link>https://dev.to/fahadazhar1/im-one-engineer-with-an-ai-team-heres-how-i-ship-agency-sized-work-alone-58p2</link>
      <guid>https://dev.to/fahadazhar1/im-one-engineer-with-an-ai-team-heres-how-i-ship-agency-sized-work-alone-58p2</guid>
      <description>&lt;p&gt;On 5 November I started the day without a list of article topics. By the end of it, four researched articles were live on my site, each with its own interactive tool, social image, sitemap entry and a Google indexing request.&lt;/p&gt;

&lt;p&gt;I didn't write them faster. I ran them the way an agency runs a project: research, writing, review and release, with several people working at once. The difference is that most of those people are AI agents, and I'm the one who decides, checks and signs off.&lt;/p&gt;

&lt;p&gt;This is how that actually works, including the parts that went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day, in numbers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Research:&lt;/strong&gt; &lt;strong&gt;3&lt;/strong&gt; agents in parallel, one each for payment gateways, ERP/courier integrations and headless/dashboards. Each made &lt;strong&gt;40–46&lt;/strong&gt; searches and page reads in about &lt;strong&gt;3.5 minutes&lt;/strong&gt;. Between them they came back with &lt;strong&gt;26&lt;/strong&gt; ranked problems, with sources for the claims.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing:&lt;/strong&gt; &lt;strong&gt;4&lt;/strong&gt; agents in parallel, one per article. Each had to read my existing posts first, match their components and tone, verify every fact against primary sources, and list what it dropped as unverifiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shipping:&lt;/strong&gt; I integrated the four posts, built, checked every page at &lt;strong&gt;390px&lt;/strong&gt; and &lt;strong&gt;1440px&lt;/strong&gt;, fixed what I found and deployed. The same day I submitted &lt;strong&gt;4&lt;/strong&gt; URLs to Google.&lt;/p&gt;

&lt;p&gt;Earlier that week I used the same setup to build a &lt;strong&gt;16-page&lt;/strong&gt; store-and-admin template: one agent laid the foundation (design tokens, layout, data), then &lt;strong&gt;3–4&lt;/strong&gt; agents built page groups in parallel, and I compared every page against the reference in a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually do
&lt;/h2&gt;

&lt;p&gt;People assume the AI does the work and I watch. It's closer to the opposite. The agents are fast and tireless. The judgement is mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choosing the problem.&lt;/strong&gt; The first list of topics the agents gave me was the everyday stuff I already fix, so I threw it out. The second round came from a brief I rewrote around one question: what is the market failing at that I've actually built?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing the brief.&lt;/strong&gt; An agent only knows what I tell it. A good brief names the files to read, the scope, what's off-limits and what "done" means. A vague brief gives you confident, generic work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking everything.&lt;/strong&gt; I treat every agent report as a draft from a smart new hire. It's often right, and it's always confident, even when it isn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saying no.&lt;/strong&gt; Publishing, passwords and anything that goes out under someone's name stay with a human.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it went wrong
&lt;/h2&gt;

&lt;p&gt;This is the part that matters most, because it's where an AI team differs from a real one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A calculator that looked right but wasn't honest.&lt;/strong&gt; One agent built a COD calculator with an input nobody could actually fill in (a guessed "error %"), then turned it into a scary yearly loss figure. Each step looked fine on its own. I spotted it on review, and we rebuilt it to use only numbers from a real courier statement: what you should have been paid, against what you were paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A word nobody could back up.&lt;/strong&gt; One draft called an app "well-known". No source said so, and the agent flagged it itself. I cut it. Small words like that are how trust goes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent that stopped halfway.&lt;/strong&gt; The template's foundation agent ended early with work unfinished. Nothing errored. I only knew because I checked what it had actually written to disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A test environment lying to me.&lt;/strong&gt; My local server kept reloading mid-check because of file syncing. The audit wasn't failing because of the pages. I had to change how I tested before I could trust the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these showed up as an error. Every one was caught by a check someone decided to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules that make it work
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parallelise the independent work, keep the decisions in one head.&lt;/strong&gt; Research and drafting fan out. Choosing and merging don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give every agent its own files.&lt;/strong&gt; Two agents never edit the same file. I do the integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No unsourced numbers.&lt;/strong&gt; If an agent can't point to where a number came from, it doesn't ship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify like an outsider.&lt;/strong&gt; Open it on a phone width, click the tool, read the page as the customer would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security runs on a schedule, not on memory.&lt;/strong&gt; A check runs every day at &lt;strong&gt;10:00&lt;/strong&gt; across my projects: leaked keys, open database policies, unguarded functions, secrets in live JavaScript.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this matters if you hire developers
&lt;/h2&gt;

&lt;p&gt;A solo developer used to mean small scope. That's no longer true. What you're really buying now is judgement: knowing which problem to solve, writing a precise brief, and refusing to ship what hasn't been checked. The typing is the cheap part.&lt;/p&gt;

&lt;p&gt;I'm Fahad, a Shopify and full-stack developer. I work this way every day, and I write up what I build at &lt;a href="https://fahadazhar.site/blog" rel="noopener noreferrer"&gt;fahadazhar.site&lt;/a&gt;. If you're running AI agents in real projects, I'd like to compare notes. What's broken for you so far?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Shopify can't tell you who changed your stock, so I built a ledger for 4 stores</title>
      <dc:creator>Fahad Azhar</dc:creator>
      <pubDate>Thu, 05 Nov 2026 13:37:51 +0000</pubDate>
      <link>https://dev.to/fahadazhar1/shopify-cant-tell-you-who-changed-your-stock-so-i-built-a-ledger-for-4-stores-20g2</link>
      <guid>https://dev.to/fahadazhar1/shopify-cant-tell-you-who-changed-your-stock-so-i-built-a-ledger-for-4-stores-20g2</guid>
      <description>&lt;p&gt;A product shows 12 in stock. Last week it showed 40. Was that 28 sales, a recount, a refund or an app? Shopify gives you the number, but not a usable history, and there's no Admin API for past adjustments. I run four Shopify stores (UK, Pakistan, Saudi Arabia, UAE), so I built the history myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit came first
&lt;/h2&gt;

&lt;p&gt;Before building anything, I exported every stock level across the four stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;108&lt;/strong&gt; SKUs at negative stock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;19&lt;/strong&gt; SKUs above 2,000 units, one at &lt;strong&gt;73,688&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;81%&lt;/strong&gt; "out of stock" on the UK store, inflated by &lt;strong&gt;5,309&lt;/strong&gt; levels on draft products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A ledger that starts from wrong numbers just records wrong numbers. We cleaned up first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shopify keeps the number, Postgres keeps the reason
&lt;/h2&gt;

&lt;p&gt;Staff still edit stock in Shopify, and POS and apps still read from it. On top of that, every change is also written to Postgres (Supabase): store, location, variant, before → after, reason, and who changed it. The starting point was an opening balance of &lt;strong&gt;11,544&lt;/strong&gt; levels.&lt;/p&gt;

&lt;p&gt;Webhooks tell you a level changed. They don't tell you why. So a job runs every &lt;strong&gt;5 minutes&lt;/strong&gt; and labels each change in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard intent:&lt;/strong&gt; written before our own change is sent to Shopify&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sale:&lt;/strong&gt; an order for that variant within −20/+10 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation:&lt;/strong&gt; a cancelled order in the same window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify admin:&lt;/strong&gt; still unexplained after &lt;strong&gt;60 minutes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last group, the changes nobody made through the dashboard, is the one the owner checks most. A nightly job compares the ledger with Shopify, and the result has to be &lt;strong&gt;0 mismatches&lt;/strong&gt; across all four stores. It is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard changes are atomic
&lt;/h2&gt;

&lt;p&gt;Receipts, adjustments, transfers and counts are numbered documents. Posting one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;locks the document&lt;/li&gt;
&lt;li&gt;reads the &lt;strong&gt;live&lt;/strong&gt; quantity from Shopify&lt;/li&gt;
&lt;li&gt;blocks anything that would go below zero&lt;/li&gt;
&lt;li&gt;writes the intents&lt;/li&gt;
&lt;li&gt;sends &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;inventoryAdjustQuantities&lt;/code&gt; call, so the whole document applies or none of it does&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If Shopify already accepted the change and something fails afterwards, the document is marked failed and can never go back to draft. You can't apply the same transfer twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  229,703 calls a day, and a bug that hid behind HTTP 300
&lt;/h2&gt;

&lt;p&gt;The project runs on Supabase's free plan (&lt;strong&gt;5 GB&lt;/strong&gt; egress a month), and in October it got blocked for about a week. The logs showed &lt;strong&gt;229,703&lt;/strong&gt; REST calls in 24 hours from edge functions, against &lt;strong&gt;507&lt;/strong&gt; from real users.&lt;/p&gt;

&lt;p&gt;There were two causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product sync:&lt;/strong&gt; it made separate calls for every product, every variant, and two for every tag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Courier tracking:&lt;/strong&gt; it called a database function once per event, and that function had two overloads. Postgres couldn't pick one, so all &lt;strong&gt;84,818&lt;/strong&gt; calls returned HTTP 300. Nothing errored loudly, and no tracking events were saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix was to batch everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;record_tracking_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_events&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ins&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;tracking_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;happened_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'order_id'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'at'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jsonb_array_elements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;conflict&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;nothing&lt;/span&gt;
    &lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ins&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Product sync now writes about &lt;strong&gt;7 upserts per page of 25 products&lt;/strong&gt; instead of hundreds of single calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Audit your numbers before you build the ledger.&lt;/li&gt;
&lt;li&gt;Let Shopify own the quantity; you own the reason.&lt;/li&gt;
&lt;li&gt;Write the reason down before you make your own change.&lt;/li&gt;
&lt;li&gt;Make multi-line stock changes atomic, and make "already applied" impossible to undo by accident.&lt;/li&gt;
&lt;li&gt;Monitor call volume, not just errors. My worst bug here returned HTTP 300 and stayed quiet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm Fahad, a Shopify and full-stack developer. More write-ups at &lt;a href="https://fahadazhar.site/blog" rel="noopener noreferrer"&gt;fahadazhar.site&lt;/a&gt;. How do you track stock changes across stores? Tell me in the comments.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>postgres</category>
      <category>supabase</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Four countries, four currencies, four Shopify orgs, one engineer: how I run the stack</title>
      <dc:creator>Fahad Azhar</dc:creator>
      <pubDate>Thu, 05 Nov 2026 13:28:45 +0000</pubDate>
      <link>https://dev.to/fahadazhar1/four-countries-four-currencies-four-shopify-orgs-one-engineer-how-i-run-the-stack-1i00</link>
      <guid>https://dev.to/fahadazhar1/four-countries-four-currencies-four-shopify-orgs-one-engineer-how-i-run-the-stack-1i00</guid>
      <description>&lt;p&gt;I look after four Shopify stores for the same brand. One in the UK, one in Pakistan, one in Saudi Arabia, one in the UAE. Pounds, rupees, riyals and dirhams. In the UK people pay by card; in Pakistan most people still want cash on delivery. And there's one developer on all of it, which is me.&lt;/p&gt;

&lt;p&gt;Nobody sits down and designs a setup like this. You launch one store, then another country asks for its own, and a couple of years later you're maintaining four of everything. This is what I've changed to keep it manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day I found out they weren't one company (on paper)
&lt;/h2&gt;

&lt;p&gt;I'd built a custom discount Function for the UK store and went to install it on Pakistan. Shopify said no:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;app can only be installed on stores in the same organization&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out each store had been set up separately, so each one sits in its own Shopify organization. Four stores, four orgs.&lt;/p&gt;

&lt;p&gt;So now there are four copies of the app, one per org, all built from the same code. The only things that differ are the &lt;code&gt;shopify.app.toml&lt;/code&gt; and the settings, and I keep the settings in a metafield so I'm not editing code per store.&lt;/p&gt;

&lt;p&gt;It ended up helping. When Pakistan needed something nobody else did, a small discount for paying online plus hiding Cash on Delivery for those customers, I just added two extensions to the Pakistan app. Both read the same cart attribute, so the storefront sets one flag and checkout handles the rest. No new app, no new install link.&lt;/p&gt;

&lt;p&gt;If you're taking over more than one store, find out which org each one is in before you build anything. I learned it after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four stores, really one theme
&lt;/h2&gt;

&lt;p&gt;All four run the same theme base with the same custom sections. That's great right up until you fix a bug on the UK store and someone finds the exact same bug on Pakistan a week later. That happened to me more than once.&lt;/p&gt;

&lt;p&gt;So my rule now is simple: a fix isn't finished until I've checked every store it could exist on. I think of it as one theme deployed four times.&lt;/p&gt;

&lt;p&gt;Two habits make that less scary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every morning a small Node script pulls a full copy of all four live themes through the Admin API. If something breaks, yesterday's version is sitting on my disk.&lt;/li&gt;
&lt;li&gt;I never push &lt;code&gt;templates/*.json&lt;/code&gt; from my local copy. That's where the store team's Theme Editor changes live, and they change them all the time. Liquid, CSS and JS I push freely; the template JSON I leave alone unless I've fetched the live version first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Same books, four countries, and Google
&lt;/h2&gt;

&lt;p&gt;Four stores selling mostly the same books look like duplicate pages to Google. &lt;code&gt;hreflang&lt;/code&gt; is the fix, but if you just print all four regions on every page you end up pointing Google at pages that don't exist.&lt;/p&gt;

&lt;p&gt;So I pulled every collection handle from all four stores and compared them. Only 53 out of 152 existed on more than one store. The snippet now only adds a tag for a store that actually has that collection, and &lt;code&gt;x-default&lt;/code&gt; goes to the first store that has it instead of one fixed store.&lt;/p&gt;

&lt;h2&gt;
  
  
  One profit number in four currencies
&lt;/h2&gt;

&lt;p&gt;The owner wanted to see profit for all four stores together. Getting that number to stay the same every time you open the page took two decisions.&lt;/p&gt;

&lt;p&gt;First, nobody types revenue in. It comes straight from the orders, and I use &lt;code&gt;current_total_price&lt;/code&gt;, not &lt;code&gt;total_price&lt;/code&gt;. When I checked recent Pakistan orders, about 8% had been edited after checkout (items added or removed) without a refund. &lt;code&gt;total_price&lt;/code&gt; still shows what the order was when it was placed, so every one of those was wrong.&lt;/p&gt;

&lt;p&gt;Second, the exchange rate is fixed per month. The combined view converts everything to riyals at that month's average rate. I fetch it once and save it. Otherwise last month's profit would shift a little every day as the rate updates. The riyal and the dirham are pegged to the dollar, so those two are constants. Only the pound and the rupee actually move.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;USD_PEG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;SAR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;AED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.6725&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;as const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;monthRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ccy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GBP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PKR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&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;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fxRates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ccy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&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="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// fetched once, then never again&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&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;fetchAverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ccy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fxRates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ccy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rate&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;h2&gt;
  
  
  Stock is its own story
&lt;/h2&gt;

&lt;p&gt;Each store tracks its own stock, and Shopify won't easily tell you who changed a number, or why. So I built a ledger that records every change across all four stores. That one deserves its own post, so it's next.&lt;/p&gt;

&lt;h2&gt;
  
  
  If I were starting again
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check the organization of every store on day one.&lt;/li&gt;
&lt;li&gt;Treat the stores as one theme, and check every fix everywhere.&lt;/li&gt;
&lt;li&gt;Leave the template JSON alone unless you've just fetched it live.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;current_total_price&lt;/code&gt;, and lock your exchange rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm Fahad. I build and fix Shopify stores, and I write up the problems I run into at &lt;a href="https://fahadazhar.site/blog" rel="noopener noreferrer"&gt;fahadazhar.site&lt;/a&gt;. If you run more than one store, I'd love to know how you keep them in sync. Tell me in the comments.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
  </channel>
</rss>
