<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Thoughts From Software]]></title><description><![CDATA[Welcome to Thoughts From Software. Expect strong opinions on functional programming, building clean frontend architecture, and avoiding over-engineered frameworks. Discussions also include Perl, C, JS, React, and other languages/frameworks I find truly fascinating in the tech field.]]></description><link>https://thoughtsfromsoftware.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a9de533039387c9b312bff0/e9d8c70a-d952-45d1-83c2-2bc6db9f58c1.png</url><title>Thoughts From Software</title><link>https://thoughtsfromsoftware.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 12 Sep 2026 00:15:57 GMT</lastBuildDate><atom:link href="https://thoughtsfromsoftware.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Empathy as a Service: The Illusion of Developer Experience]]></title><description><![CDATA[Preface: This article has been brewing for quite some time for me — built up over years of grievances, weaponized semantics, being forced to engage in everything from corporate jargon to vicious perfo]]></description><link>https://thoughtsfromsoftware.hashnode.dev/the-illusion-of-developer-experience</link><guid isPermaLink="true">https://thoughtsfromsoftware.hashnode.dev/the-illusion-of-developer-experience</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[developer experience]]></category><category><![CDATA[Web Developer]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[technology]]></category><category><![CDATA[agile]]></category><category><![CDATA[Scrum]]></category><category><![CDATA[management]]></category><category><![CDATA[engineering-management]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Career]]></category><category><![CDATA[webdev]]></category><category><![CDATA[software]]></category><dc:creator><![CDATA[TE.]]></dc:creator><pubDate>Wed, 09 Sep 2026 20:18:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9de533039387c9b312bff0/f2ff1270-f852-457f-ba9a-d1409da2f845.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Preface: This article has been brewing for quite some time for me — built up over years of grievances, weaponized semantics, being forced to engage in everything from corporate jargon to vicious performance review settings, and the endless stop-start of software lifecycles. I've been building software for over 14 years. I've sat in the CTO chair twice, navigated the hiring and firing trenches, and sat in the management meetings. But above all else, I am a coder. Whether I'm deep in the terminal on Neovim (by the way) working on code or architecting an entirely new frontend build for a SaaS, my absolute love for this craft is what keeps me in the game.</p>
<p>This critique comes from a place of respect for the work and the people who do it. But it's also aimed directly at the near-apathy I've seen across modern management. I've worked under some truly incredible leaders, so this isn't a blanket condemnation, but if the critique feels "finger-pointy" to certain C-suite readers or PMs — good. The spotlight needs to be shined here. This is how some of the people actually building your products feel.</p>
<p>Side Note: We are going to get into the technical weeds here, making this one of my longer pieces. But if you've survived more than a month at a tech company, you'll be able to follow along and probably have a few opinions of your own pop up. Enjoy the min-TED talk.</p>
<p><strong>TL;DR</strong> — <em>Every tool, platform, and initiative promises to make your life easier. Here is a look at who's actually cashing in the improvement.</em></p>
</blockquote>
<p>Somewhere in the last decade, "<em>Developer Experience</em>" stopped being a description and became a <em>department</em>. It got a Slack channel, a quarterly OKR, a slide in the all-hands deck w/a green arrow trending upward. Companies now hire dedicated DX engineers, run DX surveys, and ship DX roadmaps, all in service of a phrase that sounds, on its face, like it must mean "<em>we care how this feels for you</em>". I want to take that phrase seriously enough to actually interrogate it, because I don't think it means that, and I think the gap between what the term implies and what it actually optimizes for, is worth naming plainly.</p>
<p>This isn't an argument that better tooling is bad, or that companies investing in internal platforms are somehow 'acting in bad faith' or some such nonsense. Most of the individual engineers building this stuff are trying to help, genuinely, and some of what gets built under this banner is genuinely good! The critique here is one level up from the people — it's about the <em><strong>incentive structure</strong></em> the term operates inside of, and about who the heck actually ends up holding the receipt when a "dev experience win" gets reported up the chain (more than typically to some suit who couldn't care less, so long as it appeases the bottom line).</p>
<hr />
<h2>EaaS — Empathy as a Service</h2>
<p>The term <a href="https://medium.com/thinking-design/putting-people-first-tips-and-advice-from-ux-pioneer-don-norman-d43b40bb0841"><em><strong>"User Experience"</strong></em></a> actually meant something radical when <a href="https://ixdf.org/literature/topics/don-norman"><em>Don Norman coined it at Apple in the early 90s</em></a><em>.</em> He wanted to look past basic 'usability' and focus on the person living w/the product, rather than the engineers who built it. The beauty of good UX is that everyone wins: a happy user sticks around and spends money on some product they didn't even realize they needed, which directly helps the company succeed and realize what parts of the UX/UI works and doesn't work overall. The incentives match up perfectly.</p>
<blockquote>
<p><em><strong>The Importance of UX: Design should start from the person who has to live with the thing, not from the constraints of the engineering team that built it.</strong></em></p>
</blockquote>
<p><em><strong>"Developer Experience"</strong></em> (<em><strong>DX</strong></em>) completely hijacked that empathetic language — talking about 'flow' and removing friction — but applies it to a totally different reality. As a dev, you aren't the customer. The code you write is absolutely the product (and don't you forget it!). So when a company invests in making your life easier, they aren't doing it <strong>actually</strong> for your sake. They're doing it so you can churn out more work (faster and more productively) on their timeline.</p>
<p>Think about the classic DX win: shrinking a local dev setup from 3 days down to something like 20 min. Nobody wants to spend 3+ days fighting dependency errors just to run <code>npm run dev</code>, so it feels like a massive win for you. But management didn't fund this fix because they were worried about your frustration levels or how stressed your Tuesday is turning out to be. They funded it because a '3-day onboarding delay' means that many more days of paying your salary before you actually ship anything to prod. Your relief is genuine, but to the business, it's just a happy accident. Their actual target was the <a href="https://www.em-tools.io/engineering-metrics/time-to-first-commit"><em><strong>"time-to-first-commit" (TTFC)</strong></em></a>, a metric that lives on a completely different dashboard than your personal wellbeing.</p>
<hr />
<h2>What Actually Is Getting Measured Here</h2>
<p>If you want to know what an organization actually cares about, don't read its values statement; read its dashboards. And the dashboards that live under the DX umbrella almost universally measure some version of the same underlying question: <em>how fast, and how often, is code moving through the system?</em> The most influential framework here is what's called <a href="https://docs.gitlab.com/user/analytics/dora_metrics/"><em><strong>DORA</strong></em></a> — <a href="https://www.atlassian.com/devops/frameworks/dora-metrics"><strong>DevOps Research and Assessment</strong></a> — which popularized <strong>4 (now-canonical) metrics:</strong></p>
<ul>
<li><p><strong>deployment frequency</strong></p>
</li>
<li><p><strong>lead time for changes</strong></p>
</li>
<li><p><strong>change failure rate</strong></p>
</li>
<li><p><strong>time to restore services</strong></p>
</li>
</ul>
<p>These are genuinely useful numbers. They tell you real, actionable things about the health of a delivery pipeline. What they do not do, and were never designed to do in the first place, is tell you anything about whether the humans producing that pipeline are actually doing okay or not. Which, in my humble opinion, is pretty damn important.</p>
<p>Sitting alongside <em>DORA</em> (<a href="https://dora.dev/quickcheck/?v=2025">try this out</a> for fun if you want to test your patience) in most engineerings orgs is the older, blunter machinery of the stuff that makes us coders gag, <em>story points</em> and <em>sprint velocity</em> — an estimation technique originally meant as a rough, relative sizing tool for a team's own internal planning, which has (in practice and theory) calcified into a running scoreboard, by which all devs, designers, techs, and engineers are now measured on a weekly/quarterly basis. How exciting.</p>
<p><strong>Velocity charts</strong> get compared sprint over sprint. Story point totals get rolled up into quarterly <a href="https://resources.scrumalliance.org/Article/capacity-planning-scrum-team"><em><strong>capacity planning.</strong></em></a> A number invented to help a team have an honest conversation w/itself (for management) about scope has, in most companies I've seen, mutated into a number used to have a much less <em>honest</em> conversation w/a team about performance, and more silently focused on <em>who's the more productive coder</em>.</p>
<blockquote>
<p><em>A quick side note for you, there is actually a better process if you have the time to read this article as well from</em> <em><strong>Atlassian</strong></em> <em>on a slightly-alternate method of story points called,</em> <em><strong>"</strong></em><a href="https://www.atlassian.com/agile/project-management/fibonacci-story-points"><em><strong>Fibonacci Story Points</strong></em></a><em><strong>"</strong></em><em>. Worth the read, tbh.</em></p>
</blockquote>
<p>None of these metrics — not DORA, not velocity, not the increasingly popular 'engineering productivity' dashboards vendors are happy to sell you — ask the only question that would actually constitute measuring <em><strong>experience</strong></em>: did the person doing this work feel supported, sustainably paced, and clear-headed while doing it? There is no chart for "I understood what I was building and why". Just the same as there's no burndown for "I wasn't context-switched into oblivion this week". The things that would genuinely indicate a good DX are largely unmeasurable in the format a dashboard requires, so they don't get measured; in most organizations, <em>what doesn't get measured quietly, stops counting as a real metric</em>.</p>
<p>This is honestly worth naming w/its actual title: <a href="https://www.cna.org/analyses/2022/09/goodharts-law"><em><strong>Goodhart's Law</strong></em></a> — the observation that once a measure becomes a target, it stop being a reliable measure of the thing it was originally tracking. The moment "<em>deployment frequency</em>" becomes a number a team is implicitly evaluated on, you start seeing commits broken into smaller, more frequent deploys not because that's the right engineering call, but because it's the move that makes the chart look healthy.</p>
<p>The moment velocity becomes a target, story points quietly inflate (noticeably), tickets get sliced into smaller pieces to produce more 'completed' units per sprint, and the number goes up while the actual pace of meaningful work stays exactly the same — or slows, honestly — because everyone's now spending part of their attention managing the number instead of the actual work. The dashboards were never lying, exactly. They were just never measuring the thing DX claims to be about in the first place.</p>
<blockquote>
<p>Another side note here: a great article from <strong>Splunk</strong> is also worth the read called, "<a href="https://www.splunk.com/en_us/blog/learn/goodharts-law.html"><em><strong>What is Goodhart's Law?</strong></em></a>".</p>
</blockquote>
<hr />
<h2>Mandatory "Convenience"</h2>
<p>There's a small but load-bearing difference between a tool that optimizes <em>for you</em>, and a tool that just optimizes <em>you</em>, and almost the entire modern DX tooling market lives on the wrong side of that preposition.</p>
<p>A tool that optimizes for you starts from your actual friction and quietly removes it, and its success is measured by whether you personally, feel less friction. A tool that optimizes <em>you</em> starts from a metric someone else wants to move, and you are the input it's tuning to get there. Both kinds of tools can look, on a features page, identical.</p>
<p>Take AI code-completion tools. Now, standard issue at almost every single tech company, not to mention most other companies/firms based around marketing, technology, sales, statistics, the list goes on but you get my point. The pitch is unambiguously developer-centric: <em>less boilerplate, faster iteration, more time for the interesting problems</em>. And for a lot of day-to-day typing, that's genuinely true (for the most part).</p>
<p>But sat next to that pitch is a second, quieter one, aimed at leadership rather than devs: acceptance-rate dashboards, suggestions-per-engineer counts, "<em>AI-assisted productivity</em>" reports that get cited in the next tooling budget meeting. The tool didn't stop being useful because it's also being measured. But the reason it <em>survived procurement</em> was never really "devs reported feeling less friction"; it was "usage numbers justify the license cost". Which is a completely different sentence wearing the same tool's name tag.</p>
<p>The same substitution shows up in "focus time" tools, meeting-load dashboards, and the various flavors of engineering-analytics platforms (God knows how many there are nowadays) sold explicitly under the DX banner. Products whose marketing copy is soaked in words like <em>toil</em>, <em>flow state</em> (ew), and <em>cognitive load</em>, and whose actual customer — the person signing the invoice — is an engineering director trying to answer questions about headcount and throughput. A calendar analysis tool that tells you "you spent 11 hours in meetings this week" is framed as <em>empowering</em>.</p>
<p>Now you can reclaim your time! But the same data rolled up across a whole org, is at least as useful for justifying a reorg as it is for helping any individual dev protect the peace of a Tuesday morning. The dev-facing feature and the leadership-facing report usually ship in the same product at the same time, and from the same company.</p>
<p>I've watched this play out at a smaller scale too, the kind that doesn't make it into a case study. A team I worked w/once rolled out a new <em><strong>PR-description template</strong></em>, sold internally as a <em>DX improvement</em> — "it'll help reviewers understand context faster, so review turnaround improves for everyone." What it actually did was add 6 new <em>required fields</em> to every damn PR. Stuff like:</p>
<ul>
<li><p>testing checklist</p>
</li>
<li><p>rollback plan</p>
</li>
<li><p>risk assessment</p>
</li>
<li><p>linked ticket</p>
</li>
<li><p>screenshot <em>requirement</em></p>
</li>
</ul>
<p>And a section that absolutely nobody (including any PMs) could remember the purpose of. Review turnaround as a metric did improve, marginally, on the dashboard someone was tracking. The actual experience of opening a PR — for the person who had just spent the day writing all the code — got measurably worse, and stayed that way because the trouble had simply moved one step earlier in the pipeline, from the reviewer's side to the author's; and only one of those experiences was the one anyone was measuring.</p>
<blockquote>
<p>The honest test for whether a tool is optimizing <em><strong>for you</strong></em> or just <em><strong>you</strong></em> is pretty simple, and almost nothing passes it: could you opt out, w/zero consequence to your standing, your reviews, or your next performance cycle? Most DX tooling is (at least in practice) mandatory. Mandatory convenience is a contradiction worth stewing over.</p>
</blockquote>
<hr />
<h2>Follow The Convenient Yellow (Mandatory) Brick Road</h2>
<p>Platform engineering is, in its best-intentioned form, a real answer to a real problem: devs were spending too much of their time hand-rolling infrastructure, wrangling Kubernetes manifests, and reinventing CI/CD deployment pipelines that a hundred other teams in the same company had already reinvented just ever so slightly differently.</p>
<p>The fix — internal dev platforms, standardized tooling, self-service infrastructure, list goes on — is (on paper) a legitimate act of care. Reduce the cognitive load of undifferentiated infrastructure work as the pitch goes (yawn), so devs can spend their attention on the <em>actual</em> problem they were hired to solve! Tools like Spotify's open-source <a href="https://backstage.spotify.com/"><em><strong>Backstage</strong></em></a> <strong>popularized the concept of a 'golden path'</strong> — a single, well-supported, very opinionated route through the platform that's easier than doing it yourself from scratch. (Check this <a href="https://engineering.atspotify.com/2020/03/what-the-heck-is-backstage-anyway">article</a> out for a breakdown if you're interested.)</p>
<p>Here's where the strings show up. A <em>golden path</em> is only golden because it's the <em><strong>only</strong></em> path anyone will help you walk. Step off it even for a second — need a slightly different database, a different deployment cadence, a language the platform team didn't actually optimize for — and you don't just lose convenience, you often lose support entirely; sometimes explicitly, through the much quieter mechanism of nobody on the platform team having the <em>bandwidth</em> (hate that term) to help you w/the thing you weren't supposed to be doing.</p>
<p>What gets marketed as <em>'convenience'</em> functions structurally as a <em><strong>mandate</strong></em>, and mandates that centralize how every team builds and ships are (not even incidentally) also mandates that centralize stuff like <em>visibility and control</em> for whoever owns the actual platform. That's not a criticism of platform teams by any stretch, who are usually just trying to keep an org from fragmenting into 40+ incompatible ways of doing the exact same thing. It's honestly a criticism of pretending the resulting control is purely a DX initiative rather than also, quite directly, an organizational-leverage one.</p>
<p>There's a specific irony that shows up once a platform matures. Devs now have to learn the platform team's <em>abstraction</em> on top of the underlying tech it was supposed to simplify, and that layer is sometimes more (a word I heard years ago and still love/hate) idiosyncratic, more specific to one company's internal priorities and conventions, and less transferable than the open tech it's wrapping. You can google a Kubernetes error message and find thousands of people who've hit the same wall as you.</p>
<p>But you can't google your own company's internal deployment tool's cryptic failure state or what failed pieces of the tech stack architecture didn't workout, because something like 5 people in the world have ever seen it, and 3 of the 5 are on the platform team, and none of them are answering Slack messages because it's Friday at 440pm and God forbid. When the abstraction leaks — and abstractions always eventually leak don't get tricked here — the dev who was "protected" from the underlying complexity now has <em><strong>less context</strong></em> to debug it than if they'd just been allowed to understand the real system from the start.</p>
<p>None of this makes <a href="https://platformengineering.com/features/the-platform-engineering-illusion-why-most-internal-developer-platforms-fail-before-developers-ever-use-them/">Platform Engineering (PE)</a> a bad idea. Standardization has real, legitimate value, and a well-run internal platform genuinely does remove a category of tedious work that nobody was excited to do by hand (can think of a few). The point is narrower than "<a href="https://thenewstack.io/platform-engineering-is-failing-heres-why-infrastructure-comes-first/"><em><strong>platform engineering is secretly evil</strong></em></a>" — it's that the <em>primary design constraint</em> behind most internal platforms is purely (as I'm sure most of you know) organizational consistency and control, and dev relief is a real (but secondary) benefit that shows up in the marketing copy far more prominently than it shows up in the actual design review.</p>
<hr />
<h2>The Lie of "Moving Faster"</h2>
<p><em>Okay so this is going to come off like the ramblings of a senior coder who has seen this kind of thing happen too much, so feel free to skip this part (as if this entire article thus far hasn't been exactly that).</em></p>
<p><a href="https://www.wrike.com/scrum-guide/faq/what-is-velocity-in-scrum/"><strong>Velocity</strong></a>, as a concept, was never supposed to be a <em>performance number</em>. In its original <a href="https://getdx.com/blog/agile-velocity/"><em><strong>Scrum framing</strong></em>,</a> it's a rough, <strong>team-internal planning tool</strong> — a relative measure of how much a specific team (with its specific quirks and context) tends to get through in a sprint — used mainly so that team can make its own honest guesses about what fits in the next one. It was designed to answer the question "<em>what can we commit to?</em>", asked by a team, of itself. It was never in fact designed to answer the question a lot of orgs now quietly use it for: "<em>is this team performing?</em>". Asked from 3 levels up the food chain, by someone who has never sat in a team standup or written a single line of code.</p>
<p>The DX-flavored language wrapped around velocity initiatives is almost always framed around the developer. "<em>We want to remove blockers so your team can move faster</em>"; "<em>let's identify what's slowing you down</em>". And sometimes that framing is completely sincere, and the blockers really do get removed, and the removal really does make the day-to-day work better. But look at where the resulting chart actually goes: it goes into a dang <em>sprint review</em>. It goes into a quarterly <em>business review</em>. It becomes a line on a slide that says the org is "shipping faster", presented to people whose job is forecasting and resourcing, not to the dev whose blocked Tuesday it used to represent. The initiative was described in the language of care for the individual. The artifact it produced was a forecasting input for the business.</p>
<p>The pressure this creates is specific and corrosive in a way that's easy to underestimate from the outside. A team's velocity can legitimately dip for reasons that have nothing to do w/laziness or dysfunction (no matter what some ugly people may tell you). Someone spent the sprint mentoring a new hire, someone else absorbed an incident and spent 3 days firefighting instead of shipping, a chunk of the team's attention went into a UX/UI design conversation that will save months of rework later but doesn't produce a single closed ticket this week.</p>
<p>None of that shows up as "<em>verified value</em>" on a <a href="https://support.atlassian.com/jira-software-cloud/docs/view-and-understand-the-velocity-chart/">velocity chart</a>. It shows up as a <em>dip</em>, and a dip invites a question; the question — even when nobody intends it this way… — lands as "<em>why weren't you productive (enough)</em>". Aimed at people who were, in fact, doing some of the most valuable and least visible work available to them that week.</p>
<p>The deeper failure is that velocity and quality are not the same axis, and a team can absolutely post record velocity while quietly accumulating the exact tech debt, burnout, and corner-cutting that will show up as a '<em>crisis</em>' 2 quarters later — at which point it gets treated as a brand new problem, worthy of a brand new DX initiative; rather than the entirely predictable consequence of having optimized the wrong number for the last 6 months. Velocity is a genuinely useful figure for a business that needs to forecast a roadmap. It has never been, and was never designed to be, a proxy for whether the people generating it are doing sustainable, healthy work. Calling it a "<a href="https://getdx.com/blog/developer-productivity-metrics/?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=content_developer_productivity&amp;utm_content=197199156235&amp;utm_term=developer%20productivity%20metrics&amp;gad_source=1&amp;gad_campaignid=23840566041&amp;gclid=Cj0KCQjw5P7UBhDaARIsAOSlS1PIDOGTeBz5UJ_kOEyJ5QCkdhT69uR0oXD-u82VdQgffZZOYAfEWW0aAjFLEALw_wcB"><em>developer experience metric</em></a>" doesn't change what it was built to measure.</p>
<p><strong>It just changes who feels obligated to smile about the number going down.</strong></p>
<hr />
<h2>Utopia, Unfunded</h2>
<p>If companies actually showed any true interest in the developer experience, it wouldn't start w/a dashboard. It would start by asking devs/designers/engineers directly how their work feels, then acting on answers that resist clean metrics — like whether anyone had room to think clearly, felt safe admitting they were stuck, or worked at a sustainable pace.</p>
<p>Real DX isn't abstract. It contains a series of important variables that (in my experience) most of them go often overlooked.</p>
<ul>
<li><p><strong>Protected Focus Time:</strong> Calendar blocks backed by enforced company policy; not ignored Slack posts.</p>
</li>
<li><p><strong>Fair On-Call:</strong> Rotations built w/actual backup coverage and real compensation.</p>
</li>
<li><p><strong>Safety Values:</strong> Structural permission to adjust a velocity target w/o it becoming a mark against you in your <em>performance review</em>.</p>
</li>
<li><p><strong>Opt-In Tooling:</strong> Software so good engineers <em><strong>want</strong></em> to use it, rather than mandatory workflows forced on the team.</p>
</li>
</ul>
<p>Here is the true litmus test for any new DX initiative: <strong>Where do the results get reported to first?</strong></p>
<p>If the first conversation is a sincere check-in asking devs, "<em>Does this actually feel better?</em>", it might be legitimate. If the first place it shows up is an executive slide-deck boasting about "improved engineering velocity", it was a productivity push all along, and therefore not necessarily healthy for the dev.</p>
<p>The people building internal tooling aren't villains. We just need to name things accurately. Call a velocity drive what it is. Call platform control a platform mandate. Reserve "Developer Experience (DX)" for the rare moments we actually optimize for the human doing the work, not the chart generated on someone else's way up the ladder.</p>
<hr />
<h2>One Last Thought…</h2>
<p><strong>On a final, personal yet public note here:</strong> if your workday feels like an ongoing state of <em>fight-or-flight</em>, remember that no velocity metric or dashboard is worth sacrificing your nervous system, and it most certainly isn't "<em>just a part of the tech industry</em>". Software engineering is what we do, not who we are, and your worth as a human is entirely separate and more important than how many story points you move across a board. <a href="https://www.leonpahole.com/blog/post/stay-healthy-software-engineer-tips/">Staying healthy matters here</a>. The latest comments on your PR from your senior are not the end of the world, I swear (been there, trust me). If you're feeling overwhelmed, don't carry it alone, check in on yourself. Reach out to a close friend, talk to a therapist (highly recommend!), or simply step back from the keyboard and breathe for a minute — '<em>touch grass</em>', as I've heard 3 different junior devs say in meetings. What "doing your best" looks like changes from day to day, and simply showing up today is, I promise you, more than enough. As they say in the south:</p>
<h3>"Give yourself some grace. Because nobody else is gonna." ~TE.</h3>
<hr />
<h2>References &amp; Further Reading</h2>
<p><em>I've compiled a list of the links I've not only put into this article w/love and care, but also things I've come across that I believe are beneficial reads for any good dev or engineer to checkout. Enjoy!</em></p>
<h3>UX &amp; Engineering Philosophy</h3>
<ul>
<li><p><a href="https://medium.com/thinking-design/putting-people-first-tips-and-advice-from-ux-pioneer-don-norman-d43b40bb0841">Putting People First: Don Norman</a> — Medium</p>
</li>
<li><p><a href="https://ixdf.org/literature/topics/don-norman">Don Norman &amp; Modern UX Origins</a> — Interaction Design Foundation</p>
</li>
</ul>
<h3>DORA, Productivity Metrics &amp; Goodhart's Law</h3>
<ul>
<li><p><a href="https://www.em-tools.io/engineering-metrics/time-to-first-commit">Time-to-First-Commit (TTFC)</a> — EM Tools</p>
</li>
<li><p><a href="https://docs.gitlab.com/user/analytics/dora_metrics/">DORA Metrics Breakdown</a> — GitLab Docs</p>
</li>
<li><p><a href="https://www.atlassian.com/devops/frameworks/dora-metrics">DevOps Research and Assessment (DORA) Framework</a> — Atlassian</p>
</li>
<li><p><a href="https://dora.dev/quickcheck/?v=2025">DORA Quick Check Assessment Tool</a> — <a href="http://DORA.dev">DORA.dev</a></p>
</li>
<li><p><a href="https://www.cna.org/analyses/2022/09/goodharts-law">Goodhart's Law &amp; Systemic Measurement Failures</a> — CNA</p>
</li>
<li><p><a href="https://www.splunk.com/en_us/blog/learn/goodharts-law.html">What is Goodhart's Law?</a> — Splunk</p>
</li>
<li><p><a href="https://getdx.com/blog/developer-productivity-metrics/?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=content_developer_productivity&amp;utm_content=197199156235&amp;utm_term=developer%20productivity%20metrics&amp;gad_source=1&amp;gad_campaignid=23840566041&amp;gclid=Cj0KCQjw5P7UBhDaARIsAOSlS1PIDOGTeBz5UJ_kOEyJ5QCkdhT69uR0oXD-u82VdQgffZZOYAfEWW0aAjFLEALw_wcB">16 Developer Productivity Metrics Top Companies Use</a> — DX Blog</p>
</li>
</ul>
<h3>Agile, Estimation &amp; Velocity</h3>
<ul>
<li><p><a href="https://www.atlassian.com/agile/project-management/fibonacci-story-points">Fibonacci Story Points: A Better Estimation Method</a> — Atlassian</p>
</li>
<li><p><a href="https://resources.scrumalliance.org/Article/capacity-planning-scrum-team">Capacity Planning in Scrum</a> — Scrum Alliance</p>
</li>
<li><p><a href="https://www.wrike.com/scrum-guide/faq/what-is-velocity-in-scrum/">What Is Scrum Velocity?</a> — Wrike Scrum Guide</p>
</li>
<li><p><a href="https://getdx.com/blog/agile-velocity/">Agile Velocity vs. Capacity</a> — DX Blog</p>
</li>
<li><p><a href="https://support.atlassian.com/jira-software-cloud/docs/view-and-understand-the-velocity-chart/">Understanding Jira Velocity Charts</a> — Jira Cloud Support</p>
</li>
</ul>
<h3>Platform Engineering &amp; Golden Paths</h3>
<ul>
<li><p><a href="https://backstage.spotify.com/">Spotify Backstage Official Open-Source Platform</a></p>
</li>
<li><p><a href="https://engineering.atspotify.com/2020/03/what-the-heck-is-backstage-anyway">What the Heck is Backstage Anyway?</a> — Spotify Engineering</p>
</li>
<li><p><a href="https://thenewstack.io/platform-engineering-is-failing-heres-why-infrastructure-comes-first/">Platform Engineering Is Failing — Here's Why Infrastructure Comes First</a> — The New Stack</p>
</li>
<li><p><a href="https://platformengineering.com/features/the-platform-engineering-illusion-why-most-internal-developer-platforms-fail-before-developers-ever-use-them/">The Platform Engineering Illusion: Why Most IDPs Fail</a> — Platform Engineering</p>
</li>
<li><p><a href="https://medium.com/tech-and-me/when-does-a-startup-need-platform-engineering-an-honest-answer-54cab53cd7ec">When Does a Startup Need Platform Engineering?</a> — Medium (The AIExplorer)</p>
</li>
</ul>
<h3>Developer Health &amp; Sustainable Pacing</h3>
<ul>
<li><p><a href="https://www.leonpahole.com/blog/post/stay-healthy-software-engineer-tips/">How I Stay Healthy as a Software Engineer</a> — Leon Pahole</p>
</li>
<li><p><a href="https://www.reddit.com/r/softwaredevelopment/comments/r394yn/software_devs_of_reddit_hows_your_health/">Software Devs of Reddit: How’s Your Health?</a></p>
</li>
<li><p><a href="https://www.thesweekly.com/p/prioritizing-health-as-a-software">Prioritizing Physical &amp; Mental Health as an Engineer</a></p>
</li>
<li><p><a href="https://youtu.be/amHy0PQEmYc?si=BQiC154w-fYbRuNy">The Health Cost of Being a Software Engineer</a> — YouTube</p>
</li>
<li><p><a href="https://www.ltvco.com/engineering/health-implications-engineer-lifestyle/">Health Implications of the Software Engineer Life—And How To Fix Them</a></p>
</li>
</ul>
<hr />
<p><code>// EOF</code></p>
]]></content:encoded></item><item><title><![CDATA[Mastering The Native Fetch Async/Await]]></title><description><![CDATA[Preface: This is a bit of a long and detailed article, and while I have a greater point to this article, and I’m going to show you the fallbacks and best practices for how to go about avoiding Axios a]]></description><link>https://thoughtsfromsoftware.hashnode.dev/mastering-the-native-fetch-api</link><guid isPermaLink="true">https://thoughtsfromsoftware.hashnode.dev/mastering-the-native-fetch-api</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[software]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[software development]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[Developer]]></category><category><![CDATA[engineering]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[React]]></category><category><![CDATA[api]]></category><category><![CDATA[frontend]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Frontend frameworks]]></category><category><![CDATA[#Web Architecture]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[axios]]></category><category><![CDATA[axios in react]]></category><category><![CDATA[fetch]]></category><category><![CDATA[fetch API]]></category><category><![CDATA[fetch vs axios]]></category><category><![CDATA[native]]></category><dc:creator><![CDATA[TE.]]></dc:creator><pubDate>Sun, 06 Sep 2026 23:20:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9de533039387c9b312bff0/1c8f56bf-25be-473d-aa21-4e203a2c6a85.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>Preface:</strong> This is a bit of a long and detailed article, and while I have a greater point to this article, and I’m going to show you the fallbacks and best practices for how to go about avoiding <strong>Axios</strong> as a whole for your future software and web apps, I feel it necessary to give a brief (albeit technical) backstory to why I feel Axios on the whole has not only never been my go-to (unless a company I’m working w/already has it in place which unfortunately has happened), but will now absolutely continue never to be in any future software products or even side-projects I work on in my career. So bare w/me here, but I truly think it’s important to fill you in on the exact details of what happened, as far as we the public know.</p>
</blockquote>
<hr />
<h3>The Gritty Start</h3>
<p>On March 31, 2026, someone ran <code>npm install</code> on a fresh clone of a project depending on the <strong>npm package</strong> <a href="https://www.npmjs.com/package/axios"><code>axios</code></a>, and got a remote access trojan for their trouble.</p>
<p>Here’s what actually happened. An attacker compromised the npm account of <a href="https://github.com/jasonsaayman"><strong>Axio’s lead maintainer</strong></a>, not through a code vulnerability, but through a targeted social-engineering campaign against the maintainer directly. Roughly 18+ hours before the main event, they published a quiet, unassuming decoy package called <code>plain-crypto-js</code>, just to establish it as a normal-looking thing that had existed for a while.</p>
<p>Then, at <strong>00:21 UTC</strong>, using the stolen credentials, they manually published <code>axios@1.14.1</code> — bypassing npm's <em>Trusted Publishing</em> safeguard entirely, since that mechanism only applies to packages published through a verified CI pipeline, and a manual publish from a compromised account walks right around it. Around 40 minutes later, they did the same thing to the legacy branch with <code>axios@0.30.4</code>. Both versions quietly pulled in <code>plain-crypto-js</code> as a dependency (big uh oh) — a package that does not appear anywhere in the actual <a href="https://github.com/axios/axios"><strong>Axios source code</strong></a>, mainly because it isn't actually a part of Axios. It's a cross-platform remote access trojan, built for macOS, Windows, and Linux (all 3 of the main OS's in case you're unaware), which phoned home to what is called a <em>'command-and-control</em>' server and then attempted to erase its own tracks before anyone was the wiser. The whole thing was live for a grand total of about 3 hours before it was discovered and pulled.</p>
<p><strong>TL;DR —</strong> <a href="https://www.youtube.com/watch?v=eGSsoSEppNU"><strong>Axios Got Hacked.</strong></a></p>
<p>Here are some figures you should know going into this article as well.</p>
<p><strong>Axios sees somewhere north of 85+ million weekly downloads.</strong> A 3-hour window on a package that size is not a “near-miss” statistic. It’s a real number of real machines that got backdoored because a project somewhere in their dependency tree ran <code>npm install</code> at the wrong moment within such a short fraction of time. I realize 'backdoored' is not a word, but I'm making it a word because it fits so beautifully for the context of the situation.</p>
<hr />
<h2>To the Point</h2>
<p>I’m not telling you this story to make you paranoid about <a href="https://www.npmjs.com/"><strong>npm</strong></a> as a concept — open-source at scale for sure requires trusting <em>some</em> semblance of code that you in fact did not write yourself, that’s the deal, that’s how the entire ecosystem functions (not perfectly, but smoothly). I’m telling you this story because it’s the cleanest possible illustration of a fact that gets treated as background noise until it isn’t: every dependency in your <code>package.json</code> file is code that runs w/the same privileges as the code you wrote yourself, maintained by people you've never met, updated on a schedule you have zero control over, and it takes exactly one singular compromised account to turn any of them into an attack vector. That's true of every single npm package available. It just so happen to be Axios's turn.</p>
<p>Which brings me to the actual point of this article. Axios exists to solve a specific, narrow problem: <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Network_requests"><strong>making HTTP requests from JavaScript</strong></a> (or TypeScript) w/o having to write a lot of repetitive boilerplate. That probelm does not require a 3rd-party dependency anymore. It requires <code>fetch</code> w/the <code>async/await</code> values; which has been sitting in every browser and in Node itself for years, and about 40-ish lines of code you write once, read in full, and never have to trust a stranger's npm account to maintain. Crazy concept nowadays I know…writing your own code.</p>
<hr />
<h2>The Myth of Fetch’s Verbosity</h2>
<p>The standard argument against <code>fetch</code> goes something like: it's too low-level, you have to manually check the <code>response.ok</code> request, you have to manually call a <code>.json()</code> for the response data, there's no built-in timeout, no interceptors, no automatic error-throwing on a bad status code. All of that is true, and easily fixable. None if it is an argument for a dependency. It's an argument for writing about (like I said) 40 lines of your own code, exactly once (reusable things called <strong>functions</strong> are pretty great for this…), instead of installing someone else's several-hundred-kilobyte abstraction over the same lines of code — an abstraction mind you that comes w/its own maintainers, npm account, and its own attack surface, as in the last section I just demonstrated in detail.</p>
<p>Here’s what actually changed the calculus. When Axios first got popular, <em><strong>JavaScript</strong></em>’s story around asynchronous code was callbacks and, later, raw promise chains (genuinely awkward to compose/too verbose at the time of its inception). The <code>async/await</code> fixed that at the language level.</p>
<p><strong>“De-structuring assignment”</strong> means pulling <code>data</code> and <code>error</code> off a returned object costs you zero extra lines. Native fetch has supported an <code>AbortController</code> (built-in timeout) — you get the timeout by writing about 6 lines around a controller, not by installing a library. Every specific complaint about fetch's verbosity is a complaint about the absence of a thin, specific wrapper, not an argument that the wrapper has to come from <code>node_modules</code>.</p>
<p><strong>So build the wrapper. Once. Read every line of it. Nobody else’s compromised account or packages can touch it.</strong></p>
<hr />
<h2>Building the Wrapper</h2>
<p>The rest of this article builds a small, dependency-free <strong>HTTP client</strong> on top of native <em>fetch</em> w/using <em>async/await</em>, showing you how to setup and use stuff like:</p>
<ul>
<li><p>global-base URL</p>
</li>
<li><p>request interceptors</p>
</li>
<li><p>normalized error-handling</p>
</li>
<li><p>closures and good old fashioned <strong>FP (functional programming)</strong></p>
</li>
<li><p>no classes/constructors/methods</p>
</li>
<li><p>no <code>this</code> uses</p>
</li>
</ul>
<p><em><strong>Side Note:</strong></em> I am the type of programmer that prefers FP &gt; OOP. If you disagree, and are more of an “object-oriented is better” person, that’s completely fine! (You’re just wrong and I hate you no worries). If you’ve spent any time around functional-style JavaScript, none of this will feel unfamiliar; it’s the same discipline applied to network requests instead of array transforms.</p>
<hr />
<h3>Step 1: A Configuration Closure, Not a Class</h3>
<p>The instinct a lot of intermediate devs reach for here is a <code>class ApiClient</code> type of thing, with <code>this.base_url</code> set in a constructor. Skip it. A factory function that closes over its configuration and returns a plain old object of methods does the exact same job, w/no <code>this</code> binding <a href="https://refactoringjs.com/files/refactoring-javascript.pdf">footguns</a> and no instantiation ceremony.</p>
<pre><code class="language-javascript">// Creating an API client is super simple
const create_api_client = ({ base_url = '', default_headers = {} } = {}) =&gt; {
    const request_interceptors = []; // new request array
    const use_request_interceptor = interceptor_fn =&gt;
        request_interceptors.push(interceptor_fn); // push the data into the array
    // more to come, this is just the shape so far
    return { use_request_interceptor };
};
// Btw, you all need to quit the python method of never using semicolons. Stop it;
</code></pre>
<p>The <code>create_api_client</code> runs once at startup, and every method it returns closes over <code>base_url</code>, <code>default_headers</code>, and <code>request_interceptors</code> for the lifetime of the app. Nothing outside this function can reach the <code>request_interceptors</code> directly — the only way in is through the <code>use_request_interceptor()</code> function, which is the exact same <a href="https://www.geeksforgeeks.org/javascript/encapsulation-in-javascript/"><em><strong>encapsulation-through-closure pattern</strong></em></a> that makes <code>const count = 0;</code> inside a counter function private w/o a single <a href="https://www.geeksforgeeks.org/javascript/what-are-access-modifiers-in-javascript/"><strong>access modifier keyword</strong></a>.</p>
<hr />
<h3>Step 2: Normalized Error Handling</h3>
<p>This is the part most hand-rolled fetch wrappers get lazy about, and it’s the part that actually matters. Fetch’s promise only rejects on a genuine network failure (DNS not resolving, connection refused). A <em>404</em> or a <em>500</em> status is — as far as fetch is concerned — a perfectly successful round trip. If your wrapper doesn’t check for a green (good) status (<code>response.ok</code>) itself and convert a bad status into an explicit failure, every single call site in your app has to remember to do it manually, and someone eventually won't.</p>
<p><strong>The fix:</strong> a wrapper that never throws to its caller and never hands back a ‘<em>successful</em>’ response for a failed request. Every call resolves to the same predictable shape, always: <code>{ data, error }</code>. Easy to remember and use, I swear.</p>
<pre><code class="language-javascript">// Create a normalize error for both the response and request
const normalize_error = (message, status = null) =&gt; ({
    data: null, // initial data pull should be null
    error: { message, status },
});
// Notice the status parameter (optional btw) has a default null value,
// this is because the error object is a generic object,
// so you don't need to specify the status code
</code></pre>
<p>One shape. Every failure — a network error, a bad status code, a JSON parse failure — collapses into this same structure. No call site downstream needs a <code>try/catch</code> block of its own, and no call site needs to know or care <em>why something failed</em> to handle the failure correctly.</p>
<hr />
<h3>Step 3: Request Interceptors</h3>
<p>So it’s a clunky term to be fair, but really an <a href="https://medium.com/@jscodelover/understanding-request-and-response-interceptors-in-javascript-e2fe20dbabbf"><em><strong>interceptor</strong></em></a> here is just a function that takes a request config and returns a (possibly modified) one. The most common real use is attaching an <a href="https://dev.to/edriso/auth-tokens-explained-from-just-ship-it-to-actually-secure-46kd"><em><strong>auth token</strong></em></a> to every outgoing request w/o repeating that logic at every call site, which can be beyond annoying as hell and tedious.</p>
<pre><code class="language-javascript">// Build out the request interceptors (async function)
const apply_request_interceptors = async (config, interceptors) =&gt; {
    let current_config = config; // this applies the config object to the first interceptor
    for (const int of interceptors) current_config = await int(current_config);
    // Loop through each interceptor and await the result and apply it to the config
    return current_config;
};
// NOTE: that the function opens up w/using the 'async' keyword to tell the
// browser that this is an async function and the await keyword is used to
// wait for the async function to complete or return a value before proceeding
</code></pre>
<p>The <code>await</code> inside the loop matters here. I wanted to make a note on how important the second part of <code>async/await</code> is — an interceptor might need to do something asynchronous itself, like refreshing an expired token before attaching it, and this makes that possible w/o the caller needing to know or really even care.</p>
<hr />
<h3>Step 4: The Core Request Function</h3>
<p>This is where everything from the last 3 steps gets all wired together and put in place. Gear up, and pay attention!</p>
<pre><code class="language-javascript">// Create the actual API Client (this is important)
const create_api_client = ({ base_url = '', default_headers = {} } = {}) =&gt; {
    const request_interceptors = [];
    const use_request_interceptor = interceptor_fn =&gt;
        request_interceptors.push(interceptor_fn);
    const normalize_error = (message, status = null) =&gt; ({
        data: null,
        error: { message, status },
    });
    const apply_request_interceptors = async config =&gt; {
        let current_config = config;
        for (const int of request_interceptors)
            current_config = await int(current_config);
        return current_config;
    };
    const request = async (path, options = {}) =&gt; {
        try {
            // Setup the request options and headers data
            const config = await apply_request_interceptors({
                ...options,
                headers: { ...default_headers, ...options.headers },
            });
            // Save the full API fetch to the response variable
            const response = await fetch(`${base_url}${path}`, config);
            // IMPORTANT! This runs a check to see if the request failed
            if (!response.ok)
                return normalize_error(
                    `Request failed with status: ${response_status}`,
                    response.status,
                );
            // Save the full response data as JSON and immediately return
            const data = await response.json();
            // Return the data and no error
            return { data, error: null };
        } catch (error) {
            // return the exact error message from the request
            return normalize_error(error.message);
        }
    };
    return { request, use_request_interceptor };
};
</code></pre>
<p><strong>Notice what’s absent:</strong> no call site of <code>request</code> ever needs a try/catch block. Every failure mode — the network dying mid-request, a 404, malformed JSON in the response body — gets caught here, once, and normalized into the same <code>{ data, error }</code> shape before it ever even leaves the dang function.</p>
<p>That’s the entire value proposition of this wrapper, and it’s the exact same ‘push mutation to a small, disposable, locally-scoped place instead of letitng it leak everywhere’ discipline that makes the <code>.reduce()</code> accumulator acceptable to mutate even in an otherwise-immutable codebase — <code>request_interceptors</code> is the one array in this whole file that gets mutated, and it's mutated in exactly one controlled spot; invisible to everything outside this closure.</p>
<hr />
<h3>Step 5: The Verb Methods</h3>
<p><code>request</code> alone works, but nobody wants to write-out <code>client.request('/tickets', { method: 'GET' })</code> at every call site. Thin, specific verb methods on top of it:</p>
<pre><code class="language-javascript">// Now we'll add the API specific CRUD calls (Create, Read, Update, Delete)
const create_api_client = ({ base_url = '', default_headers = {} } = {}) =&gt; {
    // ...(same as above)...
    // The GET request
    const GET = (path, options) =&gt; request(path, { ...options, method: 'GET' });
    // The POST request
    const POST = (path, body, options) =&gt;
        request(path, {
            ...options,
            method: 'POST',
            body: JSON.stringify(body),
            headers: {
                'Content-Type': 'application/json',
                ...options?.headers,
            },
        });
    // The PUT request
    const PUT = (path, body, options) =&gt;
        request(path, {
            ...options,
            method: 'PUT',
            body: JSON.stringify(body),
            headers: {
                'Content-Type': 'application/json',
                ...options?.headers,
            },
        });
    // The DELETE request
    const REMOVE = (path, options) =&gt;
        request(path, { ...options, method: 'DELETE' });
    // Return all the methods
    return { GET, POST, PUT, delete: REMOVE, use_request_interceptor };
};
// NOTE: We use the 'remove' word instead of 'delete', I'll explain why below.
</code></pre>
<p>The <code>delete</code> keyword is a reserved word as a variable name, not as an object property, so the internal function is named <code>REMOVE</code> and exposed on the returned object as <code>delete</code>. The callers get the natural <code>client.delete(...)</code> they'd expect, and nothing internal has to fight the parser to get there.</p>
<p><strong>NOTE:</strong> I used uppercase words for these method calls but it is to show that these (while only lexical scope, not global) should be used as global keywords. I’m old school, and I was taught that global variables and functions should be uppercase to help separate them from localized (lexical) scope variables. Feel free to name them anything you’d like, again so long as they are not a <a href="https://www.w3schools.com/js/js_reserved.asp"><strong>reserved keyword in JavaScript</strong></a>.</p>
<p>So now we just need to wire it up once, at the edge of the app:</p>
<pre><code class="language-javascript">// lib/create_api_client.js (everything from steps 1-5 above)

// lib/api_client.js
import { create_api_client } from './create_api_client';

export const api = create_api_client({
    base_url: import.meta.env.VITE_API_BASE_URL,
    default_headers: { 'Content-Type': 'application/json' },
});
api.use_request_interceptor(config =&gt; {
    const TOKEN = localStorage.getItem('auth_token');
    if (!TOKEN) return config;
    return {
        ...config,
        headers: {
            ...config.headers,
            Authorization: `Bearer ${TOKEN}`,
        },
    };
});
</code></pre>
<p><strong>Now setup a</strong> <code>.env</code> <strong>file:</strong></p>
<pre><code class="language-yaml"># Create a (.env) file in the same directory,
# and add the following line:
VITE_API_BASE_URL=http://localhost:1337
</code></pre>
<p><strong>Note:</strong> You can choose whichever Port # you’d like, so long as it’s not a currently used one like <em>3000</em> or something. I just prefer <code>1337</code> because of a silly old <a href="https://www.theproblemsite.com/reference/mathematics/codes/leet-speak"><strong>Leetspeak</strong></a> reference from an <a href="https://megatokyo.com/strip/9"><em>old comic</em></a>. Feel free to <strong>not</strong> check it out.</p>
<p>The line <code>import.meta.env.VITE_API_BASE_URL</code> is <a href="https://vite.dev/guide/env-and-mode"><strong>Vite's environment-variable</strong></a> convention. This whole setup assumes a standard Vite client build, not a meta-framework doing anything clever w/the request lifecycle on your behalf (looking at you <a href="https://news.ycombinator.com/item?id=45099922"><em>Next.js</em></a>).</p>
<hr />
<h2>Wiring It Into React and Zustand</h2>
<p>The point of building this as plain functions instead of a class is that it composes into any state layer w/o any ceremony. Here’s the part that actually matters architecturally:</p>
<p>Components should not know <code>fetch</code> even exists. Data fetching lives in the <em><strong>store</strong></em>. Components read state and call actions. This is where a <a href="https://namastedev.com/blog/why-you-should-use-zustand-for-react-state-2/"><strong>state management system</strong></a> like <a href="https://zustand.docs.pmnd.rs/"><strong>Zustand</strong></a> comes in handy!</p>
<pre><code class="language-javascript">// store/use_ticket_store.js
import { create } from 'zustand';           // the 'create' function is not a default export in zustand
import { api } from '../lib/api_client';    // import the api info from what you createconst initial_state = {
    tickets: [],
    is_loading: false,
    error: null,
};
export const use_ticket_store = create(set =&gt; ({
    // import the initial_state object or simply put it right here;
    // dealer's choice.
    ...initial_state,
    fetch_tickets: async () =&gt; {        // notice the function call is async
        set({ is_loading: true, error: null });
        const { data, error } = await api.GET('/tickets');
        // Remember, the GET request method was uppercased in the api_client file
        if (error) {
        // Check for errors on the callback here as well
            set({ error, is_loading: false });
            return;
        }
        // Now set the tickets to the data and shut off the is_loading state
        set({ tickets: data, is_loading: false });
    },
}));
</code></pre>
<p>The <code>fetch_tickets</code> function is the only place in the entire app that knows <code>/tickets</code> is an <a href="https://dev.to/shubhamtiwari909/mastering-api-handling-in-javascript-react-a-complete-guide-45kk"><strong>endpoint</strong></a>, or that fetching it might fail. The component consuming this store doesn't import <code>api</code>, doesn't know the <code>create_api_client</code> function even exists, and absolutely does not contain a <code>try/catch</code> block. Now set it up:</p>
<pre><code class="language-javascript">// components/TicketList.jsx
import React, { useEffect } from 'react'; // React is no longer necessary; I do it anyway
import { use_ticket_store } from '../store/use_ticket_store';
import Loading from './Loading'; // a Loading component
const TicketList = () =&gt; {
    const tickets = use_ticket_store(state =&gt; state.tickets);
    const is_loading = use_ticket_store(state =&gt; state.is_loading);
    const error = use_ticket_store(state =&gt; state.error);
    const fetch_tickets = use_ticket_store(state =&gt; state.fetch_tickets);
    // Create a useEffect that will run every time the fetch_tickets function is called
    useEffect(() =&gt; {
        if (tickets.length === 0) fetch_tickets(); // check if tickets are empty
    }, [fetch_tickets]);
    if (is_loading) return &lt;Loading /&gt;;
    if (error) return &lt;h1&gt;Something went wrong: {error.message}&lt;/h1&gt;;

    return (
        &lt;ul className='ticket-list'&gt;
            {tickets.map(ticket =&gt; (
                &lt;li key={ticket.id}&gt;
                    {ticket.customer} - {ticket.status}
                &lt;/li&gt;
            ))}
        &lt;/ul&gt;
    );
};
export default TicketList;
// NOTE: Some people simply deconstruct here and do something like:
// const { tickets, is_loading, etc... } = use_ticket_store();
// I prefer to create each variable explicitly for each state slice.
</code></pre>
<p>This is the decoupling that actually matters; it’s worth being explicit about why.</p>
<p>If <code>TicketList</code> imports the <code>api</code> directly and calls <code>api.GET('/tickets')</code> itself instead of using a state store, every component that needs ticket data would need its own loading state, its own error state, and its own opinion about what 'failed' looks like. Centralizing the fetch inside the store means there's exactly one place that understands the network, and an unlimited number of components that just read a plain, predictable state shape. Swap the entire backend, change the endpoint, add caching — none of it touches a single component.</p>
<hr />
<h3>The Takeaway</h3>
<p><a href="https://cloud.google.com/blog/topics/threat-intelligence/north-korea-threat-actor-targets-axios-npm-package"><strong>The <em>Axios incident</em></strong></a> wasn’t a failure of Axios as a project, and it isn’t really an argument specifically against Axios going forward either. The maintainers responded, the malicious versions got pulled, the ecosystem’s tooling did roughly what it’s supposed to do once the compromise was detected.</p>
<p>It’s an argument about the shape of the risk itself: the moment you add a dependency to your code, you’ve added a second party who can ship code directly into your production environment, and you don’t get to vet every release the way you’d review a PR (pull request) from your own team.</p>
<p><strong>Fetch</strong> is not going to get compromised by a hijacked <em>npm account</em>, because it isn’t an npm package. It ships w/the runtime, it’s specified by a standards body, and the worst thing that can happen to your <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch"><strong>request-handling logic</strong></a> is a bug you personally wrote and can personally read, in a file that’s maybe 80 lines long, that you understand completely because you built every line of it yourself. That’s not a purity argument. It’s a <a href="https://blog.riskrecon.com/what-is-risk-surface"><em><strong>risk-surface</strong></em></a> argument, and after <em>March 2026</em>, it’s a considerably easier one to make outloud.</p>
<p>Learn the platform first. Reach for a dependency only once you’ve confirmed the platform genuinely cannot do it, not before.</p>
<h3>Sources on the March 2026 Axios compromise:</h3>
<ul>
<li><p><a href="https://snyk.io/blog/axios-npm-package-compromised-supply-chain-attack-delivers-cross-platform/"><strong>Snyk’s technical writeup</strong></a></p>
</li>
<li><p><a href="https://www.huntress.com/blog/supply-chain-compromise-axios-npm-package"><strong>Huntress’s incident report</strong></a></p>
</li>
<li><p><a href="https://github.com/axios/axios/issues/10636"><strong>maintainer’s own post-mortem on GitHub</strong></a></p>
</li>
</ul>
<p><code>// EOF</code></p>
]]></content:encoded></item><item><title><![CDATA[Hello World]]></title><description><![CDATA[If you have spent any meaningful amount of time in the software engineering field over the last few years, you’ve probably noticed a trend. A big trend. The industry has developed a massive, collectiv]]></description><link>https://thoughtsfromsoftware.hashnode.dev/exhausting-software-hype-cycle</link><guid isPermaLink="true">https://thoughtsfromsoftware.hashnode.dev/exhausting-software-hype-cycle</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[software development]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[Developer]]></category><category><![CDATA[engineering]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[frameworks]]></category><category><![CDATA[Frontend frameworks]]></category><category><![CDATA[api]]></category><category><![CDATA[#Web Architecture]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[TE.]]></dc:creator><pubDate>Sun, 06 Sep 2026 22:50:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9de533039387c9b312bff0/9c232405-4864-4eb0-897a-f3093da2ba58.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have spent any meaningful amount of time in the software engineering field over the last few years, you’ve probably noticed a trend. A big trend. The industry has developed a massive, collective allergy to simplicity, and the lack of care for the how things are built and work.</p>
<p>We take perfectly functional programming paradigms and bury them under massive object-oriented class structures. We take dynamic, expressive scripting languages and try to force them into the rigid, compiled boxes of the 1990s. We download 400MB of dependencies just to render a static blog post.</p>
<p>After 14+ years of writing software and documentation, building web applications, and consulting companies large and startup, I’ve decided to carve out a quiet corner here on Medium (and my personal tech blog) to talk about the craft of code — w/o the noise and slop.</p>
<p>My name is Tyler. I am a Senior Software Engineer who spends entirely too much time in a terminal environment configuring everything from legacy code on deprecated/backlogged systems, all the way to coding in the most up-to-date software repos for companies (and myself) — and I strongly prefer writing code that actually makes sense. It’s truly as simple as that.</p>
<h3>What to Expect</h3>
<p>If you know me from my other corners of the internet, you know I have a deep, abiding love for a highly specific set of tools: Vanilla JavaScript, React, Node, C, and Perl. I don’t chase the flavor-of-the-month meta-frameworks, and I truly believe Next.js is utter trash, and you only need “type safety” in rare instances across the board. I care about how things actually work at not only the high-level, but mainly the compiler and memory levels, dealing with low-level languages has become my true passion.</p>
<p>To kick things off on this publication, I’m currently writing two deep-dive series aimed at engineers who want to understand their tools rather than just copy-pasting from documentation or some outdated AI prompt.</p>
<h3>Scheme in a Trench Coat: The Computer Science of JavaScript</h3>
<p>Everyone loves to make jokes about JavaScript’s weird edge cases. Most of those jokes are lazy, and they obscure the fact that JS is one of the most brilliantly misunderstood functional languages ever designed. I’m going to tear the language down to the studs. I’ll show you the brilliant functional architecture <strong>Brendan Eich</strong> stole from <em>Scheme</em>, why the single-threaded <em>Event Loop</em> is a masterclass in concurrency, and how the <em>V8 Engine’s JIT</em> compiler essentially tricks your computer into running dynamic code at C-level speeds.</p>
<h3>There’s More Than One Way to Write History: The Philosophy and Culture of Perl</h3>
<p>Perl was the duct tape that held the early internet together, alongside C. But it’s not just some relic language of the 90s dot-com boom. I’ll explore how <strong>Larry Wall</strong>’s background in linguistics created a language that thinks like a human speaks rather than a machine, how <em>CPAN</em> laid the groundwork for every modern package manager (insert some stupid, lazy <em>npm</em> joke here.), and why a dedicated community of pragmatic engineers keeps the language thriving today. For example, did you know there’s a Perl Convention that still meets every year in South Carolina? Pretty neato.</p>
<h3>React and Move On: The Pragmatist’s Guide to Sustainable Development</h3>
<p>The JavaScript ecosystem has been obsessed lately with “fixing” React by wrapping it in layers of complexity, heavy build steps, and mandatory meta-frameworks (looking at you Next.js). But what if React was never the problem? This series strips away the vendor-driven hype cycle to look at what made React a dominant force in the first place: a simple functional composition, predictable state flow, and a commitment to keeping the UI predictable. I am moving past the framework fatigue to reclaim a pragmatic, sustainable approach to building web/software apps.</p>
<h3>My Baseline</h3>
<p>I believe strongly in <strong>FP</strong> — <em>functional programming</em>. I believe in composition over inheritance. I believe that writing software should feel like a craft, like art, not some bureaucratic chore that — in some companies, unfortunately — it has truly become.</p>
<p>If you are tired of the endless churn of the <strong>hype cycle</strong> as I call it, and want to get back to the core <strong>computer science</strong> of the tools that actually run the web and software itself, stick around. We’ve got a lot of ground to cover.</p>
<p><strong>Welcome to my terminal.</strong></p>
<p><code>// EOF</code></p>
]]></content:encoded></item></channel></rss>