<?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: Juan Isidoro García Cifuentes</title>
    <description>The latest articles on DEV Community by Juan Isidoro García Cifuentes (@juanisidoro).</description>
    <link>https://dev.to/juanisidoro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3780696%2F8621dd02-337c-43a5-8e9b-bbae10d6fdcb.jpeg</url>
      <title>DEV Community: Juan Isidoro García Cifuentes</title>
      <link>https://dev.to/juanisidoro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juanisidoro"/>
    <language>en</language>
    <item>
      <title>Open Source Licenses: Which One Should You Pick? MIT, GPL, Apache, AGPL and More (2026 Guide)</title>
      <dc:creator>Juan Isidoro García Cifuentes</dc:creator>
      <pubDate>Fri, 27 Feb 2026 10:37:49 +0000</pubDate>
      <link>https://dev.to/juanisidoro/open-source-licenses-which-one-should-you-pick-mit-gpl-apache-agpl-and-more-2026-guide-p90</link>
      <guid>https://dev.to/juanisidoro/open-source-licenses-which-one-should-you-pick-mit-gpl-apache-agpl-and-more-2026-guide-p90</guid>
      <description>&lt;p&gt;You just finished your side project. You push it to GitHub, hit "Create repository" and there it is — that dropdown you always skip:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Choose a license.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;MIT, GPL, Apache, BSD... does it really matter?&lt;/p&gt;

&lt;p&gt;Short answer: &lt;strong&gt;yes, a lot.&lt;/strong&gt; Pick the wrong one and a big company can take your work and never give anything back. Pick none and technically nobody can legally use your code at all — even if it's sitting there in a public repo.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical problem. Let me give you a real example that shook the industry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HashiCorp&lt;/strong&gt; is a company behind some of the most widely used tools in cloud infrastructure. One of their flagship products is &lt;strong&gt;Terraform&lt;/strong&gt; — a tool that thousands of companies (from startups to Fortune 500) use to manage their servers, databases, and cloud resources. Think of it as the "remote control" for your entire cloud infrastructure. It was open source for years, with a massive community building plugins, writing tutorials, and integrating it into their workflows.&lt;/p&gt;

&lt;p&gt;In August 2023, HashiCorp changed Terraform's license from MPL (a permissive open source license) to the &lt;strong&gt;Business Source License (BSL)&lt;/strong&gt; — which, as we'll see later, is not open source. Overnight, companies that had built their entire infrastructure around Terraform had to ask themselves: &lt;em&gt;can we still use this the way we do?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The community's response? They &lt;strong&gt;forked the entire project&lt;/strong&gt; — took a copy of the code from before the license change — and created &lt;a href="https://opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt;, a fully open source alternative backed by the Linux Foundation. Thousands of contributors, millions of lines of code, infrastructure teams across the world scrambling to evaluate their options. All because of a license change.&lt;/p&gt;

&lt;p&gt;That's how much this stuff matters. A single license decision can split a community, create competing projects, and force entire organizations to rethink their tech stack.&lt;/p&gt;

&lt;p&gt;As someone who builds SaaS products and works as a freelance developer, I've learned to check licenses before adding any dependency to my stack. It's one of those things you ignore until it bites you. This guide is everything I wish someone had explained to me when I started — the most common licenses with &lt;strong&gt;real use cases&lt;/strong&gt;, no legal jargon, no fluff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's what we'll cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key concepts you need before diving in&lt;/li&gt;
&lt;li&gt;Quick comparison table&lt;/li&gt;
&lt;li&gt;Each license explained with real use cases&lt;/li&gt;
&lt;li&gt;Dual licensing explained&lt;/li&gt;
&lt;li&gt;License compatibility rules&lt;/li&gt;
&lt;li&gt;Common licensing mistakes&lt;/li&gt;
&lt;li&gt;AI tools and code licensing&lt;/li&gt;
&lt;li&gt;FAQ for solo developers&lt;/li&gt;
&lt;li&gt;Decision tree to pick your license&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Concepts Before Diving In
&lt;/h2&gt;

&lt;p&gt;If you already know what copyleft means and why distribution matters, skip ahead to the comparison table. If not, spend 2 minutes here — everything else will make a lot more sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Software License?
&lt;/h3&gt;

&lt;p&gt;A license is just a document that tells people &lt;strong&gt;what they can and can't do with your code&lt;/strong&gt;. Think of it as the "house rules": you decide whether others can copy, modify, sell, or redistribute your work.&lt;/p&gt;

&lt;p&gt;Without a license, &lt;strong&gt;nobody has permission to do anything&lt;/strong&gt; with your code — even if it's public on GitHub. Public does not mean free to use. It just means visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does "Open Source" Actually Mean?
&lt;/h3&gt;

&lt;p&gt;It means the source code is available and anyone can view, use, modify, and distribute it &lt;strong&gt;under certain conditions&lt;/strong&gt; (defined by the license). It doesn't mean "free" and it doesn't mean "no rules."&lt;/p&gt;

&lt;p&gt;For a license to be officially considered open source, it must be approved by the &lt;strong&gt;OSI (Open Source Initiative)&lt;/strong&gt; — they're the ones who set the standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Source vs. Source-Available: What's the Difference?
&lt;/h3&gt;

&lt;p&gt;"Source-available" is a newer term. It means you can &lt;strong&gt;see the code&lt;/strong&gt; (it's transparent), but you &lt;strong&gt;can't do whatever you want with it&lt;/strong&gt;. There are restrictions, usually around commercial use. The BSL (Business Source License) is a good example — we'll cover it later.&lt;/p&gt;

&lt;p&gt;The key difference: open source lets you use the code freely. Source-available lets you look at it, but with limits on usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive vs. Copyleft Licenses: The Big Split
&lt;/h3&gt;

&lt;p&gt;Every open source license falls into one of two families. Understanding this is the single most important thing in this entire post:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🟢 Permissive licenses (MIT, Apache, BSD)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The relaxed ones. They basically say: &lt;em&gt;"Do whatever you want with my code. Use it in your commercial app, modify it, sell it. Just give me credit."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your code can end up inside closed-source proprietary software, and that's totally fine. The author gives up control over what happens with the code once it's shared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🟠 Copyleft licenses (GPL, AGPL, LGPL)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The protective ones. They say: &lt;em&gt;"You can use and modify my code, but if you distribute your work, you must share it under the same conditions."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The term &lt;strong&gt;copyleft&lt;/strong&gt; started as a wordplay on "copyright." While copyright restricts copying, copyleft &lt;strong&gt;flips copyright on its head&lt;/strong&gt;: instead of preventing copies, it forces you to keep things open. If you use copyleft code, your derived work must be open too.&lt;/p&gt;

&lt;p&gt;This creates what people call a &lt;strong&gt;"viral" effect&lt;/strong&gt; — the license spreads to everything it touches. If you include GPL code in your project, your entire project becomes GPL. It's not literally a virus, but the practical effect is similar.&lt;/p&gt;

&lt;p&gt;Why would anyone choose copyleft? Because it protects the community's work. Without it, a big company could take your open source project, improve it, close it, and sell it without giving anything back. With copyleft, that can't happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does "Distribute" Mean in Software Licensing?
&lt;/h3&gt;

&lt;p&gt;This concept comes up everywhere in licensing and it's not always obvious. &lt;strong&gt;Distributing&lt;/strong&gt; means delivering a copy of the software to someone else. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publishing an app on a store (App Store, Google Play)&lt;/li&gt;
&lt;li&gt;Sending a binary or installer to a client&lt;/li&gt;
&lt;li&gt;Uploading a package to npm, PyPI, or any public registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not considered distribution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using the software internally at your company&lt;/li&gt;
&lt;li&gt;Running the software as a web service (SaaS) — this is where the AGPL comes in, and we'll get to that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction is critical: if you use GPL code only internally and never distribute anything, you're not required to open your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Do Software Patents Matter for Licensing?
&lt;/h3&gt;

&lt;p&gt;Some companies patent specific techniques or algorithms. If a license doesn't mention patents, someone could contribute code to your project and later sue you for using a technique they've patented. Licenses like Apache 2.0 include a &lt;strong&gt;patent grant&lt;/strong&gt; that protects you from this.&lt;/p&gt;

&lt;p&gt;If you're a solo developer working on small projects, this probably won't affect you. But if your code will be used by larger companies or receive external contributions, it's worth thinking about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Commercial use?&lt;/th&gt;
&lt;th&gt;Copyleft?&lt;/th&gt;
&lt;th&gt;Must open source?&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MIT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permissive&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Maximum adoption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache 2.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permissive&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Enterprise projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BSD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permissive&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;MIT-like + brand protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPL v3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong copyleft&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (entire project)&lt;/td&gt;
&lt;td&gt;Software that must stay free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AGPL v3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong copyleft&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (even SaaS)&lt;/td&gt;
&lt;td&gt;Protection against cloud providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LGPL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Weak copyleft&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (library only)&lt;/td&gt;
&lt;td&gt;Libraries used by closed software&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BSL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Source-available&lt;/td&gt;
&lt;td&gt;⚠️ Restricted&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌ (during restriction period)&lt;/td&gt;
&lt;td&gt;Companies wanting transparency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the full spectrum at a glance — from most permissive to most restrictive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; PERMISSIVE                              COPYLEFT                         SOURCE-AVAILABLE
 ─────────────────────────────────────────────────────────────────────────────────────────
 MIT        BSD       Apache 2.0       LGPL        GPL v3      AGPL v3         BSL
 │          │         │                │           │           │               │
 Do what    Same as   MIT + patent     Copyleft    Everything  GPL + SaaS      Visible but
 you want   MIT but   protection       only for    must stay   must also       restricted
            protects                   the lib     open        be open         commercially
            your name                  itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  MIT License
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the MIT License and When Should You Use It?
&lt;/h3&gt;

&lt;p&gt;The world's most popular open source license. Use it, modify it, sell it, distribute it. The only requirement: keep the copyright notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; React, Node.js, Meilisearch, jQuery, VS Code&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You build a &lt;strong&gt;React component library&lt;/strong&gt;. You want every company and developer out there to use it in their commercial projects with zero friction. The more people use it, the more contributions you get and the more your reputation grows.&lt;/p&gt;

&lt;p&gt;You don't mind if someone puts it in a closed-source product — your goal is &lt;strong&gt;maximum adoption&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick MIT when:&lt;/strong&gt; you want your code to spread everywhere and you don't care who makes money from it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Apache 2.0 License
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the Apache 2.0 License and How Does It Differ From MIT?
&lt;/h3&gt;

&lt;p&gt;Very similar to MIT but with one key difference: &lt;strong&gt;patent protection&lt;/strong&gt;. If someone contributes code to your project, they automatically grant you a license to any related patents. If they later sue you over those patents, they lose their rights to the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; Kubernetes, TensorFlow, Apache Kafka, Android (AOSP)&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;Your startup is building a &lt;strong&gt;machine learning framework&lt;/strong&gt;. You know big companies will contribute code. You need the legal guarantee that they won't sue you later over patents covering the features they contributed themselves.&lt;/p&gt;

&lt;p&gt;With MIT, that protection doesn't exist. With Apache 2.0, &lt;strong&gt;every contributor automatically grants you a patent license&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick Apache 2.0 when:&lt;/strong&gt; you're in an enterprise environment where patents are a real risk.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  BSD License (2-Clause and 3-Clause)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the BSD License?
&lt;/h3&gt;

&lt;p&gt;Almost identical to MIT. The &lt;strong&gt;3-clause&lt;/strong&gt; version adds one extra rule: &lt;strong&gt;you can't use the author's name or the project's name to promote a derivative product&lt;/strong&gt; without permission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; Nginx, FreeBSD, Flask, PostgreSQL (similar variant)&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You publish a &lt;strong&gt;high-performance networking tool&lt;/strong&gt;. You're fine with others using or modifying it commercially. But you don't want a company to fork it and market it saying:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Built on the work of [your name]"&lt;/em&gt; or &lt;em&gt;"From the creator of [your project]"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;...to gain credibility at your expense without your consent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick BSD 3-clause when:&lt;/strong&gt; you want MIT-level freedom but with brand protection.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  GPL v3 (GNU General Public License)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the GPL License and What Does Copyleft Mean in Practice?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strong copyleft.&lt;/strong&gt; If you distribute software that includes GPL code, your software &lt;strong&gt;must also be GPL and open source&lt;/strong&gt;. That means publishing your full source code under the same terms.&lt;/p&gt;

&lt;p&gt;Remember: distributing means handing a copy to someone (publishing an app, sending a binary, uploading a package). If you use GPL code only internally and never distribute it, you're not required to open your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; Linux kernel, WordPress, GIMP, GCC&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You're building a &lt;strong&gt;content management system&lt;/strong&gt;. You want to make sure that if a company takes it, improves it, and distributes it, those improvements &lt;strong&gt;must come back to the community&lt;/strong&gt;. Nobody can "close" your work.&lt;/p&gt;

&lt;p&gt;WordPress works exactly like this. That's why its ecosystem is massive: every theme and plugin that gets distributed must keep the GPL license.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick GPL when:&lt;/strong&gt; you want to guarantee your code and all its derivatives stay free forever.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ⚠️ Keep in mind
&lt;/h3&gt;

&lt;p&gt;GPL is "viral" (as we explained earlier): if you include GPL code in your project, &lt;strong&gt;your entire project&lt;/strong&gt; becomes GPL. This scares away many companies working with closed source. That's not a bug — it's the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  AGPL v3 (Affero General Public License)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the AGPL and Why Does It Matter for SaaS?
&lt;/h3&gt;

&lt;p&gt;Just like GPL but with one crucial addition. The GPL has a &lt;strong&gt;loophole&lt;/strong&gt;: offering software as a web service (SaaS) is not considered "distribution." So a company can take your GPL code, run it on their servers, charge customers for it, and never share a single line of their modifications.&lt;/p&gt;

&lt;p&gt;The AGPL closes that gap. If you make the software available over a network, &lt;strong&gt;you must also publish the source code&lt;/strong&gt;, including your changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; Grafana, Nextcloud, Mastodon, MongoDB (before switching to SSPL)&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You create a &lt;strong&gt;self-hosted analytics platform&lt;/strong&gt;. Without the AGPL, Amazon could take your code, launch it as a managed service on AWS, charge money for it, and &lt;strong&gt;give nothing back&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With AGPL, if they offer it as SaaS, they're &lt;strong&gt;required to publish their modifications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the anti-cloud-giant shield. MongoDB eventually moved from AGPL to the even more restrictive SSPL — the AGPL wasn't enough to hold off AWS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick AGPL when:&lt;/strong&gt; your software will be offered as a web service and you want anyone monetizing it to contribute back.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  LGPL (Lesser General Public License)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the LGPL and When Should You Use It for Libraries?
&lt;/h3&gt;

&lt;p&gt;A softer version of GPL. It allows proprietary software to &lt;strong&gt;use your library&lt;/strong&gt; (by linking to it dynamically) without becoming GPL themselves. But if someone &lt;strong&gt;modifies your library directly&lt;/strong&gt;, those modifications must be open source.&lt;/p&gt;

&lt;p&gt;The difference from full GPL: with GPL, if you use the library, your whole project becomes GPL. With LGPL, only direct modifications to the library itself must be opened — the rest of your application can stay closed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; FFmpeg (partially), Qt (community edition), GNU C Library (glibc)&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You build a &lt;strong&gt;video processing library&lt;/strong&gt;. You want every app — including closed-source commercial ones — to be able to use it. But if someone improves your compression algorithm, that specific improvement &lt;strong&gt;must come back to the community&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The rest of their application can stay proprietary. Only the direct changes to &lt;em&gt;your&lt;/em&gt; library need to be shared.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick LGPL when:&lt;/strong&gt; you're building a library you want everyone to use, while protecting direct improvements to it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  BSL (Business Source License)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is the BSL and Is It Open Source?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No, the BSL is not open source&lt;/strong&gt; — it's "source-available" (as we discussed earlier). The code is visible and auditable, but production or commercial use is &lt;strong&gt;restricted for a set period&lt;/strong&gt; (usually 3–4 years). After that period, it automatically converts to a fully open source license (typically Apache 2.0).&lt;/p&gt;

&lt;p&gt;Remember the HashiCorp/Terraform story from the intro? This is the license they switched to — and the reason the community forked the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Used by:&lt;/strong&gt; MariaDB, Sentry, CockroachDB, HashiCorp (Terraform, Vault), Elastic&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real-world scenario
&lt;/h3&gt;

&lt;p&gt;You're launching a &lt;strong&gt;database as a SaaS product&lt;/strong&gt;. You want developers to see your code, audit it, learn from it, and use it in development. But you &lt;strong&gt;don't want AWS to clone your product&lt;/strong&gt; and compete against you using your own code.&lt;/p&gt;

&lt;p&gt;BSL gives you a window of commercial exclusivity. Once the period expires, the code becomes fully open.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pick BSL when:&lt;/strong&gt; you want transparency and trust without handing your business model to the hyperscalers.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Dual Licensing: How Some Projects Offer Two Licenses
&lt;/h2&gt;

&lt;p&gt;Some projects don't pick just one license — they offer &lt;strong&gt;two&lt;/strong&gt; (or more) and let you choose. This is called &lt;strong&gt;dual licensing&lt;/strong&gt;, and it's more common than you might think.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does dual licensing work?
&lt;/h3&gt;

&lt;p&gt;The project is available under a copyleft license (usually GPL or AGPL) &lt;strong&gt;for free&lt;/strong&gt;. But if the copyleft terms don't work for you — say, you're building a closed-source product and can't go GPL — you can &lt;strong&gt;buy a commercial license&lt;/strong&gt; that lets you use the code without copyleft obligations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Real examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qt&lt;/strong&gt; — Free under LGPL/GPL. Need to keep your code closed? Buy a commercial license from The Qt Company.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; — Free under GPL. Don't want your project to be GPL? Oracle sells a commercial license.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB&lt;/strong&gt; (before SSPL) — Used this model with AGPL + commercial option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFmpeg&lt;/strong&gt; — Parts are LGPL, parts are GPL. If you need the GPL parts in a closed-source product, you need a separate agreement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💡 When does this matter to you?
&lt;/h3&gt;

&lt;p&gt;If you're using a dual-licensed library and the free license is copyleft, ask yourself: &lt;em&gt;"Am I okay with my project being copyleft too?"&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt; → Use the free license. No problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No&lt;/strong&gt; → You'll need to buy the commercial license. Budget for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a freelancer, I've had to factor this into project estimates more than once. A "free" library isn't always free if your client needs a closed-source product.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Consider dual licensing for your own projects if:&lt;/strong&gt; you want to keep the code open for the community but also generate revenue from companies that need proprietary terms.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  License Compatibility: Can You Mix MIT and GPL?
&lt;/h2&gt;

&lt;p&gt;This is where most developers mess up — and I'll be honest, I had to learn some of these the hard way while auditing dependencies in my own projects. &lt;strong&gt;Not all licenses play well together.&lt;/strong&gt; Mix incompatible ones and you've got a legal problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The basic compatibility rules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Permissive → Copyleft = ✅ Generally OK&lt;/strong&gt;&lt;br&gt;
You can use MIT or Apache code inside a GPL project. The result will be GPL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copyleft → Permissive = ❌ Nope&lt;/strong&gt;&lt;br&gt;
You can't take GPL code and put it in an MIT project. Copyleft propagates upward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPL + AGPL = ⚠️ Careful&lt;/strong&gt;&lt;br&gt;
GPL v3 and AGPL v3 are compatible with each other. GPL v2 (without the "or later" clause) is not compatible with AGPL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache 2.0 + GPL v2 = ❌ Incompatible&lt;/strong&gt;&lt;br&gt;
Apache 2.0 has patent clauses that clash with GPL v2. It is compatible with GPL v3 though.&lt;/p&gt;
&lt;h3&gt;
  
  
  How copyleft propagation actually works
&lt;/h3&gt;

&lt;p&gt;Think of it like mixing paint. If you pour a drop of red paint (copyleft) into a bucket of blue paint (permissive), the entire bucket changes. You can't take the red out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your project (MIT) + Library A (MIT) + Library B (MIT) = Your project stays MIT ✅

Your project (MIT) + Library A (MIT) + Library B (GPL) = Your ENTIRE project becomes GPL 🔴
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second scenario catches a lot of people off guard. You didn't write the GPL code, you just used it as a dependency — but the copyleft still propagates to your whole project.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Real example
&lt;/h3&gt;

&lt;p&gt;You're building a commercial app. You pick 15 libraries, all MIT. Then you add one small utility library that happens to be GPL. Your entire app &lt;strong&gt;must now be GPL&lt;/strong&gt; — meaning you have to publish your source code. If your business model depends on keeping the code closed, that one dependency just blew it up.&lt;/p&gt;

&lt;p&gt;I've seen this happen in freelance projects. The fix is almost always painful: find an alternative library with a permissive license, or rewrite the functionality yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; check the license before adding any dependency. Tools like &lt;a href="https://fossa.com/" rel="noopener noreferrer"&gt;FOSSA&lt;/a&gt;, &lt;code&gt;license-checker&lt;/code&gt; (npm), or &lt;code&gt;pnpm licenses&lt;/code&gt; can audit your project for you. Make it a habit — future you will thank you.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5 Open Source Licensing Mistakes Developers Make
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Not adding a license to your GitHub repo
&lt;/h3&gt;

&lt;p&gt;"It's on a public GitHub repo, so it's open source."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong.&lt;/strong&gt; Without an explicit license, nobody has legal permission to use, copy, or modify your code. Public ≠ free to use. I've reviewed GitHub repos that had hundreds of stars but no license file — technically, none of those stargazers could legally use the code in their projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using GPL code in a closed-source product
&lt;/h3&gt;

&lt;p&gt;If you distribute software that includes a GPL dependency, &lt;strong&gt;your entire project must be GPL&lt;/strong&gt;. This isn't hypothetical — it has real consequences. In 2008, Cisco had to release the source code for their Linksys router firmware after the Free Software Foundation found they were using GPL-licensed code (from the Linux kernel and other projects) without complying with the license terms. They settled the case and had to make the code available. A multi-billion dollar company, caught by a license they didn't take seriously.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Confusing source-available with open source
&lt;/h3&gt;

&lt;p&gt;BSL, SSPL, and similar licenses are &lt;strong&gt;not open source&lt;/strong&gt; according to the OSI. You can see the code, but you can't do whatever you want with it. If your README says "open source" and your license is BSL, that's misleading. This distinction matters — especially if your users or contributors care about open source principles.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Ignoring transitive dependencies and their licenses
&lt;/h3&gt;

&lt;p&gt;Your project is MIT. But you use a library that depends on another library that's GPL. That chain of dependencies can change the rules without you even noticing. This is why automated license auditing tools aren't optional — they're essential. One &lt;code&gt;npm install&lt;/code&gt; can pull in hundreds of transitive dependencies, each with its own license.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Assuming "non-commercial" means open source
&lt;/h3&gt;

&lt;p&gt;Creative Commons licenses with an NC (Non-Commercial) clause are &lt;strong&gt;not open source&lt;/strong&gt;. They're designed for content (text, images, music), not software. If you see a project on GitHub with a CC BY-NC license, you cannot use it in any commercial context — and it's not considered open source by the OSI definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Tools, GitHub Copilot, Claude Code, and Open Source Licenses
&lt;/h2&gt;

&lt;p&gt;This deserves its own section because it's reshaping how we think about code ownership. If you use GitHub Copilot, Claude Code, ChatGPT, Cursor, or any other AI-powered coding tool, you need to understand what's happening under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who owns AI-generated code?
&lt;/h3&gt;

&lt;p&gt;This is genuinely uncharted territory. Current copyright law in most countries requires a &lt;strong&gt;human author&lt;/strong&gt; for something to be copyrightable. If an AI generates a block of code, there's no clear human author — which means it might not be copyrightable at all.&lt;/p&gt;

&lt;p&gt;What does that mean in practice? It's still being debated. Courts in different countries are reaching different conclusions. The safest approach for now: &lt;strong&gt;treat AI-generated code as if you wrote it yourself&lt;/strong&gt;, and take full responsibility for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The training data problem
&lt;/h3&gt;

&lt;p&gt;Here's where it gets tricky. Tools like GitHub Copilot were trained on publicly available code — including code under GPL, AGPL, MIT, and every other license. When Copilot suggests a code snippet, it might be reproducing (or closely paraphrasing) code from a GPL-licensed project.&lt;/p&gt;

&lt;p&gt;If you accept that suggestion and put it in your closed-source app, are you violating the GPL? &lt;strong&gt;Nobody knows for certain yet.&lt;/strong&gt; There are active lawsuits on exactly this question. In 2022, a class-action lawsuit was filed against GitHub, Microsoft, and OpenAI arguing that Copilot violates open source licenses by reproducing licensed code without attribution or license compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about AI coding agents like Claude Code?
&lt;/h3&gt;

&lt;p&gt;AI coding agents (Claude Code, Copilot Workspace, Cursor Agent, etc.) can write entire files, modules, or even full applications. The license question gets more complex here because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The tool's license&lt;/strong&gt; — Claude Code itself is licensed under Apache 2.0. This means the tool is open source, but it doesn't say anything about the code it generates for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The generated code&lt;/strong&gt; — Code that these tools write for you is generally considered yours. Anthropic's terms (and similar terms from other providers) typically grant you ownership of the output. But "ownership" doesn't resolve the question of whether the generated code might accidentally reproduce GPL-licensed patterns from the training data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your responsibility&lt;/strong&gt; — Regardless of how the code was generated, &lt;strong&gt;you are responsible for the licenses in your project&lt;/strong&gt;. If an AI tool writes code that looks suspiciously like a well-known open source library, you should verify the original license and comply with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical advice for developers using AI coding tools
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't blindly accept large code blocks&lt;/strong&gt; from AI tools without reviewing them. If a suggestion looks like it could be copied from a specific project, check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run license auditing tools&lt;/strong&gt; on your project regularly — they won't catch AI-reproduced code directly, but they'll catch any dependencies you add.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be especially careful with unique or distinctive code patterns.&lt;/strong&gt; Generic patterns (a for loop, a REST endpoint) are fine. But if the AI outputs a complex algorithm that feels very specific, it might be reproducing someone's copyrighted work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the terms of service&lt;/strong&gt; of your AI tool. Most providers (Anthropic, OpenAI, GitHub) include clauses about code ownership and liability. Know what you're agreeing to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When in doubt, rewrite.&lt;/strong&gt; If you're unsure whether a generated block might have license implications, rewrite it in your own style. It's safer and you'll understand the code better anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This whole area will keep evolving as courts rule on pending cases and as AI tools mature. Stay informed — the rules might be very different a year from now.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ: Open Source Licensing for Solo Developers and Small Teams
&lt;/h2&gt;

&lt;p&gt;If you work alone, build your own apps, and rely on third-party libraries and services, this section is for you. These are the real questions that come up day to day — answered without the legal jargon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use React (MIT) in my closed-source commercial app?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes, completely.&lt;/strong&gt; MIT lets you use, modify, and distribute the code in commercial and closed-source projects. You just need to keep the copyright notice somewhere (usually a &lt;code&gt;LICENSES&lt;/code&gt; file or an "about" section in your app). You don't need to open your code or pay anyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  If I install a GPL library via npm, does my whole project become GPL?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It depends on whether you distribute your project.&lt;/strong&gt; If your app is a SaaS running on a server and you never deliver the code to anyone, you're technically not "distributing" — so the GPL doesn't force you to open your code. But heads up: if it were AGPL instead, you would have to.&lt;/p&gt;

&lt;p&gt;If you do distribute your app (upload to a store, package as a binary, ship to clients), then yes — your project becomes GPL.&lt;/p&gt;

&lt;p&gt;Practical advice: if your project is closed-source, &lt;strong&gt;avoid GPL dependencies&lt;/strong&gt; and look for MIT or Apache alternatives. In my experience, there's almost always a permissively licensed alternative for whatever you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use code from Stack Overflow in my project?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technically, you should give credit.&lt;/strong&gt; All content on Stack Overflow is published under CC BY-SA 4.0 (Creative Commons Attribution-ShareAlike). If you copy a code block, you should attribute it. For small snippets (a few lines), the real-world risk is minimal, but for substantial blocks, good practice is to credit the source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I change my project's license from MIT to GPL?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes, with caveats.&lt;/strong&gt; If you're the sole author, you can change the license whenever you want. Older versions you published under MIT stay MIT (anyone who already downloaded them keeps those rights), but new versions can be GPL.&lt;/p&gt;

&lt;p&gt;If you have external contributors, you need consent from &lt;strong&gt;every single one of them&lt;/strong&gt; to change the license — unless you set up a CLA (Contributor License Agreement) upfront that grants you those rights. That's why many large projects require a CLA before accepting contributions. It's also why the HashiCorp situation was possible — they owned the copyright, so they could change the license unilaterally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Firebase, Supabase, Stripe, or AWS SDKs impose license obligations?
&lt;/h3&gt;

&lt;p&gt;Client SDKs and libraries from these services are usually permissively licensed (MIT or Apache 2.0), so you can use them freely in closed-source projects. But &lt;strong&gt;always verify&lt;/strong&gt;: go to the SDK's GitHub repo and check the &lt;code&gt;LICENSE&lt;/code&gt; file. Don't assume the license based on the service name.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Terms of Service&lt;/strong&gt; (ToS) of the service itself are a separate thing — they define how you can use the platform, not the SDK code. I make it a habit to check both when integrating any third-party service into a project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I charge money for software that uses open source libraries?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Absolutely.&lt;/strong&gt; "Open source" does not mean "free of charge." You can charge for your software even if it uses open source libraries. What matters is respecting each license's conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MIT / Apache / BSD libraries:&lt;/strong&gt; Charge freely. Just give credit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LGPL libraries:&lt;/strong&gt; You can charge, but if you modify the library itself, those modifications must be open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPL libraries:&lt;/strong&gt; If you distribute your software, the whole project must be GPL — meaning your code must be accessible. You can still charge, but anyone who gets your software has the right to see and redistribute the source.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Can I change the license if I fork an open source project?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Depends on the original license:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MIT / BSD / Apache:&lt;/strong&gt; Yes. You can fork and relicense (even as closed-source), as long as you keep the original copyright notices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPL / AGPL:&lt;/strong&gt; No. Your fork must keep the same GPL/AGPL license. That's the entire point of copyleft — and that's exactly what happened with OpenTofu. They could fork Terraform because the code was still under MPL at the time, but they couldn't have forked it if the code had always been BSL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LGPL:&lt;/strong&gt; Direct modifications to the library must stay LGPL, but the project using it can be closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Which open source license matters when building a SaaS?
&lt;/h3&gt;

&lt;p&gt;This is the million-dollar question, and as someone who builds SaaS products, it's the first thing I check before picking any open source tool as a base. It depends on the project's license:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MIT / Apache / BSD:&lt;/strong&gt; Full freedom. Run it as SaaS, modify it, keep everything closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPL:&lt;/strong&gt; As long as it's purely SaaS and you don't distribute the code, you're technically not required to open it. But this is a gray area that generates a lot of debate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGPL:&lt;/strong&gt; You must publish your source code, including any modifications. This license was specifically designed for this scenario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BSL:&lt;/strong&gt; Check the specific terms. Usually you can't use it in commercial production during the restriction period without a separate commercial license.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Do open source licenses apply if I don't distribute my app?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Barely.&lt;/strong&gt; If the software is for personal or internal use and you don't distribute it or offer it as a service, most open source licenses don't impose obligations beyond keeping copyright notices. Even the GPL won't force you to publish code if you never distribute the software.&lt;/p&gt;

&lt;p&gt;The exception: if you run a network-accessible service (a website, an API, a backend) and use AGPL code, you must publish the source even though you're not "distributing" anything in the traditional sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who is responsible for licenses in AI-generated code (Copilot, Claude Code, Cursor)?
&lt;/h3&gt;

&lt;p&gt;This is the question everyone's asking right now. Short answer: &lt;strong&gt;you're responsible.&lt;/strong&gt; The code the AI generates is considered yours (check your tool's ToS to confirm), but if that code happens to reproduce patterns from a GPL-licensed project in the training data, you could theoretically be on the hook.&lt;/p&gt;

&lt;p&gt;Practical advice: use AI tools freely, but review the output. If something looks like it was lifted from a specific library, check the source. And always run your usual license auditing tools on the final project — AI-generated code isn't exempt from license compliance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Open Source License Should You Pick? Decision Tree
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Want maximum adoption?
├── Worried about patents?
│   ├── Yes → Apache 2.0
│   └── No → MIT
│
Want derivatives to stay free?
├── Is it a library?
│   ├── Yes → LGPL
│   └── No → GPL v3
│
Will your software run as SaaS?
├── Want contributors to give back?
│   ├── Yes → AGPL v3
│   └── No, but I want to protect my business → BSL
│
Want to protect your brand?
├── Yes → BSD 3-clause
│
Want open source + revenue from closed-source users?
└── Yes → Dual licensing (GPL/AGPL free + commercial option)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://choosealicense.com/" rel="noopener noreferrer"&gt;choosealicense.com&lt;/a&gt; — GitHub's interactive guide for picking a license&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tldrlegal.com/" rel="noopener noreferrer"&gt;tl;drLegal&lt;/a&gt; — License summaries in human-readable language&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fossa.com/" rel="noopener noreferrer"&gt;FOSSA&lt;/a&gt; — Automated license auditing for your dependencies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opensource.org/licenses/" rel="noopener noreferrer"&gt;OSI Approved Licenses&lt;/a&gt; — The official list of approved open source licenses&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt; — The Terraform fork born from a license change (a cautionary tale)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Remember that Terraform story from the beginning? HashiCorp built an incredible tool, grew a massive community, and then changed the license. The result: a split community, a competing fork, and years of trust gone in a single announcement.&lt;/p&gt;

&lt;p&gt;Licenses aren't an afterthought. They're a &lt;strong&gt;promise to your users and your community&lt;/strong&gt;. They define the relationship between your code and everyone who touches it.&lt;/p&gt;

&lt;p&gt;Whether you're pushing your first repo or maintaining a project with thousands of users, take 5 minutes to understand the license you're choosing. It's one of the few decisions in software that's genuinely hard to undo.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What license do you use in your projects and why? Have you ever run into a licensing issue that caught you off guard?&lt;/strong&gt; I'd love to hear your stories in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, hit ❤️ and share it with someone about to push their first repo without a license.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>licensing</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hexagonal Architecture in Plain JavaScript: Why I Use Objects as Interfaces (And Validate Them at Startup)</title>
      <dc:creator>Juan Isidoro García Cifuentes</dc:creator>
      <pubDate>Wed, 25 Feb 2026 16:37:15 +0000</pubDate>
      <link>https://dev.to/juanisidoro/why-i-use-plain-javascript-objects-as-interfaces-and-validate-them-at-startup-2adj</link>
      <guid>https://dev.to/juanisidoro/why-i-use-plain-javascript-objects-as-interfaces-and-validate-them-at-startup-2adj</guid>
      <description>&lt;p&gt;How do you enforce contracts between layers in plain JavaScript? This is how I implemented hexagonal architecture — ports and adapters — in a Node.js monolith without TypeScript.&lt;/p&gt;

&lt;p&gt;This is the system I found. It's been running in production since last summer without a single issue.&lt;/p&gt;

&lt;p&gt;If you're coming from the previous post in this series, I promised I'd talk about those interfaces with &lt;code&gt;throw new Error('Not implemented')&lt;/code&gt;. Here we are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with importing everything directly
&lt;/h2&gt;

&lt;p&gt;Early on, my code looked like every other Node.js project. Controllers imported Firestore directly. The customer service imported the WooCommerce API. Everything worked, but everything was glued together.&lt;/p&gt;

&lt;p&gt;The moment I realized this was a problem: I wanted to support shops without WooCommerce. Shops that only manage data from the app, no external CMS. I searched for where WooCommerce was used and found it hardcoded all over the place.&lt;/p&gt;

&lt;p&gt;If I wanted a shop to work without WooCommerce, I'd have to add &lt;code&gt;if/else&lt;/code&gt; checks everywhere. That wasn't going to scale.&lt;/p&gt;

&lt;p&gt;I needed a way to say: "this service needs something that can create customers, update them, and delete them — but it doesn't care if it's WooCommerce, Shopify, or nothing at all."&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing boundaries with plain objects
&lt;/h2&gt;

&lt;p&gt;The solution was what Hexagonal Architecture calls "ports." In my case, they're plain JavaScript objects — not classes — with methods that throw if you don't implement them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/core/ports/CustomerRepository.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomerRepositoryInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;createCustomer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;updateCustomer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 19 methods total&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No inheritance. No abstract class. It's a contract: "this is what I expect you to know how to do."&lt;/p&gt;

&lt;p&gt;The adapters, on the other hand, are classes — &lt;code&gt;FirestoreCustomerRepository&lt;/code&gt;, &lt;code&gt;WooCommerceCustomerAdapter&lt;/code&gt;. They implement the port's methods with real logic. Ports define the shape. Adapters do the work. That's the split.&lt;/p&gt;

&lt;p&gt;I have three types of ports per entity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt; — the write side (Firestore)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReadModel&lt;/strong&gt; — the read side (Meilisearch or optimized queries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMSAdapter&lt;/strong&gt; — the connection to the external CMS (WooCommerce, or nothing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one defines its contract separately. The application service only knows the ports, never the concrete implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  But JavaScript won't tell you if a method is missing...
&lt;/h2&gt;

&lt;p&gt;This is the real problem. You can build an adapter, implement 18 out of 19 methods, and JavaScript says nothing. Everything works great until someone calls the missing method in production.&lt;/p&gt;

&lt;p&gt;My solution: &lt;code&gt;validateImplementation()&lt;/code&gt;. Each port exports the list of required methods and a function that checks if an implementation has all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/core/ports/CustomerRepository.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_METHODS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerRepositoryInterface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;implementation&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;missingMethods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_METHODS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missingMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`CustomerRepository missing methods: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;missingMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: &lt;code&gt;REQUIRED_METHODS&lt;/code&gt; is derived from the interface object with &lt;code&gt;Object.keys()&lt;/code&gt;. If I add a method to the port, it's automatically required in validation. No duplicate list to maintain — that was an early mistake I already fixed.&lt;/p&gt;

&lt;p&gt;Now here's the important part — each adapter validates itself when Node.js loads the module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bottom of FirestoreCustomerRepository.js, BEFORE module.exports&lt;/span&gt;

&lt;span class="nf"&gt;validateImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FirestoreCustomerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It validates the &lt;code&gt;.prototype&lt;/code&gt; of the class against the port's contract. Port is a plain object that defines the contract. Adapter is a class that implements it. The validation bridges the two.&lt;/p&gt;

&lt;p&gt;If I forget a method, the app won't start. Not at 3am when a customer places an order. At startup. When I'm at my desk and can fix it in two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency injection without a framework
&lt;/h2&gt;

&lt;p&gt;With ports defined, application services receive their dependencies through the constructor. They don't know if there's Firestore, PostgreSQL, or a mock behind them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/application/services/CustomerService.js&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerReadModel&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerReadModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cmsAdapter&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cmsAdapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addressRepository&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addressRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventBus&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Inversify. No IoC container. Just an object with dependencies.&lt;/p&gt;

&lt;p&gt;And where does it all come together? In a factory that acts as the composition root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/application/services/index.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCustomerServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependencies&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customerRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerReadModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;cmsAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addressRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&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;customerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;customerRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerReadModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cmsAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addressRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventBus&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;addressService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerAddressService&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;addressRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customerService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addressService&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you decide which concrete adapter goes to each port. The service never knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Null Object that saved me
&lt;/h2&gt;

&lt;p&gt;Back to the original problem: supporting shops without WooCommerce.&lt;/p&gt;

&lt;p&gt;The bad option was adding &lt;code&gt;if (shop.hasCMS)&lt;/code&gt; everywhere the adapter was called. The good option was creating a &lt;code&gt;StandaloneCustomerAdapter&lt;/code&gt; — it implements the exact same port, but does nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/infrastructure/adapters/StandaloneCustomerAdapter.js&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandaloneCustomerAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;syncCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// No CMS, nothing to sync&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;fetchAllCustomers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Empty generator — no external customers to fetch&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Same validation pattern as every other adapter&lt;/span&gt;
&lt;span class="nf"&gt;validateImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StandaloneCustomerAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a factory that picks the right one based on the shop's config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/customers/infrastructure/adapters/CustomerAdapterFactory.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCustomerAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmsType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmsType&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;woocommerce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getWooCommerceCustomerAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getStandaloneCustomerAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unsupported CMS type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cmsType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CustomerService&lt;/code&gt; doesn't know which adapter it gets. And it doesn't care. That's the whole point.&lt;/p&gt;

&lt;p&gt;Tomorrow, if I want to add PrestaShop, I create a new adapter, register it in the factory, and zero application services change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;It's not perfect. Without TypeScript you lose autocomplete and types on hover. That hurts day to day.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;validateImplementation()&lt;/code&gt; only checks that methods exist as functions — it doesn't validate signatures, return types, or anything beyond "is the method there?" It's a basic safety net, not a type system.&lt;/p&gt;

&lt;p&gt;Sometimes I wonder if I should have started with TypeScript from the beginning.&lt;/p&gt;

&lt;p&gt;But for a solo dev, the simplicity of "an object with methods" outweighs full type safety. I don't have a team of 10 where someone can break a contract without realizing it. It's just me, and validating at startup is enough to catch my own mistakes.&lt;/p&gt;

&lt;p&gt;It's been in production since last summer. Not a single missing method at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Now we have ports separating the write side from the read side, and adapters implementing them. But there's a missing piece — when I write to Firestore, how does Meilisearch find out?&lt;/p&gt;

&lt;p&gt;The answer is the event system. And it has a two-level naming trick — domain events (&lt;code&gt;ecommerce.*&lt;/code&gt;) and infrastructure events (&lt;code&gt;firestore.*&lt;/code&gt;) — that solved the coupling problem in a way I didn't expect.&lt;/p&gt;

&lt;p&gt;That's the next post.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Do you enforce interfaces at runtime, or does TypeScript give you enough confidence?&lt;/em&gt;&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>node</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I Ended Up Doing CQRS in a Node.js Monolith (Without Planning It)</title>
      <dc:creator>Juan Isidoro García Cifuentes</dc:creator>
      <pubDate>Fri, 20 Feb 2026 09:47:25 +0000</pubDate>
      <link>https://dev.to/juanisidoro/how-i-ended-up-doing-cqrs-in-a-nodejs-monolith-without-planning-it-k89</link>
      <guid>https://dev.to/juanisidoro/how-i-ended-up-doing-cqrs-in-a-nodejs-monolith-without-planning-it-k89</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📌 This is part 1 of a series on building event-driven architecture in a Node.js monolith. No microservices, no Kafka — just patterns that actually work at scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn't set out to implement CQRS. I'm a solo developer building a sync platform for e-commerce stores. Node.js, Express, Firestore. Nothing fancy.&lt;/p&gt;

&lt;p&gt;But at some point, I needed search. Real search. With filters, facets, and sorting. And Firestore just... couldn't.&lt;/p&gt;

&lt;p&gt;So I made a decision that changed my entire architecture without me even realizing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it broke
&lt;/h2&gt;

&lt;p&gt;Here's the thing about Firestore — it's great for writes. Atomic operations, real-time listeners, scales without thinking. I was happy with it.&lt;/p&gt;

&lt;p&gt;Then the frontend came along and asked for this: "I need to search products by name, filter by three categories, sort by price, and show me how many results are in each status."&lt;/p&gt;

&lt;p&gt;If you've tried doing this in Firestore, you know the pain. Composite indexes for every combination. Client-side filtering. Queries that kinda work but not really. I spent two days trying to make it happen before accepting it wasn't going to.&lt;/p&gt;

&lt;p&gt;I needed a search engine. I went with Meilisearch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two stores, one problem
&lt;/h2&gt;

&lt;p&gt;The moment I added Meilisearch, I had two data stores. Products lived in Firestore (my source of truth) and also in Meilisearch (for the frontend to query).&lt;/p&gt;

&lt;p&gt;At first it was messy. Some parts of the code read from Firestore, some from Meilisearch. The logic for "where do I get this data?" was scattered everywhere.&lt;/p&gt;

&lt;p&gt;So I did what any developer does when things get messy — I drew a line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing the line
&lt;/h2&gt;

&lt;p&gt;I created two interfaces. Simple JavaScript objects with methods that throw 'Not implemented'. No TypeScript, no abstract classes. Just contracts.&lt;/p&gt;

&lt;p&gt;The write side — &lt;strong&gt;ProductRepository&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProductRepositoryInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;saveProduct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stockData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;updatePrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;priceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;deleteProduct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// 12 methods total&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The read side — &lt;strong&gt;ProductReadModel&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProductReadModelInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;listProducts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;searchProducts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;getProductStats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;checkSKUExists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;excludeProductId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// 5 methods total&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12 write methods. 5 read methods. Zero overlap.&lt;/p&gt;

&lt;p&gt;That's it. That's CQRS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters (practically)
&lt;/h2&gt;

&lt;p&gt;This isn't about architecture buzzwords. It's about not going crazy when your codebase grows.&lt;/p&gt;

&lt;p&gt;Before the split, I had this constant question: "where does this data come from?" After the split, it's obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mutating something? → &lt;code&gt;ProductRepository&lt;/code&gt; → Firestore&lt;/li&gt;
&lt;li&gt;Querying something? → &lt;code&gt;ProductReadModel&lt;/code&gt; → Meilisearch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each store gets to be good at what it's good at:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Write Side (Firestore)&lt;/th&gt;
&lt;th&gt;Read Side (Meilisearch)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Good at&lt;/td&gt;
&lt;td&gt;Atomic writes, real-time&lt;/td&gt;
&lt;td&gt;Full-text search, facets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data shape&lt;/td&gt;
&lt;td&gt;Normalized, nested&lt;/td&gt;
&lt;td&gt;Denormalized, flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Eventual&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The write side doesn't import Meilisearch. The read side doesn't import Firestore. They don't know about each other. And that's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: eventual consistency
&lt;/h2&gt;

&lt;p&gt;I won't pretend this is free. There's a cost.&lt;/p&gt;

&lt;p&gt;When I save a product to Firestore, it doesn't appear in search results immediately. There's a projection system that picks up the change and syncs it to Meilisearch. Usually takes a few seconds.&lt;/p&gt;

&lt;p&gt;For a management platform where admins are editing products, a few seconds is fine. For a customer-facing checkout page? Probably not. Know your context.&lt;/p&gt;

&lt;p&gt;I'll go deeper into how the projection system works in a later post. For now, just know it exists and it's the glue between the two sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing nobody tells you
&lt;/h2&gt;

&lt;p&gt;I read about CQRS months after I implemented it. I was looking at my codebase one day and thought "wait, this is that pattern from the DDD book."&lt;/p&gt;

&lt;p&gt;I think that's actually the best way to learn architecture patterns. Not by reading about them first and trying to force them into your code. But by solving a real problem and then discovering the pattern has a name.&lt;/p&gt;

&lt;p&gt;The best patterns don't feel like patterns. They feel like common sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;In the next post, I'll talk about those &lt;code&gt;throw new Error('Not implemented')&lt;/code&gt; interfaces. Why I use plain JavaScript objects as port definitions, how I validate them at startup, and why I chose this over TypeScript interfaces for my use case.&lt;/p&gt;

&lt;p&gt;If you've ever split your reads and writes — on purpose or by accident — I'd love to hear what triggered it. Was it search? Was it performance? Something else?&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI agents are wasting 90% of tokens on your navbar — I built a protocol to fix it</title>
      <dc:creator>Juan Isidoro García Cifuentes</dc:creator>
      <pubDate>Thu, 19 Feb 2026 08:31:06 +0000</pubDate>
      <link>https://dev.to/juanisidoro/ai-agents-are-wasting-90-of-tokens-on-your-navbar-i-built-a-protocol-to-fix-it-21mm</link>
      <guid>https://dev.to/juanisidoro/ai-agents-are-wasting-90-of-tokens-on-your-navbar-i-built-a-protocol-to-fix-it-21mm</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;I was spending hundreds of dollars on AI API calls. Half of it wasn't buying me useful information — it was paying to process navigation bars, cookie banners, and inline scripts.&lt;/p&gt;

&lt;p&gt;A typical e-commerce product page? &lt;strong&gt;47,000 tokens&lt;/strong&gt;. The useful product information? &lt;strong&gt;~300 tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In many cases, over 90–99% of the processed tokens are structural or navigational noise.&lt;/p&gt;

&lt;p&gt;I kept thinking: what if the server could just... give the agent what it needs?&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter MAKO
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MAKO&lt;/strong&gt; (Markdown Agent Knowledge Optimization) is an open protocol that uses standard HTTP content negotiation to serve AI-optimized content.&lt;/p&gt;

&lt;p&gt;The idea is dead simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An AI agent sends &lt;code&gt;Accept: text/mako+markdown&lt;/code&gt; in its request header&lt;/li&gt;
&lt;li&gt;The server detects this header&lt;/li&gt;
&lt;li&gt;Instead of HTML, it responds with structured markdown + YAML frontmatter&lt;/li&gt;
&lt;li&gt;The agent gets exactly what it needs in ~280 tokens&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No new endpoints. No special APIs. Just HTTP, the way it was designed.&lt;/p&gt;

&lt;p&gt;MAKO does not replace HTML. Browsers still receive HTML.&lt;br&gt;
Only agents explicitly requesting &lt;code&gt;text/mako+markdown&lt;/code&gt; receive the optimized version.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a MAKO Response Looks Like
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;mako&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product&lt;/span&gt;
&lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nike&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Air&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Max&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;90"&lt;/span&gt;
&lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-02-10"&lt;/span&gt;
&lt;span class="na"&gt;tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;280&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;en&lt;/span&gt;
&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mid-range&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shoe,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;47%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;off"&lt;/span&gt;
&lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/running-shoes&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Browse&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shoes"&lt;/span&gt;
&lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add_to_cart&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shopping&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cart"&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/cart/add&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Nike Air Max 90&lt;/span&gt;

&lt;span class="c1"&gt;## Key Facts&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;€79.99 (was €149.99, -47%)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;In stock (23 available)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sizes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;38-46&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Rating&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4.3/5 (234 reviews)&lt;/span&gt;

&lt;span class="c1"&gt;## Overview&lt;/span&gt;
&lt;span class="s"&gt;Casual running shoe, mid-range. Leather/mesh upper.&lt;/span&gt;
&lt;span class="na"&gt;Competitors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Adidas Ultraboost (€129), NB 1080 (€139).&lt;/span&gt;
&lt;span class="na"&gt;Strong point: price. Weak point&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;narrow fit per reviews.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Not Just Scrape HTML?
&lt;/h2&gt;

&lt;p&gt;Scraping pushes complexity to the client.&lt;br&gt;
MAKO moves structure to the source.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
Agent → Parse DOM → Remove noise → Infer structure&lt;/p&gt;

&lt;p&gt;You get:&lt;br&gt;
Agent → Receive structured semantic content&lt;/p&gt;

&lt;p&gt;Lower token cost. Clear semantic boundaries. Deterministic machine-readable structure.&lt;/p&gt;

&lt;p&gt;These numbers come from real-world pages measured using GPT tokenization.&lt;br&gt;
Exact savings depend on site structure, but the pattern is consistent.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page Type&lt;/th&gt;
&lt;th&gt;HTML Tokens&lt;/th&gt;
&lt;th&gt;MAKO Tokens&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E-commerce product&lt;/td&gt;
&lt;td&gt;47,000&lt;/td&gt;
&lt;td&gt;280&lt;/td&gt;
&lt;td&gt;99.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog article&lt;/td&gt;
&lt;td&gt;35,000&lt;/td&gt;
&lt;td&gt;670&lt;/td&gt;
&lt;td&gt;98.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;38,000&lt;/td&gt;
&lt;td&gt;980&lt;/td&gt;
&lt;td&gt;97.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Landing page&lt;/td&gt;
&lt;td&gt;110,000&lt;/td&gt;
&lt;td&gt;640&lt;/td&gt;
&lt;td&gt;99.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can see a real generated MAKO page here:&lt;br&gt;
👉 &lt;a href="https://makospec.vercel.app/en/p/aZo89_kQ" rel="noopener noreferrer"&gt;https://makospec.vercel.app/en/p/aZo89_kQ&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Implement It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Using the SDK
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mako-spec/js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MakoGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MakoParser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mako-spec/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Generate a MAKO file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MakoGenerator&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;mako&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nike Air Max 90&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;# Nike Air Max 90&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s1"&gt;Casual running shoe...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;More running shoes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add to cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Parse a MAKO file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MakoParser&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;makoContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "Nike Air Max 90"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Express Middleware (3 lines)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;makoMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mako-spec/js/middleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makoMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return your MAKO content for this URL&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchMakoForUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Free Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://makospec.vercel.app/en/score" rel="noopener noreferrer"&gt;MAKO Score&lt;/a&gt;&lt;/strong&gt; — Audit any site's AI-readiness (0-100). Checks 30 criteria across 4 categories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://makospec.vercel.app/en/analyzer" rel="noopener noreferrer"&gt;MAKO Analyzer&lt;/a&gt;&lt;/strong&gt; — Paste any URL and see the generated MAKO output + token savings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress Plugin&lt;/strong&gt; — Full WooCommerce support, AI generation with your own API key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How MAKO Fits In
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;MAKO Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;llms.txt&lt;/td&gt;
&lt;td&gt;1 file per site&lt;/td&gt;
&lt;td&gt;MAKO: 1 file per page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare MD&lt;/td&gt;
&lt;td&gt;Auto-converted HTML&lt;/td&gt;
&lt;td&gt;MAKO: Semantically optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema.org&lt;/td&gt;
&lt;td&gt;For search engines&lt;/td&gt;
&lt;td&gt;MAKO: For AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebMCP&lt;/td&gt;
&lt;td&gt;Actions only&lt;/td&gt;
&lt;td&gt;MAKO: Content + actions + context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MAKO is &lt;strong&gt;complementary&lt;/strong&gt; — it works alongside all of these.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;AI agents are becoming a first-class web audience.&lt;/p&gt;

&lt;p&gt;If search engines needed HTML, AI agents need structured representations.&lt;/p&gt;

&lt;p&gt;The web evolved once for browsers. It's evolving again for agents.&lt;/p&gt;

&lt;p&gt;Visually, the difference looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional Web Flow&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser   → HTML → Rendered Interface → Human
AI Agent  → HTML → DOM Parsing → Layout + Navigation + Script Overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With MAKO&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser   → HTML → Rendered Interface → Human
AI Agent  → MAKO → Structured Semantic Content → Deterministic Extraction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;Everything is Apache 2.0 licensed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spec:&lt;/strong&gt; &lt;a href="https://github.com/juanisidoro/mako-spec" rel="noopener noreferrer"&gt;github.com/juanisidoro/mako-spec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS SDK:&lt;/strong&gt; &lt;a href="https://npmjs.com/package/@mako-spec/js" rel="noopener noreferrer"&gt;npmjs.com/package/@mako-spec/js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI:&lt;/strong&gt; &lt;a href="https://npmjs.com/package/@mako-spec/cli" rel="noopener noreferrer"&gt;npmjs.com/package/@mako-spec/cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress:&lt;/strong&gt; &lt;a href="https://github.com/juanisidoro/mako-wp" rel="noopener noreferrer"&gt;github.com/juanisidoro/mako-wp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://makospec.vercel.app" rel="noopener noreferrer"&gt;makospec.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love your feedback — on the protocol design, the scoring system, or anything else. Star the repo if you find it useful.&lt;/p&gt;

&lt;p&gt;How are you handling web content extraction in your AI apps right now? Drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
