<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Baby CTO: Toolkit]]></title><description><![CDATA[Dive deep into a treasure trove of tech tools and frameworks. Assess their fit in a startup landscape, considering factors like cost, scalability, maintainability, and more.]]></description><link>https://www.baby-cto.com/s/toolkit</link><image><url>https://substackcdn.com/image/fetch/$s_!u3-p!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4c9d8fc-b387-47a4-b77f-c2aa0a303dc7_619x619.png</url><title>Baby CTO: Toolkit</title><link>https://www.baby-cto.com/s/toolkit</link></image><generator>Substack</generator><lastBuildDate>Mon, 11 May 2026 04:04:05 GMT</lastBuildDate><atom:link href="https://www.baby-cto.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Rémy]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[babycto@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[babycto@substack.com]]></itunes:email><itunes:name><![CDATA[Rémy]]></itunes:name></itunes:owner><itunes:author><![CDATA[Rémy]]></itunes:author><googleplay:owner><![CDATA[babycto@substack.com]]></googleplay:owner><googleplay:email><![CDATA[babycto@substack.com]]></googleplay:email><googleplay:author><![CDATA[Rémy]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Documentation Archaeology: How to Extract Knowledge from Abandoned Codebases with AI]]></title><description><![CDATA[Developers are no strangers to being thrown in a wild project where their ability to understand technical debt will be key to their survival and mental health. LLMs are amazing tools for this use-case]]></description><link>https://www.baby-cto.com/p/documentation-archaeology-how-to-with-ai-llm</link><guid isPermaLink="false">https://www.baby-cto.com/p/documentation-archaeology-how-to-with-ai-llm</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sat, 02 Aug 2025 09:42:25 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/99fa6ac8-90b4-41c4-9de9-1c49cdfcca15_1408x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You need to work on a legacy project. The engineer who knew all about it left the company a year ago. Because the deadline was too tight, they didn&#8217;t take time to write a documentation. They also didn&#8217;t take a step back on the architecture and simply piled up technical debt. Welcome to what is essentially the digital version of Man versus Wild!</p><p>The good news is: this is a strong use-case for generative AI. Many of you will have strong reservations against clankers, but the general rule of thumb with LLMs is that they are good to make something long shorter.</p><p>More specifically, if you feed them some code they will perfectly understand what it does and most likely even why it does it. It also has strong knowledge of all business areas, so anything that isn&#8217;t pure company jargon should be picked up as well.</p><p>In this article, we will explore the less obvious techniques that will give you superhuman abilities to jump into any project that you have never seen before.</p><p></p><p><em>tldr/spoilers:</em></p><pre><code><em>pfff src/**/*.py | llm -m 'gemini-2.5-pro' -s 'Please write a complete documentation of this project. I want a high-level overview of the main user flows. For each flow, generate proper Mermaid diagrams explaining the communication between all the different parties. Then go into the detail of each flow and explain the specific business decisions taken, edge cases, special rules, etc. For each step of the flow, tell me roughly where to look in the code in case I want to change something.'</em></code></pre><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Sounds good? More ideas like this in future newsletters!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h1>Workflow setup</h1><p>We&#8217;ll use two main tools for this:</p><ul><li><p><a href="https://pypi.org/project/llm/">llm</a> from <a href="https://simonwillison.net/">Simon Willison</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> gives a great CLI interface to various LLMs of the market</p></li><li><p><a href="https://pypi.org/project/pfff/">pfff</a> from this author, which is simply a way to generate a context with source code in and pipe it into <code>llm</code></p></li></ul><p>Given that they are both Python tool, I can only recommend to use <a href="https://docs.astral.sh/uv/getting-started/installation/#shell-autocompletion">uvx</a> alongside with your shell&#8217;s aliasing system.</p><p>For <code>fish</code> users, that would be:</p><pre><code>alias -s llm='uvx --with llm-gemini --with llm-claude-3 llm'
alias -s pfff='uvx pfff'</code></pre><p>For <code>bash</code>, add to your <code>~/.profile</code> (or the relevant file to your configuration):</p><pre><code>alias llm='uvx --with llm-gemini --with llm-claude-3 llm'
alias pfff='uvx pfff'</code></pre><p>Note how we add <code>llm-gemini</code> and <code>llm-claude-3</code> as dependencies to llm. This is because there are <a href="https://llm.datasette.io/en/stable/plugins/directory.html">many plugins</a> for many providers.</p><p>If you are going to get only one plugin, you should get <code>llm-gemini</code>. This is by far the most useful LLM for the task at hand for a very simple reason. While most top-of-line LLMs have the same exact capabilities, Gemini shines with a 1 million token context window. This is big enough to fit a lot of entire codebases, and this will come in handy.</p><p>Once the plugin is installed, reach to <a href="https://aistudio.google.com/">Google&#8217;s AI Studio</a> to grab an API key then run:</p><pre><code>llm keys set gemini</code></pre><p>Once this is done, you should be able to run something through Gemini, for example:</p><pre><code>&#10095; echo "What is the answer to Life, the Universe and Everything? Give me the answer in JSON and only JSON." | llm -m gemini-2.5-pro
```json
{
  "question": "What is the answer to the ultimate question of Life, the Universe and Everything?",
  "answer": 42
}
```</code></pre><p>From this point on, you&#8217;re ready to go!</p><p>As a bonus however, you can have a look at the following tools:</p><ul><li><p><a href="https://mermaid.js.org/">Mermaid</a>, a lib/tool for diagrams embeddable in Markdown (and supported by GitHub).</p></li><li><p><a href="https://typora.io/">Typora</a>, a nice desktop Markdown editor, which also happens to support Mermaid. Use any editor you want of course, but make sure to have one at hand for the rest of the article.</p></li></ul><h1>Project documentation</h1><p>The LLM being a translation system, we often use it to translate a specification into code, with more or less effect. But on the other hand the code <em>is</em> the ultimate specification, which is fairly easy to translate back into English.</p><h2>Good documentation</h2><p>In order to get something useful, you first need to understand what it is that you seek.</p><p>A good documentation takes you through a story. Not of a princess sleeping in the highest room of the highest tower, but of the various user and data flows that compose the application. A transverse view if you prefer.</p><p>Apart from obscure Doxygen-generated documentations, all popular open source code essentially gives you a set of things:</p><ul><li><p>A &#8220;Getting Started&#8221; guide, whose job is to get you doing something useful within 3 minutes, beyond which point you would lose patience and try another tool</p></li><li><p>A set of &#8220;Tutorials&#8221; or &#8220;Guides&#8221;, which will cover specific use-cases</p></li><li><p>And the &#8220;Reference&#8221; that goes into the nitty gritty details of how individual functions or pieces of function work</p></li></ul><p>The &#8220;Getting Started&#8221; does not usually make sense in a corporate project, given that it has one instance and that&#8217;s it. It&#8217;s already running, you can observe it, not a problem.</p><p>The &#8220;Reference&#8221; well, you will see later but essentially that is not the biggest help at the moment.</p><p>Which leaves you with the topic-centric &#8220;Guides&#8221;. This is what you&#8217;re going to look at generating. What you want to know is, for each &#8220;story&#8221;:</p><ul><li><p>Who speaks to whom in which order. This is what <a href="https://en.wikipedia.org/wiki/Sequence_diagram">sequence diagrams</a> are for, and they are <a href="https://mermaid.js.org/syntax/sequenceDiagram.html">entirely supported</a> by Mermaid</p></li><li><p>Why this exists in a first place</p></li><li><p>Where to find it in the code</p></li><li><p>What are important implementation details that you should be aware of</p></li></ul><p>All you need to do to get this, is to ask :)</p><h2>From scratch</h2><p>Let&#8217;s imagine that at this point, you have no useful documentation. Provided that your project is reasonably small (less than about 100k lines of code) and managed in Git, the first thing you need is to figure a way to list all useful code files.</p><p>A very obvious approach might be something like:</p><pre><code>ls src/**/*.py</code></pre><p>You might otherwise want to look at all the non-binary files in Git:</p><pre><code>git ls-tree -r --name-only HEAD | xargs -I{} sh -c 'git show HEAD:"{}" | grep -Iq . &amp;&amp; echo "{}"'</code></pre><p>You are doing an important job of curating the context for the LLM: picking the right files, as exhaustive as possible, without also throwing huge useless content, confusing or contradictory information, etc.</p><p>In the next part we&#8217;ll be piping this into the LLM and you&#8217;ll start seeing results. If you are happy with the results it&#8217;s great, otherwise you might want to come back and revise the list of files to make it more relevant and/or try to make it fit into the context window if you exceeded it.</p><p>But don&#8217;t overthink it. Do something quick and dirty first. If you like it, go to the next step. And come back only if it fails.</p><p>That&#8217;s where <code>pfff</code> comes into action. It&#8217;s a very small tool whose sole purpose is to print the content of all the files you provided, alongside with their name so that the LLM can get a sense of the project&#8217;s structure.</p><p>Try it out:</p><pre><code>pfff src/**/*.py</code></pre><p>You should end up with your terminal full of your source code. That&#8217;s what you will be sending to the LLM.</p><p>Now let&#8217;s send it with the question:</p><pre><code>pfff src/**/*.py | llm -m 'gemini-2.5-pro' -s 'Please write a complete documentation of this project. I want a high-level overview of the main user flows. For each flow, generate proper Mermaid diagrams explaining the communication between all the different parties. Then go into the detail of each flow and explain the specific business decisions taken, edge cases, special rules, etc. For each step of the flow, tell me roughly where to look in the code in case I want to change something.'</code></pre><p>Adjust the prompt if needed, this one should give you a good first draft. You will receive in the output a long Markdown file containing the long-lost documentation of your project!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kfuz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kfuz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 424w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 848w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 1272w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kfuz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png" width="648" height="453.95604395604397" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1020,&quot;width&quot;:1456,&quot;resizeWidth&quot;:648,&quot;bytes&quot;:224958,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.baby-cto.com/i/169826783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kfuz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 424w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 848w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 1272w, https://substackcdn.com/image/fetch/$s_!kfuz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f2c272d-25e0-4b73-b59f-0fa4db6411ef_1975x1384.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Auto-generated documentation for the <a href="https://github.com/cloud-hypervisor/fuse-backend-rs">fuse-backend-rs</a> open source project, which does exactly what I want but whose documentation is still a bit lacking</figcaption></figure></div><p>Copy/paste it into your favorite Markdown editor, you should be able to see all the lovely Mermaid flows and explanation of what is happening under the hood.</p><h2>Keeping the documentation up-to-date</h2><p>What is fantastic with this process is that you can also use it to keep the documentation up-to-date. Nothing easier:</p><pre><code>pfff src/**/*.rs README.md Cargo.toml | llm -m gemini-2.5-pro -s 'Give me an updated version of the README which reflects the state of the code'</code></pre><p>You can integrate other ideas into your prompts, depending on the expected results:</p><ul><li><p>&#8220;Fix all the docstrings that no longer match what the code actually does, or those who are incomplete. Do not rewrite text uselessly, only change things that need changing. Only give me the changed bits.&#8221;</p></li><li><p>&#8220;Compare the documentation with the current state of the code. Add sections that do not exist yet and adjust existing sentences that are inconsistent with the reality of the code. Avoid minor adjustments. Only give me the changed bits.&#8221;</p></li></ul><h2>Figuring the why</h2><p>More often than not you will encounter functions who remain mysterious to you. The previous concepts can be laser-focused onto a specific part of the code. For example, you&#8217;re trying to understand how something specific works in the Linux kernel, which is millions of code thick. From the kernel&#8217;s root folder:</p><pre><code>pfff fs/{ext4,fuse}/**/*.c | llm -m gemini-2.5-pro -s "The FUSE system has a lookup count system. From the implementer's point of view, what should I know? And which opcodes affect it?"</code></pre><p>And there you go. A straight answer from one of the most massive pieces of code that you will ever see.</p><h1>Debugging</h1><p>Another way LLMs are surprisingly efficient is in the unfolding of bugs. That one is not fool-proof, but you can still get very interesting results that will certainly help you to get going.</p><p>Let&#8217;s say that you have a weird bug. You open up your network inspector and grab the query that seems to be the issue:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CPBq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CPBq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 424w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 848w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 1272w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CPBq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png" width="482" height="507.0012062726176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:872,&quot;width&quot;:829,&quot;resizeWidth&quot;:482,&quot;bytes&quot;:151815,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.baby-cto.com/i/169826783?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CPBq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 424w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 848w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 1272w, https://substackcdn.com/image/fetch/$s_!CPBq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eec68be-9e0a-4fbf-b73f-2095e8c72216_829x872.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Browsers usually have a &#8220;Copy as cURL&#8221; option to let you reproduce the issue in your shell later on</figcaption></figure></div><p>Then run it in your terminal, copy/paste the command + the output at the same time, and feed that again into the llm (I&#8217;ll use <code>pbpaste</code> here to paste the text easily, yes you can get it on Linux as well):</p><pre><code>begin pbpaste; pfff **/*/*.py; end | llm -m gemini-2.5-pro -s "I'm getting an error in this query, how come?"</code></pre><p>You can usually ask more information than this, for example:</p><ul><li><p>&#8220;Give me the steps to reproduce the bug&#8221;</p></li><li><p>&#8220;What would be a successful outcome?&#8221;</p></li></ul><p>The actual diagnostic of the bug is often wrong or at least misleading. However the explanation of what happens and/or how to reproduce the bug is very helpful.</p><p>Here the example is using cURL, but you can of course get your information from somewhere else. For example a suspicious stack trace from Sentry, a more or less accurate description from the customer support, etc.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/p/documentation-archaeology-how-to-with-ai-llm?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">DO NOT SHARE this post if you want to be the only one looking smart at work</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/p/documentation-archaeology-how-to-with-ai-llm?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.baby-cto.com/p/documentation-archaeology-how-to-with-ai-llm?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><h1>Conclusion</h1><p>LLMs are very useful tools when used in the right way. Here you are shown how to leverage Gemini&#8217;s 1 million token context window in order to be able to dive quickly and efficiently into legacy projects. You were even shown how to curate your context to get interesting output out of behemoths such as the Linux kernel.</p><p>This comes a bit against current practices such as coding agents (see Cursor, Windsurf, Junie, etc). What makes them useful is their ability to interact with the real world without a human in the loop. But when it comes to efficiency, if a LLM can one-shot a given task&#8212;such as the kind of tasks showcased here&#8212;then you&#8217;re much better off piping everything at once rather than waiting 10 minutes for the agent to do its job.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Co-creator of <a href="https://www.djangoproject.com/">Django</a> and extremely active user of LLMs with a ton of interesting takes on his blog</p></div></div>]]></content:encoded></item><item><title><![CDATA[100 Ways LLMs can Boost Your Business]]></title><description><![CDATA[LLMs are not just about chat. All professions can reap productivity increase on various tasks. The only limit is your imagination!]]></description><link>https://www.baby-cto.com/p/100-ways-llms-can-boost-your-business</link><guid isPermaLink="false">https://www.baby-cto.com/p/100-ways-llms-can-boost-your-business</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sat, 13 Jul 2024 06:01:22 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f665c94e-eef8-49f2-bbf9-8d98ccc2ea9a_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>LLMs certainly are a breakthrough in terms of natural language processing. However the real spark that turned to world mad is ChatGPT. Before it, you could still use GPT-3, but few people outside of specialists did. It&#8217;s when the chat form factor appeared that the general public started to realize the power of LLMs.</p><p>Unfortunately, chat &#8212; or at least passing as intelligent humans &#8212; is not the main strength of this technology, which is rather a sort of elaborate parser/translator. As such, there is a million ways you could integrate a LLM into your business at different levels, optimizing 10% of someone&#8217;s job here and there.</p><p>To prove this point, today we&#8217;ll explore 100 use cases that stand besides the stereotypical uses of LLMs to imagine what you could truly do in a wide range of industries, provided a bit of brain juice and a few lines of code.</p><h1>Development and Project Management</h1><h2>Automated compliance checks in code or documents</h2><p>Any company beyond a few dozen employees ends up forced to draft policies, processes and rules that must be followed. Some of those require a big picture thinking, but some of them are precise checkpoints that can be easily checked in text-based outputs: source code, contracts, commercial propositions, etc. A series of robots could entirely make sure that the bulk of policies is indeed applied throughout the company.</p><h2>Programming language conversion</h2><p>As said  in introduction, LLMs are great at translating. But while this works amazingly for human-to-human languages, it also works quite well for programming languages. Typically, you can take any API vendor documentation in any language, get the example snippets and convert them into your current language. This also works within a given programming language to replace a specific library by another one that has equivalent features but different structure.</p><h2>Detect bug reports from user reviews</h2><p>It becomes easy to apply <a href="https://en.wikipedia.org/wiki/Linus%27s_law">Linus&#8217;s law</a>: &#8220;given enough eyeballs, all bugs are shallow&#8221;. If your product is meeting a certain level of success, people will inexorably start complaining about their frustractions online: through social media, app store reviews and so forth. Using a LLM, you can parse the whole lot of those reviews to detect if any of them actually describe a potential bug that you should care about.</p><h2>Validate business strategies against doctrine</h2><p>It is no secret that I am a fan of <a href="https://learnwardleymapping.com/">Wardley Maps</a>. The only issue being: the source material is very long and complex. A potential use for LLMs (especially long context ones) is to be able to assist you in the map creation but most of all to check that your predictions and projections are actually taking into account all the rules from the 800 pages of the book.</p><h2>R&amp;D progress audit</h2><p>It is always tedious to document R&amp;D as the nature of it asks to iterate rapidly between various experiments. However if you were to centralize all your results in a semi-formal way, you can imagine have a LLM take over this reporting process and generate exact day-by-day reports of who did what, what are the conclusions and what are the next things being tested. Extremely convenient in the case of grant justification as well.</p><h2>Task break-down and planning</h2><p>Why are developers always so late? Sometimes, it&#8217;s simply unforseeable problems popping up, but most of the time &#8212; and especially for juniors &#8212; it&#8217;s because they fail to decompose the tasks that they have not already done. If you never did something, your brain will probably ignore all the sub-tasks that you will have to accomplish. A LLM could be a good help to break down a given task until all the steps and dependencies are clear.</p><h2>Natural language programming</h2><p>Instead of having to code a specific behavior from a software component (email filter, automation platform, data ingestion platform, etc), you could simply specify what you want in plain human language and have it transformed into code under the hood.</p><h2>Drive processes (CRM, issue tracking, etc)</h2><p>Having a system read all your emails, messages and so forth will definitely be a privacy challenge, but on the other hand this could enable automatically reporting status updates and changes to CRMs, issue trackers and so forth. For example you could analyze the Git history to move an issue&#8217;s status (alongside with comments explaining what happened). Or track commercial emails to automatically report on a lead&#8217;s status.</p><h2>Run end-to-end tests of applications written in natural language</h2><p>Isn&#8217;t that so fucking annoying to write front-end tests? This could change with appropriate use of LLMs. They could not only write tests for you but &#8212; and most importantly &#8212; they could also heal existing tests to adapt for code changes.</p><h2>Visually test applications</h2><p>LLMs can have vision capabilities. As such, they are able to do something more smart than a pixel-perfect validation. They could check two images and tell you if there are significant differences. Look at a web page and tell you about obvious issues (text overflowing, alignment problems, etc).</p><h2>Log analysis to detect abnormal behaviors</h2><p>Server logs are usually very long files that you keep to be able to diagnostic a particular issue if it happens, but when it&#8217;s about knowing what happens in real time then it becomes more complicated. Log monitoring tools exist but they are limited by the fact that logs are extremely diverse and unexpected. Instead, LLMs  could be used to read all logs in real time and raise alerts when needed.</p><h2>Threat modeling assistance</h2><p>How do you secure a product? Nothing can be considered secure in the absolute, best practices are only good as long as they fit your needs. That is why you need to <a href="https://owasp.org/www-community/Threat_Modeling">model your threat</a>, which basically comes down to finding the weakest link in all the components holding the product&#8217;s security and figuring which might break easily enough for the prize to be worthy of the effort. This requires to imagine a full dependency map of everything related to the product, which a LLM could help enumerate.</p><h2>Open source issue qualification</h2><p>Open source projects historically always had issue with bug reports and feature requests, that are often done in a terribly unclear way. A robot could on the other hand be able to assist people in doing their report, until the produced description is clear enough for all parties.</p><h1>E-commerce</h1><h2>Image-based search</h2><p>Classical e-commerce facetted search requires detailed product description with a structured model of the product&#8217;s characteristics. And while for a stick of RAM this might be kind of easy, for some fields like clothing for example it&#8217;s already harder to categorize <em>everything</em>. On the other hand you could be asking questions about images, like &#8220;I want a pair of blue jeans with a contrasting seam&#8221; and the search engine could smartly filters images on this unexpected characteristic.</p><h2>Mix-and-match assistant</h2><p>Imagine that you find your perfect pair of pants but you are looking for a shirt to go with it. A LLM would be able to understand the level of formality, the color and the style of those pants and then to find a matching shirt for it. Let&#8217;s note that it&#8217;s a different concept from &#8220;recommended products&#8221; that exist today: here we consider the user&#8217;s explicit intention. This works for all kinds of products: cosmetics, food, tools, etc.</p><h2>Organize products from raw pictures and spec sheet</h2><p>Imagine that you are building a e-commerce in which you have raw material for each product that are pictures and PDF datasheets. You could have AI take care of creating categories, structured product characteristics and product description completely automatically, only leaving humans for review.</p><h2>Product composition decoder</h2><p>Imagine that you are lactose-intolerant and are looking to buy food. Or your skin has some specific alergies to chemicals. It would be interesting to be able to ask those questions to the e-commerce directly, which will decode tricky product compositions for you. Or even better, state in your profile which components you wish to avoid and the system will automatically put a warning tag on all corresponding products, along with a warning before checking out.</p><h2>Product suggestion</h2><p>You are redecorating your terrace and you need to figure what to put there. Send a picture of it to your furniture store and have directly matching suggestions displayed to you. It also works for various cases where the user could state a problem: &#8220;my computer is too slow&#8221;, &#8220;I need to water my tomatoes&#8221;, etc.</p><h2>Visual audit of second-hand products</h2><p>Since LLMs are able to view and to follow instructions, second-hand platforms could specifically ask to visually check known defects on pictures. This could help the user into qualifying their own product, as well as highlight important checkpoints to customers.</p><h2>Price suggestion for second-hand platforms</h2><p>In the same vein of being able to analyze products visually, you could as well automatically compare a given product to similar products sold in the past and suggest from there a fair price completely automatically.</p><h2>Extract and categorize pain points from online reviews</h2><p>Online reviews are a trove of user feedback for some products sold beyond a certain scale. Using a LLM to systematially parse them can be an interesting way to find out defects, use cases, quantify perception through time, etc.</p><h2>Faceted search</h2><p>Most e-commerce websites have what you call faceted search. It&#8217;s those filters on the left that allow you to refine a listing by some characteristics whether it&#8217;s size, color or anything else. Sometimes the experience is great but sometimes it is also not super smart. A way to improve the experience wouuld be to have a search bar that lets you specify in natural language the filters that you want to apply and then let the AI translate into the right request. No more awkward clicking, scrolling and waiting for page load again and again.</p><h1>Entertainment</h1><h2>Drive NPCs &#8212; basically, Westworld</h2><p>The Westworld show was pretty good &#8212; at least season 1 &#8212; at showing us instinctively what AI could accomplish for us and how it could do it. Give structured scenarios to <a href="https://en.wikipedia.org/wiki/Non-player_character#:~:text=A%20non%2Dplayer%20character%20(NPC,rather%20than%20by%20another%20player.">NPCs</a> and let actual player interact with them. LLMs can entirely be used to generate dialogues, figure the steps to stay on the scenario, etc. Potentially very exciting amusement parks in perspective, but also of course video games.</p><h2>Generate backstories and character sheets for RPGs</h2><p>If you are a RPG player &#8212; like D&amp;D and the sort &#8212; you probably know that getting your character off the ground can be a lengthy process. Generate a backstory, specs, etc. It&#8217;s hours spent doing administrative procedures instead of playing. Instead you could just prompt the basic concept of the character and have it all generated in an instant.</p><h2>Assist users learning how to play a game</h2><p>Board games are always hard to understand the first time you play them. You could however imagine that an LLM-boosted agent could understand those rules and help beginners to play: explain what happened, let them know of potential moves, etc.</p><h2>Answer questions about movies halfway</h2><p>Sometimes in the middle of a movie you are just lost at what happened. However platforms already have a lot of data a LLM could exploit, like subtitles for example. Using this, you could imagine asking Netflix to clarify specific plot points and have the system check the transcript of the movie thus far to help you understand.</p><h2>Image culling and storytelling</h2><p>Ever came back from holidays with thousands of pictures that you never actually sort through? LLMs would be good at making a consistent story and picking the top X pictures to tell it in your album.</p><h2>Book, podcast, etc. length or style adjustment</h2><p>I personally hate reading fiction. For some reason, I&#8217;ve been devouring wikis and theories from GoT, LOTR and so forth but never actually managing to finish the books. It&#8217;s too long and too indirect. What if book &#8212; and podcast, news, etc &#8212; platforms let you adjust the length and style of what you are reading? 50-pages version of GoT? 2 minutes, to-the-point version of a 20 minutes podcast? The ability to further explore topics that piqued interest? Lots of people already watch movies in 1.5x, this would only be a logical next step.</p><h2>Trope analysis and novelty factor</h2><p>Star Wars episode IV is always a good example of how contextual movies need to be. Watch it in 1977 and it will fucking blow your mind. On the other hand I&#8217;ve shown it recently to a friend that was like &#8220;oh come on and then it&#8217;s going to be his father? how fucking original&#8221;. If you want to make a movie or an article entertaining, it must be composed of a good mixture of things that people are used to, spiked with an edge of novelty. Using AI to systematically explore and quantify tropes in existing scripts can help establish the novelty factor of a new project. Let&#8217;s note that this also works for politics, journalism, fiction and basically anything targeted at the mass market.</p><h2>Auto-edition of video interviews</h2><p>Interviews are a significant pain in the ass to edit. But using a LLM you could transcribe everything said, ask for it to pick the best part that will fill up X minutes and automatically slice and edit the video at the proper timestamps.</p><h2>Conspiracy theory generator for social media</h2><p>Whether we like it or not, social media is full of trolls trying to influence people&#8217;s choices and votes. A way of doing this is to attack specific pillars of a society (science, government, etc) by throwing an insane amount of conspiracy theories to destroy them. It doesn&#8217;t need to be consistent, it just needs to be massive. That is great, given that LLMs are excelling at making text that <em>sounds good</em> but that is utterly shallow. Pick your target, throw a LLM at Twitter and enjoy a massive ideological destruction.</p><h2>Fan-fiction generation</h2><p>Fans usually like their media so much that they want to keep exploring this world endlessly. Without making those stories canon, entertainment giants could easily generate literally endless stories by fine-tuning LLMs on the specific do&#8217;s and don&#8217;ts of a universe and let them generate content for their fans. As a bonus, the most successful stories could serve as a basis for major projects.</p><h1>Data Analysis</h1><h2>Data visualization</h2><p>Data visualization is a hard topic in the sense that managing all those graph libraries, SQL queries and the weirdest APIs like Pandas, it&#8217;s not very accessible to your average executive Joe. On the other hand LLMs are <em>excellent</em> at this, given a proper human intent. They are going to play a key role at making data more accessible.</p><h2>Transform natural language signals into structured data</h2><p>Scrape social media, listen to Slack messages or emails and turn this into structured data that you can quantify and analyze easily through graphs and statistics.</p><h2>Loosely structured data cleanup</h2><p>How many times data is provided in CSV form with completely inconsistent content? Poorly escaped lines, inconsistent IDs, etc. A usually tedious cleanup job could be entirely automated away with a properly trained LLM.</p><h2>Reverse-engineer structures</h2><p>Have you ever tried to understand what a company does from the outside? It&#8217;s usually very hard, given that the corporate website will tell you that they &#8220;deliver excellence&#8221; across a wide range of industries, present their &#8220;solutions&#8221; and &#8220;case studies&#8221; but will never go into the detail of <em>what</em> they actually did. The best way to understand the truth in my opinion is to look at job descriptions, both their quantity and their content. Gather them all together and you understand exactly which operational tasks, tools and hierarchy those companies have. Tedious by hand, but very suitable for LLMs to complete.</p><h1>Natural Language Processing</h1><h2>Translate</h2><p>All right this one is obvious in a conversational setup, but it of course also works if you are trying to internationalize a service. In an e-commerce or social media for example, the level of translations from a top LLM is good enough that you can trust it unsupervised in many languages for many non-critical use-cases.</p><h2>Generate alt tags</h2><p>Something that all CMSes should start doing: automatically generate alt tags for their image library. LLMs are now entirely capable of describing an image, and it&#8217;s so good for SEO and accessibility that this should become the norm very quickly.</p><h2>Spellcheck</h2><p>LLMs are also very good at spell-checking and can be used in a wide range of applications to help you improve your writing.</p><h2>Find acronyms</h2><p>The hardest thing when starting a project is to find a good name for it. Well not anymore, as now you can simply describe what your project does, ask Claude for a fitting acronym and there you go!</p><h2>Parse free-form numbers</h2><p>It&#8217;s not uncommon to end up with a data table where you need to parse prices or different kind of amounts but unfortunately they have been given in various forms, like &#8220;30 millions&#8221; or &#8220;45k&#8221;. While you can solve this with regular expressions, a cheap LLM can often be very efficient at parsing this.</p><h2>Anything to Markdown</h2><p>Given the ability of LLMs understand documents structure &#8212; textual or from images &#8212; they are excelling at producing markdown from anything. Just rasterize your PDF, throw it into a LLM and you&#8217;ll get your markdown version pretty easily.</p><h2>Parse citations from academic papers</h2><p>My understanding is that academic parpers follow a formal structure but in a semi-formal way technically speaking. Typically they are all linked to each other through citations, but their parsing is tedious. LLMs could empower this.</p><h2>Smart replace in document</h2><p>Imagine you write a long proposal for a client and refer repeatedly the name of their product or some important concept. But then your boss swoops in and asks you to remove or change all those references by another one. Sometimes search and replace can do the job, but sometimes it will affect the grammar or the structure of sentences. LLMs could do this job completely automatically.</p><h2>Auto-adaptation of texts for different targets</h2><p>Imagine writing a scientific revue. Maybe you want to address different levels of readers from the most advanced to kids. Or imagine a publisher that wants to make Shakespeare accessible to foreigners. LLMs are able to translate between languages but also between styles.</p><h2>Re-phrasing of customer input</h2><p>Customer support is a fantastic world where you get insulted for things you didn&#8217;t do. Instead, LLMs could act as a buffer between the customer and the support where aggressive, sarcastic sentences are turned into plain and clear ideas.</p><h1>Content Generation and Management</h1><h2>Generate FAQ from website content</h2><p>Gather all the content of your website, figure all the questions that it answers and generate the FAQ pages from this.</p><h2>Generate decent usernames</h2><p>It is quite hard to generate a decent username when subscribing to a platform. With a few smart questions and methodology, a pretty cheap LLM that could even run locally would produce many interesting name propositions in real time.</p><h2>Create recipe/tutorial variations</h2><p>When cooking, doing some work in your home, taking care of your garden or anything hobby-level in which you have no particular expertise, you will tend to follow tutorials to learn how to do things &#8212; and more importantly to achieve particular goals. The only issue with those tutorials is that they might have details incompatible with your particular situation. For example you want to cook a cake but you are alergic to one particular component. How do you replace it? That&#8217;s where the LLM can make educated guesses and alter the content dynamically to fit the user&#8217;s need.</p><h2>Smart filling of templatized documents</h2><p>Newsletter software allows you to place people&#8217;s name and a few other details within the text. But what if you could go much further than that? Create templates for documents like contracts, commercial outreach, etc. Then have a LLM fill up the blanks respecting grammar, gender or even making up whole sentences based on meta information: &#8220;Hi John, you expressed on our contact form that you need XXX, which can be filled by products in your YYY range. Let&#8217;s schedule a call?&#8221;.</p><h2>Generate onboarding procedure and training path</h2><p>Anyone running a company knows that transmitting the company&#8217;s knowledge is a tedious endavor. Pages and pages of process have been written over the many years of existence of the company, all at different levels of maturity. How do you introduce a newcomer to all this in a consistent order? You can feed all your documents to a long context LLM (like Gemini&#8217;s 2M tokens) and have it sort out documents in topological order and that are interesting in respect to a given job description.</p><h2>Organize asset production based on company policy</h2><p>Many companies have a process for rolling out a product or communication: social media assets, press releases, etc. While generating them directly will still be a human work, a LLM could allow high-level definition of guidelines in plain text, with more nuances possible than regular automation platforms, and automatically create outlines for the assets that need to be created.</p><h2>SEO and keyword-centric upgrade of articles</h2><p>CMS and other content management tools could receive specific directives regarding SEO and keywords that need to be present to perform not only live audits but also suggestions of modifications to the content in order to integrate the desired keywords.</p><h2>Meeting prep, create meeting agenda</h2><p>What worse than a poorly prepared meeting? Gathering information from previous meetings, ticket trackers and other digital platforms, an AI could outline the agenda of upcoming meetings, while at the same time helping each participant to gather their own content to present.</p><h2>Auto-update documentation</h2><p>All products and company processes need to be documented at various levels from the most technical to the most high-end. As the product grows and changes are made, it becomes hard to keep track of what needs to be updated in the documentation. Combining LLMs and embeddings,  you could track the overall company activity and highlight parts of the documentation that become obsolete, list the missing parts and even automatically propose edits.</p><h2>Dynamic course re-writing</h2><p>Imagine a student learning online in front of their computer. Some topics will be easy but assuredly some others will prove more challenging. These courses are often evaluating the student&#8217;s skills all along the way. What if depending on those evaluations the content of the courses was adapted to the strengths and weaknesses of the student? Catch-up texts can be generated from the original course but focusing on the weaknesses and ellaborating on them further than in the initial content.</p><h2>Infinite copy generator</h2><p>How do you know which words are going to transform your audience best? What if you generated one version of your content for every single time that someone reads it? Then observe which versions worked the best and use this as reinforcement for your model, to produce more and more efficient versions of the copy.</p><h2>Dynamic content</h2><p>In the same vein, you can also observe the user&#8217;s behavior and browsing history in order to dynamically re-write or optimize pages when he lands there. Connect the dots with concepts freshly ingested, push forward detected interests, etc.</p><h1>Image and Visual Processing</h1><h2>Transcribe handwritten notes</h2><p>This might sound like a miracle but GPT-4o is able to read my handwriting. Not only this but it can transform it into a well-structured Markdown document. And then of course translate, summarize and all the perks. This can be helpful in number of scenarios from digitizing meeting notes to processing and translating on-the-fly antique manuscripts.</p><h2>Pet control</h2><p>Pets tend to behave differently when their owners aren&#8217;t home, like jumping on the bed or sofa. LLMs are definitely not the most efficient but they are for sure the easiest way to express to a machine &#8220;if a dog rolls in my sheets yell at them to stop&#8221;.</p><h2>Generate color palettes</h2><p>Just like LLMs are trained on word patterns, they are trained on visual patterns and including the understanding of colors. This means that you can generate smartly color palettes that actually work (as opposed to this color wheel madness you often see). This can help you generate your own UIs but even more than that what if the LLM were able to generate all the design tokens up to the user&#8217;s taste, ending up with a unique, custom and beautiful UI for every single user?</p><h2>Art explanation</h2><p>If like me you are art-illiterate but still end up in museum wondering what happened in a specific painting, only to find the name of the painter with a vague title next to it, with a lengthy audio guide telling you everything except what you want to know&#8230; you&#8217;ll understand this idea. Instead of audiobooks, musuems could provide interactive assistants fed with in-depth knoweldge on every work of the musuem but able to distil it in a way tailored to the visitor&#8217;s taste and to reply to their questions directly.</p><h2>Picture-based food search</h2><p>Google Maps is trying very hard to create ontologies of the real world, especially with its &#8220;questions&#8221; program asking you if a given service or food is available in various places that you have visited. However if you are not american you probably ended up confused when you got asked if your local high-end bakery was making smores. Food simply does not translate between cultures. That&#8217;s where a deep understanding of images could lead to a much more efficient search that would echo one&#8217;s way of expressing their wants.</p><h2>Better narration for GPS</h2><p>Did you ever take the Madrid highway with some US-optimized GPS voice? How long did it take you to take the wrong turn? With ample imagery available &#8212; street view, 3D maps, etc &#8212; you could absolutely have much more descriptive directions from the GPS. Referring landmarks, taking into account perspective, etc.</p><h2>Drone or CCTV-based visual inspection of equipment or land</h2><p>As you can describe what you want to see, you can have drones or cameras film something you want to inspect and ask the LLM to tell you if it matches your expectations or not. Look at satellite imagery and ask &#8220;tell me places where forrests have been depleted&#8221;. Look at a building and say &#8220;tell me if any tile is missing&#8221;. And so forth.</p><h2>Auto-design simple, templated flyers, posters, etc</h2><p>Some apps will help people organize events or do marketing. Especially for small businesses it&#8217;s going to be hard to create those assets on their own, as they will not have the means to work with bigger agencies and are most likely unaware of best practices. On the other hand the app could leverage LLMs to apply best practices, pick colors, use and customize proven layouts to generate all kinds of visuals.</p><h2>Check that translations are meaningful in context by visually analyzing apps</h2><p>A common example of translation error that infuriates me is around the word &#8220;check&#8221; in English that can be understood as two distinct French words: either as in &#8220;verify&#8221; or as &#8220;check this box&#8221;. And very often, the meaning is lost, leading to crazy translations like &#8220;Verify the terms and conditions to continue&#8221;. Since LLMs can read texts and context, they could be used to apply translation files on an UI and make sure that all buttons make sense.</p><h1>Document Processing</h1><h2>Parse invoices</h2><p>Invoice management is the bane of any small business. You receive hundreds of them, need to extract different items and taxes systematically, but on the other hand every single invoice has a different format. Fortunately LLMs are pretty good at extracting this information and putting it into a JSON &#8212; whether it comes from an email, a PDF, a picture of a ticket, etc.</p><h2>Pick food at restaurant</h2><p>Did you ever end up undecisive at a restaurant? Just snap the menu, feed it into a LLM and let it guide you into ordering something. It even works with hand-written texts that you can&#8217;t understand &#8212; Japan explorers will rejoice. If you are a restaurant you can even push this further and help users through a custom assistant.</p><h2>Normalize recipes</h2><p>As a nutrition app or related, you might want to make the link between the food listed in a recipe and the calories for example. But people writing recipes love to use the weirdest units or even have things implied &#8212; like some common ingredients not even being listed as ingredients. With the help of LLMs you can extract these ingredient lists, transform them into units that make sense and get the nutritional value of what you are cooking.</p><h2>Convert mind-maps to structured linear document</h2><p>Mind maps &#8212; or the Post-It method as well &#8212; produces a lot of ideas around one given topic but you might end up overwhelmed at the end of the process by the amount of information that needs to be processed. LLMs can transform those ideas into a linear structure, properly sorted and organized.</p><h2>Paper forms digitalization</h2><p>As an intern I have been copying lots of paper forms made on-the-spot for fidelity cards in a store. Or recently, we all have been filling up countless COVID forms whenever taking the plane. Using LLMs, you can understand and transcribe those forms completely automatically into a digital system.</p><h2>Transcribe, tag and reference historical paper-only archives</h2><p>Countless historical documents or books have been scanned but how many are properly referenced? You can guess that over thousands of years of history, we could set to map out all those documents, link them together, analyze references and ideas over time, to build a better understanding of our history and our currents of thoughts.</p><h1>Customer Support and User Experience</h1><h2>Start workflows from emails</h2><p>Customer supports will often be drowning in emails. You can parse them, detect intents and trigger the proper systems in your back-office to start procedures, without any human intervention.</p><h2>Request routing inside the company</h2><p>When the organigram starts becoming big, it might be hard to navigate the responsibilities and knowledge of the people in there. Especially for newcomers, it cn be a challenge to find the right person to talk to while also not bothering them unnecessarily. As CTO and founder I can answer most questions on most topics within my company, but should an intern come and ask me how to connect the printer? A reassuring AI could help people orient themselves in the hierarchy to know who they can confidently reach out for in order to receive help.</p><h2>Prioritize incoming messages and notifications</h2><p>You are probably like everyone else drowning in countless useless sollicitations, from services to which you subscribed 20 years ago to urgent business emails. Depending on the time of the day and your personal goals, you might want to be notified of one thing but not the other. Or you might want to receive notifications in bulk for some topics. For example I&#8217;d love to see Slack create a &#8220;what do you want to be notified about?&#8221; option and then burry irrelevant mentions and messages.</p><h2>Configure complex features</h2><p>When you use some apps, there will be features that are extremely complex to grasp. For example, try any product in Binance, it&#8217;s complex enough to throw you off if you are not eager enough to learn about it. Through the use of AI they could instead ease the user into setting the right parameters according to their own personal goals.</p><h2>Voice message summarization</h2><p>Some people love voice messages, some people like me are loathing them. Having an AI skip through the &#8220;sorry I&#8217;m sending you a voice message because I&#8217;m in the street and it&#8217;s easier to send a voice message [&#8230;]&#8221; and instead deliver to you just the point of the message would be a great WhatsApp addition.</p><h2>Conversation coach</h2><p>We are constantly exposed to conflictual situations, especially in low-stake but annoying uses cases like negociating a refund overe an incorrect package received. Email and messaging apps could help you understand what you could obtain in that situation and redact emails for you, helping you every step of the way and reducing your mental load.</p><h2>Automated test grading</h2><p>The point of MCQs is that they are easy to grade, including if done by a computer. That&#8217;s why e-learning platforms use them so much. But given the advances of LLMs, it would be easy to imagine having them grade even textual responses, looking to see specific bits of information and telling you if it&#8217;s correctly explained or not.</p><h2>Interpretation of complex diagnostics</h2><p>Some diagnostics are not nice to hear, especially when they are particularly complex. From medical reports to SEO audits, if you are not an expert you might be confused by the terms and implications of those documents. A properly trained LLM could instead simplify them for you and even answer potential questions you might have.</p><h2>Allow customers to do self-diagnostic on products</h2><p>Vice-versa, some products are complex and have many failure modes. Companies internally have debugging procedure that can pin-point exactly what is faulty, but it&#8217;s hard for the regular customer to follow such procedures. Instead of paying a human being to tell you to turn it on and off again, such workflows could be assisted entirely by a LLM, specially driven by a tailor-made logic engine for your product.</p><h2>IT support for end-users</h2><p>The most feared and annoying department of a company is often the IT, that has the important task of securing the company&#8217;s data and intellectual property while also having to explain to users how to connect the Wifi. By the proper use of LLMs, with their general knowledge of how computer works, but with a specific training adapted to the policy, they could skim a lot of useless requests off the pile from IT departments.</p><h2>Suggest A/B testing variants</h2><p>A/B testing is great to test how the user is going to react to different UX or copies, but how might you do it? You need the ideas, after all. A well-trained model that knows the UX best practices for different industries could do this job of taking a human&#8217;s work and proposing potential optimizations.</p><h2>Algorithm transparency</h2><p>Many complex algorithms are ruling our lives. For example every electronic payment goes through a set of rules to determine if the action is legitimate or not. These departments are utterly closed and opaque to the rest of the company. Typically, if your card gets blocked then nobody in the bank can tell you why nor for how long it will stop working. Having a LLM being aware of the different rules of the algorithm, it could explain in simple words the reasons of this block to the bank advisor, and the available perspectives. This works for banking, but any sector with complex algorithm could leverage this.</p><h1>Personal Assistance</h1><h2>Help user stay on plan</h2><p>So many apps are helping us become a better version of ourselves, wether it&#8217;s for diet, exercise, jet lag, pet training, etc. But how often can you follow 100% of the plan? With a bit of intelligence you could let users report their deviations and help them stay on track without overreacting with counter-productive actions or simply getting demotivated.</p><h2>Context-picking for events, emails, etc</h2><p>Imagine an event in your calendar with few details. When the event comes up, the system could read your emails, meeting notes talking about this event and then infer useful information ranging from the latest tickets from the issue tracker, the weather if you need to go somewhere, remind you to take your IDs or advising you to dress in a certain way. Overall, for one item and a lot of context, the LLM could pick the top few elements that are relevant for you not to forget &#8212; wheter it&#8217;s an event, an email you are writing, a plane ticket, etc.</p><h2>Long-term goal tracking</h2><p>The human brain is very wired to small tasks and has a hard time taking a step back to see if you are achieving your long term goals. On the other hand an AI could be aware of your goals and rank each of your actions telling you if they seem to be helpful or not to achieve that specific goal.</p><h2>Natural language passwords</h2><p>Passwords are a notoriously hard problem. How do you make a password that is secure yet that you can remember? You could imagine generating complete phrases through LLMs but not only. When the user types back the phrase, you could use LLMs to normalize the text before hashing it so that spelling mistakes or punctuation or even word arrangements do not affect the outcome of the hashing.</p><h1>Business and Legal</h1><h2>Categorization of items</h2><p>The other day I wrote an article about GDPR, which parsed a big HTML page from the CNIL. The information there was semi-structured and I had to categorize things further in order to make sense of them. Same for the current article, the different ideas have been categorized by a LLM. This is a great tool to group similar concepts together.</p><h2>Critique political programs</h2><p>While LLMs are obviously biased &#8212; especially with the US culture wars &#8212; but are nonetheless able to project without ego into many persona. As such they are quite interesting to tools to use in order to review political programs and see how they are backed by facts and theory. Journalistic platforms could enhance their content with thorough review of every single politician, detect their parting from party ideas, and most important let people explore concepts on their own, for their personal situation or their vision of society.</p><h2>Find out adequation between candidate and job description</h2><p>Large companies will match candidates only based on keywords based on a first pass. However we know how biased this approach is, given specific technical knowledge is not shared by recruiters &#8212; and even less by those in charge of unpiling thousands of CVs, which must not be the highest ranking ones. On the other hand AI is great at matching a CV to a job description, describing quite well the fitting and lacking areas as well as the challenges to work with this person. Even further, based upon a transcript of the interview, you can ask questions and validate specific checkpoints by searching intelligently for the relevant parts, without having to listen to hours of recording.</p><h2>Check ToC exhaustivity</h2><p>When you reply to a RFP, a grant or any exercise of that style, there will be a list of requirements you need to meet and precise points that need to be addressed. Obviously it is more subtle than just filling up a form, you need to make sure that various aspects are answer consistently throughout the response. You can use a LLM to both extract from the RFP the list of points that need answering &#8212; or at least cross-check it &#8212; and also to check if your response provides adequate light on each of those elements.</p><h2>Insurance claim/policy matching</h2><p>Insurance policies are always a bit obscure. A well-trained LLM could allow customers to role-play use cases before subscribing, as well as gather all the necessary information in case of a real claim.</p><h1>Social Media and Content Moderation</h1><h2>Automated content moderation</h2><p>As OpenAI has proven to us with its extremely restrictive usage policy, LLMs can be used to detect offensive content &#8212; or any kind of content that you don&#8217;t want to see, offensive isn&#8217;t the same for everyone. In a day and age where social media operates at a great scale, being able to detect &#8220;forbidden&#8221; content would not only make the platforms safer but also more customizable. Indeed, what if instead of having one single policy, different communities had their own policy automatially applied? Free speech and safety for all!</p><h2>Social media filters</h2><p>In the same idea, what if instead having algorithms sorting your feed in the most opaque way, you could express what you want to see? The same as TweetDeck allows you to do by keywords for example, but with concepts instead. &#8220;Tell me all about space news&#8221; or &#8220;I&#8217;m sick and tired of meme X&#8221;. On top if filtering this could also mean groupping: different posts talking about the same topics could be groupped or even hidden after a threshold.</p><h1>Specialized Applications</h1><h2>Ask questions about meeting transcript</h2><p>Summarizing a meeting is good, but being able to look for specific information in it is the killer feature. &#8220;What did we conclude on the topic X?&#8221;. This is what I really want to see in those AI meeting platforms, especially to be used during ulterior meetings.</p><h2>Step assistance in tutorials/recipes</h2><p>When following a tutorial or a recipe, some steps that might seem obvious to the person writing it will probably be hard for you to follow if you are too new to the topic. Having a LLM to be able to write sub-steps to fill the gaps for you will be a great help.</p><h2>Democratic platforms for citizen engagement</h2><p>Politicians love to claim that they know what their people want, but how do they really know? With AI&#8217;s capability to categorize and summarize, you could turn griefs and ideas into structured input coming from the whole nation. A super-simplified procedure where you could complain about anything or ask for any change as it goes through your mind. Then processed and presented in any level of detail to your representative.</p><h2>Real-estate property auto-description</h2><p>So many platforms allow to post real-estate ads, but the quality of those is often mediocre to non-existent. What if using the proper context based on the pictures, the map information, the neighborhood meta-data and so much more, you could generate a proper text description highlighting the strengths and weaknesses of a given property?</p>]]></content:encoded></item><item><title><![CDATA[3 reasons why Webhooks suck and 2 Masterclasses to replace them]]></title><description><![CDATA[The most popular way for different services to send messages to each other should never have existed. We review why and how we can make it better, taking real-world implementations as examples.]]></description><link>https://www.baby-cto.com/p/3-reasons-why-webhooks-suck-and-2</link><guid isPermaLink="false">https://www.baby-cto.com/p/3-reasons-why-webhooks-suck-and-2</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sun, 28 Apr 2024 07:01:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0a8cac61-fbb0-4779-b34e-a93beea35e56_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The most common way for independent services to exchange messages &#8212; even more so on public APIs &#8212; are webhooks. A beauty of simplicity: you simply provide an URL that you want to notify when an event occurs and the other service simply has to make an HTTP call. Except, not really.</p><h1>Webhooks suck</h1><p>In the shadow of this superficial simplicity are creeping major problems which make it hard for both ends to exploit webhooks.</p><h2>Not missing a drop</h2><p>First of all, the most basic prerequisite for a webhook to work is that the receiving end is able to receive. Meaning that the webservice must be up and running. But what happens if a maintenance is ongoing, a technical issue plagued the server or simply the network connection has an instant of failure?</p><p>The message will simply and purely be lost. It&#8217;s a <a href="https://en.wikipedia.org/wiki/Byzantine_fault">Byzantine fault</a>: how can  you know if a message was sent if the sender is unable to contact you either way?</p><p>In order to remedy this, most providers resort to implementing retry mechanisms. Which is fairly complex to implement: you need to store somewhere that you&#8217;ll have in the future to execute a given set of messages and wake up accordingly. Most queuing systems will struggle at doing this reliably because they are working in &#8220;at least once&#8221; mode, meaning the same message could be sent twice. You can decide you don&#8217;t care but then your client has a problem on their side.</p><p>Another issue is that if you are doing a maintenance on your server, maybe you configured something wrong and it ends up responding 200 when actually the wrong service was receiving the message. In that case the message simply gets obliterated, given that the sender thinks it&#8217;s received and the receiver has no idea the message even exists.</p><h2>Avoiding flashbacks</h2><p>This retry logic will also amplify another danger. Messages could very well arrive out of order, and this for different reasons.</p><p>For example if you are implementing a retry mechanism but consider all messages as independent. In that case if the receiver gets unavailable for a while, they are at risk of receiving the missing messages after they started catching back with earlier messages. You can let the receiver with the burden of fixing this, but it honestly will get ignored most of the time.</p><p>What can happen as well is if your receiver operates at a larger scale and has at least 2 web servers, if two of your messages arrive at the same time and get processed by 2 different processes at the same time, there is no saying which message will be dealt with first.</p><h2>Harder to develop</h2><p>Now this is a more practical than theoretical consideration, but most of the time developers won&#8217;t have the luxury of a public IP address on their development machine. Which is a big problem since webhooks are actually going to have to initiate the network connection, meaning that you will probably end up resorting to tools like HTTP tunnels.</p><p>On top of that it means that your code needs to be aware of its own public URL, which you cannot really do automatically. For example if you use a regular API,  you never need to declare what is your public address. But for webhooks you need to know what it is and to declare it. Often through complex back-offices or needing propagation times.</p><p>As a result you end up with an extra configuration variable which you could probably avoid otherwise, you probably also need to go through some manual configuration and on top of that free plans of popular HTTP tunnels will change your URL every time so you possibly end up changing it all the time.</p><h1>There are alternatives</h1><p>How do we deal with this situation better than with webhooks? First you need to realize that you are actually trying to solve two separate problems:</p><ol><li><p>Knowing that there is at least one update pending &#8212; when an event occurs then your code needs to wake up and do its job, preferably as fast as possible after said event.</p></li><li><p>Synchronizing state &#8212; the final goal of this is to have different systems converge into the same state, whether it&#8217;s knowing if the user expects the light on or to get the full status of a shared online document.</p></li></ol><h2>Waking up remote code</h2><p>The most naive thing you can come up with is polling. Every X seconds you&#8217;ll check if there are updates available. This is however considered as wildly inefficient:</p><ul><li><p>The cost of establishing a connection is pretty high relatively to other options.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p></li><li><p>You won&#8217;t get the updates &#8220;in real time&#8221; but rather only every time you poll</p></li></ul><p>That&#8217;s why in most cases, polling will not be recommended and both software and hardware architecture were designed to avoid polling. If you were to simplify it to the extreme, modern computers are driven by inputs. A physical electrical signal on your network card will trigger a processing chain that will eventually wake up the relevant process, all the way down to your favorite abstraction from Python, JS or any other language.</p><p>This is what makes webhooks attractive: a remote computer can wake up your local process. But it&#8217;s not the only way to do it. If you open a network connection from your local machine to the remote API &#8212; which is extremely easy to do even without a public IP address &#8212; then as long as the connection is up then packets will be flowing both ways.</p><p>WebSockets were invented exactly for this. It&#8217;s an easy way to have a client, typically behind a NAT or a proxy, to connect to a server and receive real-time updates. That would be my go-to option for waking up remote code.</p><p>Alternatively, before WebSockets we used a technique called &#8220;long polling&#8221;. The idea is to make a regular HTTP query but that hangs for a very long time (typically minutes) until an update happens and the HTTP query returns with the message. A bit messy but almost as efficient as WebSockets if you don&#8217;t have a very high throughput and not more costly than webhooks.</p><p>When implementing this kind of technique, you need to consider that you will be maintaining one full TCP connection with every single client. That used to be a challenge, it is becoming quite easy these days if you can use an async infrastructure.</p><p>Alternatively you can turn towards dedicated services like <a href="https://cloud.google.com/pubsub?hl=en">Google&#8217;s Pub/Sub</a>, <a href="https://aws.amazon.com/eventbridge/">AWS EventBridge</a> or countless others. For example, Shopify offers webhooks but recommends notifications through <a href="https://shopify.dev/docs/apps/webhooks/configuration/eventbridge">AWS</a> and <a href="https://shopify.dev/docs/apps/webhooks/configuration/google-cloud">Google</a>. Kind of the same as dealing with the WebSocket yourself but you let someone else manage the scale for you.</p><h2>Staying on the same page</h2><p>Distributed systems are notoriously hard and I am not aware of an universal law that allows you to deal with any situation whatsoever, especially as you scale up. However it usually boils down to the same core idea &#8212; which can be remixed at will to fit the project&#8217;s needs.</p><p>Consider that your data model is a bit like a Git repository. At a point in time, the source code has a given state but in order to reach there a series of different edits had to happen. Said otherwise, if you sum up all the edits then you get the state of the code at a point in time.</p><p>So the key here will be to identify which edits happen in your model, convert them into a stream of events and re-compose them on the other side. This can be more or less difficult to achieve, for example Google Wave used <a href="https://en.wikipedia.org/wiki/Operational_transformation">Operational Transformation</a> which took 2 years to develop but on the other hand if you&#8217;re just dealing with a messaging app your life should be much simpler.</p><p>Now imagine all those edits as a sequential log. As you read the log, you keep track of your current cursor, pointing to the latest known edit. When you are notified of another event then you need to read starting from this cursor.</p><p>This resolves a lot of issues raised earlier:</p><ul><li><p>By using edit logs, your communication protocol is basically writing itself and will look fairly simple. If you&#8217;re used to Vuex or Redux, it&#8217;s basically the idea behind mutations.</p></li><li><p>The cursor allows to know where we are in the update stream. If you lost a notification because your program was down or crashed, you can catch back from your latest known state.</p></li><li><p>Even if the transmission of messages fails, you can easily have a retry mechanism to eventually get up-to-date.</p></li><li><p>There is no risk from getting the same message twice as messages are basically sequential, numbered items.</p></li></ul><p>From looking at WhatsApp&#8217;s WebSocket communications, you can presume for example that they use this kind of strategy and it&#8217;s even what enables them to have end-to-end encryption with consistent shared states between participants and devices while having servers completely oblivious to the actual content of conversations.</p><h1>Masterclasses</h1><p>Having recently interacted with different APIs, two of them really stand out in my opinion, showing how you can make a public API that avoids pitfalls explained earlier. I picked them up because the choices they made really highlight how you can implement things in correct way while also keeping things simple.</p><h2>Telegram</h2><p>The world of instant messaging is highly competitive, with all major players pushing their platform as hard as they can. Facebook has the two most popular platforms &#8212; WhatsApp and Messenger &#8212; however the third one is a pure player gaining traction only through their strategy<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>.</p><p>One part of this strategy is to have an amazing bot experience, allowing with very small developer effort to create real-time applications. This is particularly prominent in the cryptocurrency world but for example it&#8217;s also a tool heavily used in Ukraine to follow bombing threats.</p><p>The basic idea of Telegram is pretty simple. You have different conversations in which you come add messages. Then more complex things can happen like people putting reactions, messages being edited, user clicking buttons, etc. All of them are listed and <a href="https://core.telegram.org/bots/api#update">documented updates</a>.</p><p>Now the interesting part. How do you get those updates?</p><p>A first method is the webhook. As you know, it sucks. The more interesting method is the long polling <a href="https://core.telegram.org/bots/api#getupdates">getUpdates</a> method. It combines two techniques explained earlier.</p><ul><li><p>Long polling &#8212; the HTTP call will hang until either an update or a timeout happens. Not as efficient as WebSockets but very easy to implement because you can do it with literally any HTTP client ever written. And of course it works from a private IP address.</p></li><li><p>Cursor &#8212; the call takes an <code>offset</code> argument, which corresponds to the ID of the last message you received.</p><ul><li><p>This is a smart way to get you to acknowledge receiving the previous messages and receiving new updates in one single call.</p></li><li><p>But on the other hand if you pass an offset of 0 then it will repeat the last offset that used. This means that if you restart your app you don&#8217;t need to remember the last offset, which is incredibly convenient.</p></li></ul></li></ul><p>As a result, developing a client for the Telegram Bot API is a very smooth and simple experience. All you need is a HTTP client and a tiny wrapper around it to get started. You can use a lib of course but implementing a client from scratch is a very easy task both in terms of code (no need for crazy libs) and of infrastructure (almost no constraints).</p><h2>Plaid</h2><p>If you never heard about <a href="https://en.wikipedia.org/wiki/Open_banking">Open Banking</a>, it&#8217;s basically all the banks in the world somewhat converging into providing standardized and modern APIs for all their services. At least in theory, in practice of course the capabilities and implementation details vary greatly country-to-country and instead of a truly open standard you need to go through middlemen such as <a href="https://plaid.com/">Plaid</a>. This is not my field of expertise so I can&#8217;t go into the details but all I can say is that Plaid does a great job at converting <s>dinosaurs</s> banks into REST APIs.</p><p>They have a wide number of APIs but the one that I&#8217;m interested into is the Transactions API. The most interesting information about bank accounts, especially if you are building a personal finance app, is to see the list of transactions that happened there.</p><p>One of three thing can happen, with examples:</p><ul><li><p>A new transaction happened (you bought something)</p></li><li><p>A transaction got modified (exchange rate got finalized)</p></li><li><p>Or it can be deleted (transaction was not captured in the end)</p></li></ul><p>In the case of Plaid, they work a lot with batches. I don&#8217;t even want to know how they receive those transaction but if you told me they came from a latin-1-encoded CSV file dropped on a FTP every 3h I would not be surprised. As a result it&#8217;s much less <em>real-time-ish</em> than Telegram. It&#8217;s not extremely relevant to ship every event individually.</p><p>Instead they&#8217;ll give you a cursor &#8212; up to you to keep track of it in that case &#8212; and give you <a href="https://plaid.com/docs/api/products/transactions/#transactionssync">aggregated added/modified/removed transactions</a>. Which makes it very easy to update your own database. If you just had the list of latest transactions for example, you&#8217;d have to diff the DB to know what to create, update or delete. But here you can blindly do a bulk insert, bulk update and delete. 3 SQL queries maximum and done.</p><p>The only issue I have with their system is that&#8230; It&#8217;s based on webhooks &#128531;</p><p>But that&#8217;s not causing much harm. Of course it means you need to setup a HTTP tunnel before developing with their API but on the other hand because they have this sync method you avoid all the other drawbacks pretty easily. You can even poll the API every day if you don&#8217;t care about being &#8220;as fast as possible&#8221;.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reaching the end! Give it a subscribe if you feel like learning more on architecturing your code and your company.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>Take away</h1><p>Webhooks suck because they bring a hoard of subtle yet annoying problems. Most queue systems are either &#8220;at most once&#8221; or &#8220;at least once&#8221;. Webhooks are &#8220;probably once &#129310;&#127995;&#8221; and bring with them a terrible developer experience.</p><p>But what we really need to do is decouple two problems: the one of waking up remote code and the one of synchronizing state.</p><p>Waking up remote code is fairly easy now that async architectures are widespread, you can either rely on an external cloud provider or simply let people open websockets to you.</p><p>And then regarding state synchronization, most likely you want a somewhat linear sequence of events to be streamed to your consumer, relying heavily on the concept of cursors to let remote code communicate its current knowledge of the state.</p><p>At the end of the day, if you are making a public API, the developer experience is going to matter a lot and involves in the current case two main elements:</p><ul><li><p>How complicated is the code going to be when using your API? The lightest the required wrapping, the least the data post-processing, the better.</p></li><li><p>How hard will the problems be to solve in terms of infrastructure? States to be kept, network flows, etc. Keep in mind that most apps start from scratch so optimize for small operations rather than world-scale conglomerates.</p></li></ul><p>So if you are making a public API &#8212; for the wide web to use or simply for other parts of your company &#8212; please think well how you can make the life of your peers easier and safer!</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>It&#8217;s not <em>that</em> high, I still do a lot of polling when I&#8217;m short in time and it will make almost no difference on the result.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Not making any judgement or recommendation here. You can be pretty sure that half the secret services in the world read your Telegram messages, but it <em>is</em> a massive platform on which you can build many interesting things.</p></div></div>]]></content:encoded></item><item><title><![CDATA[5 criterion to pick your front-end framework]]></title><description><![CDATA[Where we take an objective look at all options ranging from jQuery to Remix in order to figure which should you use for your next front-end project.]]></description><link>https://www.baby-cto.com/p/5-criterion-to-pick-your-front-end</link><guid isPermaLink="false">https://www.baby-cto.com/p/5-criterion-to-pick-your-front-end</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sun, 14 Apr 2024 07:01:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d7d07a89-96c8-4658-8357-1feb927dc8e5_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One thing for which yours truly is particularly glad is to have been able to partake to the development of the Web for two thirds of its history and to see all the twists and turns it has taken over the year. Now that the big platforms dominate it, that Chromium has a quasi-monopoly &#8212; except for Safari, which is a lesser version of the same thing &#8212; and that the hoops to jump are just so numerous, one can only imagine that getting into web development must be a disheartening thing.</p><p>Let us however keep an eye on the North. Imagine you were to create a major project right now, which involves a website, what route should you wish to take?</p><ul><li><p>The old school would go towards <a href="https://jquery.com/">jQuery</a></p></li><li><p>The minimalists would pick <a href="https://htmx.org/">htmx</a></p></li><li><p>And the mainstream would pick a meta-framework such as <a href="https://remix.run/">Remix</a>, <a href="https://nuxt.com/">Nuxt</a> or <a href="https://kit.svelte.dev/">SvelteKit</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p></li></ul><p>Those three voices will often be vocally contradicting each other on social media, adding confusion into the mix. Of course there are no silver bullets, only bullets that will hit the target more difficultly than others. My bullet is the meta-framework, and that&#8217;s not out of kool-aid nor fear.</p><p>For the first years of my career it was impossible for me to use meta-frameworks, for they were not invented yet. This was a painful experience. If you end up creating a real-time interactive game in which the whole DOM is dynamic, trust me jQuery is <em>definitely not</em> the tool for the job.</p><p>On the other hand if you are not creating something so deeply interactive, chances are that jQuery &#8212; a library whose main purpose is to even out the differences between Internet Explorer 6 and Firefox 2 &#8212; isn&#8217;t going to be <a href="https://youmightnotneedjquery.com/">of great help</a>. Especially if you are feeling more like a backend person, a tool like htmx will allow basic interactions which require very little front-end work and might just be enough for you.</p><p>But if you work for projects of a more unpredictable nature then maybe those solutions are not optimal. It&#8217;s with the mind set on a large, enterprise-grade&#169; project that we&#8217;ll go in quest of the perfect framework to build your startup.</p><h2>Performance</h2><p>A common consideration against meta-frameworks will be the performance. Surely, all those features must come at the cost of very expensive and bloated JavaScript runtimes? Let&#8217;s put that to the test. I&#8217;ll make a hello world page using major frameworks and measure the transfered payload size (gzipped for the most part):</p><ul><li><p>HTMX &#8212; 16 kio</p></li><li><p>SvelteKit &#8212; 25 kio</p></li><li><p>jQuery &#8212; 30 kio</p></li><li><p>Remix &#8212; 90 kio</p></li><li><p>Nuxt 3 &#8212; 131 kio</p></li></ul><p>This tells us that meta-framework do not have to be heavy and clunky. They can, like Remix and Nuxt 3, but SvelteKit is lighter than jQuery in that regard.</p><p>What about the execution speed, memory use? <a href="https://krausest.github.io/js-framework-benchmark/current.html">This benchmark</a> is fairly popular and while it doesn&#8217;t include HTMX nor jQuery, it does have vanilla JS, which should be the closest to what you can achieve. You&#8217;ll have to explore the numbers yourself but in a nutshell even React that is often lagging behind stays pretty close to the baseline. The same goes for memory use.</p><p>In any case, those benchmarks are toys. The better question is: how fast is your website going to be at scale?</p><p>The clear winner is going to be HTMX, because all the rendering logic happens on the server, which isn&#8217;t so much bound by script size. On the other hand, all the other options will have to write numerous lines of code. If you write your project using jQuery, you will be ending up either with one big JS file, either with a collection of different files but either way, the organization will be yours. Now if you want to minify all this, you will not escape the need for a set of build tools. When scaling up, it <em>is</em> difficult to escape having a build stage.</p><p>And while meta-frameworks also have a build stage, this process is entirely integrated. You don&#8217;t have to do anything: your code gets transpiled, minified and processed in all the necessary ways completely automatically. With the added bonus that the build tools are aware of the dependency tree. As a result, every page can automatically be bundled in its own file, so that you never need to load code that you don&#8217;t need immediately.</p><p>From the performance standpoint, you thus have two choices:</p><ul><li><p>Either you think that HTMX will never be a limit in what you are trying to achieve, in which case it is the easiest option</p></li><li><p>Or you need to have some wiggle room in terms of feature &#8212; 100% of my professional projects &#8212; and then a meta framework will be a far superior option for a relatively small price to pay</p></li></ul><p>Let&#8217;s declare the winner of this round: SvelteKit!</p><h2>Maintainability</h2><p>While a strong argument of the web development community was the separation of concerns between HTML and CSS, you need to understand that at this time people were writing CSS directly in the style tag, making poor use of semantic and limiting greatly reusability. It is however essential to consider <a href="https://www.baby-cto.com/p/html-the-facade-of-complexity">HTML, CSS and JS as a whole</a>.</p><p>This is why the concept of <em>component</em> is everywhere. It&#8217;s the same as the widgets from UI frameworks. The advantage of a component is that, like explained above, it has a clear dependency tree, it contains all the code that is required for its proper execution and if you want to refactor or delete it you do not need to look for its bits and pieces all over your code base, worrying about side effects.</p><p>The first and most important question to deal with is the CSS, whose C stands for &#8220;Cascading&#8221;. It&#8217;s another way to say &#8220;if you are not careful, one change here will cascade into disfiguring your whole product&#8221;. You could deal with the styling of your component by writing the style into the style tag directly, or even generate all the possible styles into classnames and write the style into the class tag directly. But that is exactly what the Elders warned us about.</p><p>A more interesting approach is to define the <a href="https://cssinjs.org/">CSS in JS</a>, which allows you two things: first the CSS gets bundled with your JS &#8212; and your HTML de facto &#8212; and second it gets a scope wich will not overal with the one of other components. It&#8217;s just a shame to be doing this manually and to deprive yourself from tools like SCSS, which make writing CSS much easier. That&#8217;s what Single File Components (SFCs) allow and they are available in both Vue and Svelte but weirdly not in React.</p><p>Once you have scoped and bundled together your CSS, JS and HTML generation then it becomes hard to write spaghetti code. Knowing that on top of that the build system will track all your dependencies, as said earlier, this allows for an extremely atomic compilation and optimization process.</p><p>On the other hand, when using jQuery or HTMX, you will be left to your own devices. Not necessarily a bad thing but the organization becomes up to you. As soon as two developers start working on the same code base, we know that t he organization can quickly go through the window.</p><p>Henceforth, we got here two winners for scalability and team work, which are SvelteKit and Nuxt 3.</p><h2>Future-proofness</h2><p>If you create a startup which depends heavily on a given framework in order to work, you want to make sure that after you have invested five years of developer time you don&#8217;t end up having to rewrite everything from scratch. Those tools need to give a good perspective to developers. For example, you can still run on a Windows machine an unmodified DOS program that was written in the 80s. Without being so extreme, if you cannot see at least 10 years forward with a tool then you have a problem.</p><p>First of all, let&#8217;s look at jQuery. Almost 20 years later the API is still basically exactly the same, version upgrades being mostly about simplifying the code because browsers are converging now. If that&#8217;s the route you intend to take the rest assured that jQuery will not go anywhere.</p><p>Then HTMX. Honestly it&#8217;s hard to say anything at this point. Small project, the maintainer probably can&#8217;t promise anything. Chances are it will only add features through time but due to its nature I can&#8217;t imagine how they could introduce major breaking changes. The main risk is more that the project dies, but even then it probably wouldn&#8217;t be too hard to maintain it yourself it it came to that.</p><p>On the React side, I don&#8217;t practice it often enough to go in-depth into it but in any case the ecosystem is so vast that you could probably write another article dedicated to chosing the right React stack. In that regard, it seems like breaking changes do occur in every part of the ecosystem but overall it&#8217;s never going to be something fundamental that asks you to through your entire codebase in the trash.</p><p>Which is fairly different from what you could say about the Vue ecosystem. Vue 3 has been a major breaking release and introduced a whole new set of completely different APIs (the &#8220;Composition API&#8221;) and while it is not inherently bad it changes completely the way to think about your code. In theory you don&#8217;t have to use it but every single library of the ecosystem only supports this anymore, so you don&#8217;t actually have a choice.</p><p>Including the Vue-3-compatible version of your favorite libraries/tools (if ported), which will force you to rewrite everything that depends on it. The main one being Nuxt 3, which came out with exactly zero overlap in API or conventions. There is <em>nothing</em> that works the same anymore and the thin compatiblity layers that exist usually are fickle, fragile and generally useless. This feels like a serious backstab barely 6 years after version 1 &#8212; yes I have products that would cost 6 figures to upgrade and I&#8217;m pissed about it.</p><p>Now remains to evaluate Svelte&#8217;s position. While I can&#8217;t find any official statement on where Svelte will be in 10 years, some good indicators are there. Firstly, the documentation is written in a way more practical way than others, showing that they care more about the use cases than the technicality of the framework. Secondly, so far upgrades have been relatively smooth. And finally Svelte 5 is cooking and the ugprade process also seems clear. Now since the ecosystem is much smaller, it&#8217;s hard to tell what is really going to be the impact, but let&#8217;s keep our eyes open.</p><p>Overall, the only tool that has demonstrated a serious commitment towards backward compatibility is jQuery, which will have to be winner of this round!</p><h2>Cognitive load</h2><p>You have probably one day heard of GTD and decided to try it for yourself. One of the advice coming out of it is: if it takes less than 5 minutes, do it immediately. So tell me, how many days did you end up spending 100% on 5 minutes tasks? Did it feel satisfying? And did you accomplish anything meaningful?</p><p>Of course it&#8217;s a rethorical question and you can&#8217;t really answer so you&#8217;ll have to imagine that you said &#8220;it&#8217;s satisfying but not meaningful at all&#8221;. Which is what happens as well with many tools that we use and that we might judge on the satisfaction to use them while actually we lose our time doing boilerplate.</p><p>Imagine that you are writing an interactive component which depending on the user actions and inputs will have to update its own DOM. Doing it in jQuery can be extremely satisfying because you create all the elements yourself, find smart ways to hook events, imagine optimizations to do it faster, etc. Very fun if it&#8217;s your jam. But on the other hand if you do the same thing with Svelte, the compiler does all this automatically and better than you ever would. Managing the DOM is just not a task with Svelte.</p><p>So while it&#8217;s satisfying you just spent your time on something that should not even grab your attention. What about our contenders?</p><ul><li><p>jQuery &#8212; As mentioned above, it&#8217;s all manual from the manipulation of the DOM to bundling it to the client. Easy to marvel at the beauty of your code, hard to actually focus on what matters.</p></li><li><p>Remix/React &#8212; Many moving parts and optional APIs (hooks, signals, etc). No management of CSS. Fairly complex overall.</p></li><li><p>Nuxt/Vue &#8212; Version 3 of both definitely made things more complex, with two competing APIs (including a fairly verbose one) and lots of build-time magic.</p></li><li><p>HTMX &#8212; Very lightweight front-end but on the other hand you still need to worry about the back-end yourself, so it&#8217;s a lot of unknowns</p></li><li><p>Svelte(Kit) &#8212; Once you&#8217;ve sorted out the idioms, it is fairly straightforward and requires no magic of any kind</p></li></ul><p>Hence, I&#8217;ll give the round to SvelteKit!</p><h2>Community and talent pool</h2><p>You could find the best framework of all times, if nobody can provide for you libraries that solve common problems (UI libraries, form validation, toasts, etc), learning resources or direct support then you are going to have a hard time.</p><p>On top of this, you need to be able to hire developers. A decent developer can learn any decent framework but they need to <em>want</em> to work with it and usually they&#8217;ll expect that it&#8217;s going to look good on their CV.</p><p>The same goes commercially speaking. Lots of customers are interested in knowing what tools you use and if you can&#8217;t justify that it&#8217;s durable and bullet-proof they might get cold feet when deciding to work with you.</p><p>So here is what to expect:</p><ul><li><p>jQuery isn&#8217;t sexy anymore, nobody needs it on their CV, nobody wants to work with it except a few indiepreneurs that claim jQuery pays for their lambo. Customers who look into your tech stack will ask you if jQuery is a headless framework and it&#8217;ll be hard to say yes.</p></li><li><p>HTMX is straight away a no go. It&#8217;s too small to put on a CV and not trusted enough to fuel a sales pitch. Using it in a professional setup right now will require a serious offset on other parts of your stack.</p></li><li><p>React is a no-brainer. Everyone wants it on their CV, it&#8217;s backed by a major player and the community is one of the largest for a framework of that kind. Plus, most companies that publish SDKs or component libraries will prioritize React.</p></li><li><p>Vue is the new React, except less popular, less demanded and less clunky. Still a workable option.</p></li><li><p>Svelte is on the line. But it receives a lot of developer love (like HTMX you&#8217;ll tell me) and is now backed by Vercel which not as big as Facebook but definitely big enough to make this serious. Plus Svelte 5 is promised to resolve all pending Svelte issues, which should boost adoption.</p></li></ul><p>Basically I have to give this round to React (hey at least one).</p><h2>Conclusion</h2><p>There are a million of other items to consider and of course many more frameworks than those. But in my experience those themes are the most important to consider when picking a technological stack.</p><p>So which framework should you use?</p><p>In the absolute, go with SvelteKit. Provided that the version 5 of Svelte doesn&#8217;t turn out to be a major betrayal of the community. It&#8217;s lightweight enough, scales well with teams, focuses developers on what matters and should not ask you to rewrite your whole application in 2 years &#8212; especially if you waited for version 5.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a></p><p>Yes you will get more difficulties finding talents for it but on the other hand it has good press and you can learn the bulk of it in a few hours. There are of course a few rough edges but nothing insurmountable. Same for your sales pitch, you can always make diversion by using a hot headless CMS.</p><p>Another interesting option if you were building your own startup with a Craigslist-type UI would be to use HTMX. Besides the talent pool, the main thing about using HTMX is that developers need to have the final word on UX/UI, because otherwise you will be fighting against the framework all the time. But if you can keep it constrained, you&#8217;re probably going to have a very efficient experience.</p><p>Now regarding other meta-frameworks, we see that they all come with dealbreaking drawbacks &#8212; namely React being a huge spaghetti bowl and Vue being a traitor. Not that you can go particularly wrong with them, but it&#8217;s just not good choices in my opinion. If you are really afraid about what others might think you can always pick some assembly of React things, but be warned that it comes at a cost for your mental load and the one of your browser.</p><p>Finally if you are team jQuery, of course it&#8217;s a safe and proven choice which will continue to support you for the next 100 years I&#8217;m certain. Which is more than you could say about anything else on that list. It&#8217;s probably good if your app is small and you have few resources to invest in the front-end.</p><p>So while there are indeed no silver bullets, it&#8217;s important to outline that given the current state of the art you are much safer and much better off with a meta-framework, especially if it&#8217;s SvelteKit. Other options exist and can be valid for some use cases but as someone that oversees the production of many front-end applications I can only recommend to stick with a paradigm that covers all angles at minimum cost.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>If you&#8217;re wondering why Angular is not part of this comparison while it definitely has the credibility to fit in there the answer is twofold. First of all, they invented backstabbing in the JS framework world. And since they backstabbed me, I didn&#8217;t get any experience with it so it would be very hard for me to talk about it. All I know is that it has no chances of winning according to the evaluation below.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>If Svelte 5 <em>also</em> ends up in treason, then I&#8217;m not sure what advice to give. If treason is acceptable then Vue is a superior option due to its large popularity while avoiding many of React&#8217;s pitfalls.</p></div></div>]]></content:encoded></item><item><title><![CDATA[From Chaos to Clarity: Streamlining End-to-End Testing with Django and SvelteKit]]></title><description><![CDATA[Learn the secrets to boosting your web development process with Django and SvelteKit. Our article reveals how to seamlessly blend these powerful frameworks for unmatched speed and reliability.]]></description><link>https://www.baby-cto.com/p/from-chaos-to-clarity-streamlining</link><guid isPermaLink="false">https://www.baby-cto.com/p/from-chaos-to-clarity-streamlining</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sun, 10 Mar 2024 08:00:44 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f06f52f0-4532-4a49-a284-684e77625374_1792x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For reasons listed in my <a href="https://model-w.readthedocs.io/en/latest/architecture.html#back-end-django">Model W Architecture</a> document, my framework of choice for the backend is Django (tldr; the ORM) and until another better option emerges in the world of JavaScript this is not going to change. On the other hand, my experience has shown that if you do a professional website you will eventually outgrow the <a href="https://htmx.org/">htmx</a> and other lightweight frameworks, making it a necessity to systematically turn towards meta-frameworks such as <a href="https://kit.svelte.dev/">SvelteKit</a>, Nuxt, Nuxt.js or Astro &#8212; to quote the most famous.</p><p>This is what we&#8217;re systematically doing at <a href="https://with-madrid.com/">WITH</a> and the combination works well. But you absolutely must figure ways to align all this properly &#8212; and there are no official ways to do this.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Baby CTO! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Today we&#8217;re going to explore one specific friction point: end-to-end testing.</p><h2>Why to test?</h2><p>Some will tell you that you need to cover 100% of your code base with unit <em>and</em> e2e tests while others will say &#8220;testing is doubting&#8221;. So while we&#8217;re not here for a theoretical lesson on the benefits of tests, we are goingt to focus on <em>why</em> we would want to have those, which in turns allows us to decide what we want to test.</p><h3>The speed factor</h3><p>First, nobody gets the code right the first time. Personally with my 20 years of coding I think that <em>once</em> I managed to land about 1000 lines of code that worked on the first time, while being extremely focused on what I was doing. The typical development cycle looks more like: write a bunch of lines, see where it breaks, repeat until it works.</p><p>As a developer, you will learn to code faster and with less mistakes over time but there is nothing you can do about it <em>right now</em>. Just code more and it will sink in. This leaves you with the second part of the process: how fast can you see where it breaks?</p><p>Obviously the answer to that question is largely dependent on what you are currently testing. If you&#8217;re talking about CSS, then a second screen with the page you&#8217;re currently integrating along with a good meta-framework that implements <a href="https://webpack.js.org/concepts/hot-module-replacement/">HMR</a> properly should be the easiest way to go.</p><p>On the other hand if you&#8217;re creating Django models and/or APIs using <a href="https://www.django-rest-framework.org/">DRF</a>, a lot of the code that you are going to write is going to be declarative &#8212; only to be later picked up by the meta functions of Django and turned into a usable project. Which means that there is literally no code for you to test, it&#8217;s mostly configuration<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>But if you are working on the typical front/back architecture that we&#8217;ve discussed earlier, most of the things that you&#8217;re ever going to want to test in an automated way are the end-to-end user stories.</p><p>If you test those manually, you will be clicking on many buttons and filling up many forms. On and on again. For test cycles of 30 seconds to 5 minutes usually.</p><p>On the other hand if you automate those tests you can probably drop the testing time to a couple of seconds. We can estimate that on average it&#8217;s going to be about 10 times faster than manual testing.</p><p>Now let&#8217;s consider the following simple equation:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;T = \\sum_{n=1}^{R} (T_c + T_t) = R \\cdot (T_c + T_t) \\quad \\text{where} \\quad \n\\begin{cases}\nT_c = \\text{Time spent coding} \\\\\nT_t = \\text{Time spent testing} \\\\\nR = \\text{Number of repetitions} \\\\\nT = \\text{Total time to develop a feature}\n\\end{cases}\n&quot;,&quot;id&quot;:&quot;OXPHUBHXCF&quot;}" data-component-name="LatexBlockToDOM"></div><p>Let&#8217;s consider that:</p><ul><li><p>The time spent testing manually is equal to the time spent coding</p></li><li><p>The automated test is 10 times faster than the manual test</p></li></ul><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align*}\nT &amp;= R \\cdot (T_c + T_t) = R \\cdot (2T_t) \\quad &amp;\\text{since $T_c = T_t$} \\\\\nT' &amp;= R \\cdot (T_c + T_t') = R \\cdot \\left(T_t + \\frac{T_t}{10}\\right) = R \\cdot \\frac{11T_t}{10} \\quad &amp;\\text{since $T_t' = \\frac{T_t}{10}$} \\\\\n   &amp;= R \\cdot \\frac{11}{10} \\cdot T_t = \\frac{11}{20} \\cdot R \\cdot (2T_t) \\\\\n   &amp;= \\frac{11}{20} \\cdot T \\quad &amp;\\text{rearranging terms} \\\\\n\\therefore T' &amp;= \\frac{11}{20} \\cdot T\n\\end{align*}\n&quot;,&quot;id&quot;:&quot;EDGOETYQPJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Even if you don&#8217;t understand the math formalism, you understand that in the end testing your code automatically while you develop is <strong>almost twice faster</strong>. The bias here of course is that you still need to write this test. That&#8217;s why we&#8217;ll explore tools that make this as easy as possible, so that the benefits are not swallowed by the plumbing.</p><p>Overall it&#8217;s hard to quantify exactly <em>how much</em> productivity gain<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> we&#8217;re talking but it should help you go about two times faster &#8212; and in the worst case scenario it seems unlikely that it will be slower than testing manually. More importantly we&#8217;re just talking about the <em>immediate</em> benefits of testing.</p><h3>Ease of mind when changing things</h3><p>Any application that lives long enough will reach the point where no single human brain can comprehend the entirety of its features at the same time. There are just too many moving parts. And this point arrives much sooner than you think, especially in environments like mine where people move from project to projet all the time.</p><p>Essentially: how do you know if something that you change will break anything in the project without testing everything? Leading to the subsequent question: how do you even know what to test?</p><p>The answer is that you cannot know what broke if you don&#8217;t test it, so indeed you <em>have to</em> test everything. Which can be done with for example a large testing booklet written and maintained manually &#8212; aka not &#8212; or also with automated tests that run every time you push  your code into the repo (and on your machine while you dev).</p><p>The second option is absolutely better in the sense that:</p><ul><li><p>If all the tests are written, it will be exhaustive</p></li><li><p>And since it&#8217;s all automated, each test should be extremely fast</p></li></ul><p>This way you reduce a QA process to a few seconds of test instead of potentially hours of man time spent. With the guarantee that everything is executed in stable conditions and in a repeatable way.</p><h3>Onboarding of newcomers</h3><p>Overall tests will show you how to use the app and how to use the code. All a newcomer has to do to understand everything that you can do with the application is to watch the tests unfold.</p><p>Let&#8217;s note that this is partly true because tests will often be cryptic and hard to document. A better way to approach this topic is with <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">BDD</a> and &#8212; spoiler alert &#8212; <a href="https://pytest-bdd.readthedocs.io/en/stable/">pytest-bdd</a>. But that&#8217;s for another article, we are focused here on the Django/Svelte integration.</p><h2>Picking the right tools</h2><p>While I am not going to list every single test runner and framework out there &#8212; that would be an entirely different article &#8212; here are the constraints I&#8217;m settings for myself in this quest for automated tests.</p><p>The first aspect is that Django-based tests have the ability to write directly into the database, which is in turn cleaned up after each test. When your application is essentially just transforming a DB schema into an API, that&#8217;s really something you want to be able to do. Without that you&#8217;re in for some very awkward mocking. The core idea is thus to run tests from Django &#8212; I even considered wrapping Django&#8217;s tests from JS but in the end that was not necessary.</p><p>The default test framework in Django is the standard <a href="https://docs.python.org/3/library/unittest.html">unittest</a>, and while honorable there are more friendly and powerful options out there. Namely <a href="https://docs.pytest.org/en/8.0.x/">pytest</a>, which as you will see right below will be the backbone of our strategy. The first thing is to integrate it with Django&#8217;s tests and this happens with <a href="https://pytest-django.readthedocs.io/en/latest/">pytest-django</a>.</p><p>The main issue however that I have with testing in Django is that, while it has a <a href="https://docs.djangoproject.com/en/5.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a> (and the <a href="https://pytest-django.readthedocs.io/en/latest/helpers.html#live-server">pytest equivalent</a>), it kinds of wants you to use <a href="https://selenium-python.readthedocs.io/">Selenium</a> and no offense for that precursor tool but oh boy is it unusable. Last time I wrote e2e tests with Django and Selenium I ended up writing more utils than tests.</p><p>Thankfully things have changed and we are now able to use <a href="https://playwright.dev/python/docs/intro">Playwright</a> through the <a href="https://pypi.org/project/pytest-playwright/">pytest-playwright</a> plugin. While I don&#8217;t particularly like Microsoft I must admit that it has two very interesting characteristics.</p><p>Firstly it has very semantic selectors which will use accessibility attributes in order to find elements on the page. This is great because while you test your features you know that if you don&#8217;t have to resort to crude CSS selectors it means that at least what you test looks more or less decent in terms of accessibility.</p><p>And secondly it has an auto-wait feature on all the selectors, which is by far the most annoying thing that you end up doing all the time with Selenium.</p><p>To summarize, we&#8217;re going to go with:</p><ul><li><p><code>Django</code> itself and its testing facilities</p></li><li><p><code>pytest</code> as test runner</p></li><li><p><code>pytest-django</code> for the Django integration</p></li><li><p><code>pytest-playwright</code> for the browser testing</p></li></ul><h2>Implementation time!</h2><p>In order to proceed to demonstrate how all those tools work together, created a <a href="https://github.com/Xowap/e2e-django-svelte">sample project</a> on GitHub which contains mostly the boilerplate that you will need along with an example of how to use everything together.</p><p>The project is extremely simple in itself: there is one model that is exposed through an API with one page that displays all the instances returned by the API. Really just the bare minimum to write a test that shows all we discussed above.</p><p>Lots of small details are going to be left out from this explanation that focuses on the big picture. The source code being entirely available, any shadow can be lifted by inspecting it. If you intend to run the project yourself, have a read at the <a href="https://github.com/Xowap/e2e-django-svelte/tree/c1b5298acce4f236d2a4fa372fc85e18f8c2577e">README</a>.</p><h3>Boilerplate</h3><p>We&#8217;ve got two projects which are fairly close to default Django and SvelteKit projects located in the <a href="https://github.com/Xowap/e2e-django-svelte/tree/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/api">api</a> and <a href="https://github.com/Xowap/e2e-django-svelte/tree/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/front">front</a> folders.</p><h4>API</h4><p>Let&#8217;s first have a look at <a href="https://github.com/Xowap/e2e-django-svelte/blob/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/api/requirements.in">our dependencies</a>. Quite obviously we&#8217;ll see there Django alongside its best friend <a href="https://www.django-rest-framework.org/">DRF</a> for the API management part.</p><p>On the testing side we have 3 plugins on top of pytest:</p><ul><li><p><code>pytest-django</code> &#8212; Takes care of the Pytest/Django integration, and specifically takes care of managing the database and live server</p></li><li><p><code>pytest-playwright</code> &#8212; Integration of Pytest and Playwright in order to be able to test things within a browser</p></li><li><p><code>pytest-env</code> &#8212; Small utility that allows to define environment variables when Pytest runs, which is super useful if like me you follow the <a href="https://12factor.net/">12 factors philosophy</a>: it allows to have a static configuration for running tests.</p></li></ul><p>Since we&#8217;re talking about end-to-end tests, I figured that it would not necessarily make sense to pin them to a specific Django app and rather I&#8217;ve created a dedicated <a href="https://github.com/Xowap/e2e-django-svelte/tree/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/api/tests">test folder</a> for it.</p><p>In order to be able to run the tests, you need to make sure to configure the settings modules and the environment in the <code>pyproject.toml</code> file:</p><pre><code>[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "e2e_django.settings"
env = [
    "DJANGO_ALLOW_ASYNC_UNSAFE=true",
]</code></pre><h4>Front</h4><p>Honestly I&#8217;ve changed nothing to the front-end except create the page that displays the thing we want to test.</p><h3>Front/API sync</h3><p>The part that was elusive to me for the longest time was: how can I synchronize the front-end and the back-end &#8212; especially in regards to the database management that I&#8217;m mentioning earlier.</p><p>Turns out, with a little bit of eblow grease and pytest magic it&#8217;s fairly easy.</p><p>First we need to talk about pytest&#8217;s <a href="https://docs.pytest.org/en/6.2.x/fixture.html">fixtures</a>. If you&#8217;re a Django developer you probably hear &#8220;fixture&#8221; and think &#8220;right to load data into the database&#8221;. But it&#8217;s not that at all. They are a mechanism of dependency injection specialized for tests.</p><p>For example you could say: I have a &#8220;user&#8221; fixture that is a user from the database and that is scoped to each individual test. If a test requires the &#8220;user&#8221; fixture then the user will be created into the database and will be cleaned after each test.</p><p>Both the Playwright and the Django plugin use them heavily for giving you access to their various features. Typicall if you ask the <code>page</code> fixture for your test then Playwright will be started but ohterwise it will not.</p><p>The same applies for the <code>live_server</code> from Django and in our case we&#8217;ll be able to leverage this in order to start and stop the front-end while testing.</p><p>This can be done relatively easily if you exploit the fact that both the front and the API are in the same repository. You can compute accurately the absolute path of the front-end and start scripting there.</p><p>Which is exactly what the <a href="https://github.com/Xowap/e2e-django-svelte/blob/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/api/tests/conftest.py#L64">front_server()</a> and its friends are doing in the conftest.py file &#8212; a file that can inject global fixtures into different tests under the same module. While you can read the source code directly, let&#8217;s review the key points:</p><ul><li><p>We use Popen to start the Vite server in preview mode, which is close enough to production for our needs. A fixture can just yield an object, and the function will suspend until all tests that need it are done. This is what we do, and after the yield finishes we just shut down the process.</p></li><li><p>The process is bound to port 0. This is a special way to tell the system &#8220;just pick any available port&#8221;. Which allows to not have to decide for a static port number thus limiting the risks of failure. The Vite server will print the chosen port when starting, so we just parse stdout to get it.</p></li><li><p>In the end we simply yield the base URL of this front-end server and then our tests will be able to connect to it in any way they want.</p></li></ul><p>This example is done with Vite because that is what powers SvelteKit, but while the detail of the commands you would have to run would be different there are equivalents of this in every single front-end framework so you&#8217;ll just need to adapt it accordingly.</p><h3>Writing the test</h3><p>Now that we&#8217;re able to summon the front-end (through the code above) and the browser (through Playwright) it&#8217;s time for us to write a test!</p><p>Be careful, this is actually very disappointing because it&#8217;s way too simple. First we create the items that we want to see through a fixture:</p><pre><code>@pytest.fixture
def some_items(transactional_db):
    return [
        Item.objects.create(name="Foo"),
        Item.objects.create(name="Bar"),
    ]</code></pre><p>Now we create a test that requires 3 fixtures:</p><ul><li><p><code>front_server</code> &#8212; The server we&#8217;ve created above</p></li><li><p><code>some_items</code> &#8212; The items defined here</p></li><li><p><code>page</code> &#8212; The Playwright control object</p></li></ul><pre><code>@pytest.mark.django_db(transaction=True)
def test_content(front_server, some_items, page: Page):
    page.goto(str(httpx.URL(front_server).join("/")))

    for item in some_items:
        item_name_escaped = repr(item.name)[1:-1]
        assert (
            page.locator(f"li:has-text('{item.id}: {item_name_escaped}')").count() == 1
        )</code></pre><p>This way we&#8217;re able to send the browser to the front-end and check the content of the page based on the expected items we&#8217;re looking for. That&#8217;s it!</p><h3>Running the GitHub Action</h3><p>If you&#8217;re making automated tests, it&#8217;s usually a good idea to run them automatically. Fortunately it&#8217;s really easy to do with GitHub Actions. We&#8217;ll define <a href="https://github.com/Xowap/e2e-django-svelte/blob/c1b5298acce4f236d2a4fa372fc85e18f8c2577e/.github/workflows/test-api.yml">a workflow</a> that triggers on push.</p><p>Beyond the installation of dependencies, let&#8217;s check some interesting steps of that workflow:</p><pre><code>- name: Run tests
    run:
        .venv/bin/python -m pytest --junitxml=/tmp/test-results.xml
        --tracing=on --video=on --screenshot=on
    working-directory: ./api</code></pre><p>When running the tests, we keep the results in JUnit format and ask Playwright to record pictures and videos of all tests. Let&#8217;s note that if your project scales up you probably just want to record failing tests and not all tests, otherwise you&#8217;ll eat up artifact storage pretty quick.</p><pre><code>- name: Publish test report
    uses: mikepenz/action-junit-report@v4
    if: always()
    with:
        report_paths: "/tmp/test-results.xml"
        check_name: "API Pytest Report"</code></pre><p>Since we&#8217;re able to export the outcome as a a JUnit file, we use an action that transforms it into a nice recap for the action.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ld3F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ld3F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 424w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 848w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 1272w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ld3F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png" width="550" height="173.92300641613198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:345,&quot;width&quot;:1091,&quot;resizeWidth&quot;:550,&quot;bytes&quot;:34732,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ld3F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 424w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 848w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 1272w, https://substackcdn.com/image/fetch/$s_!ld3F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfc80dc0-f86f-4dcb-960b-e1f2317cc8e1_1091x345.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Test summary from the GitHub Action (only visible if you&#8217;re connected)</figcaption></figure></div><pre><code>- name: Keep Playwright artifacts
    uses: actions/upload-artifact@v4
    if: always()
    with:
        name: playwright-traces
        path: api/test-results/</code></pre><p>Finally, we&#8217;ll save temporarily the Playwright videos and screenshots into a GitHub Action artifact, which allows to analyze in-depth failed tests (for example using the online <a href="https://trace.playwright.dev/">Trace Viewer</a>).</p><h2>Conclusion</h2><p>After establishing that automated testing is well-worth going through the trouble of establishing a well-oiled testing infrastructure, we set to explore how this can be accomplished with Django and a Javascript meta-framework such as SvelteKit.</p><p>While this requires a little bit of boilerplate and adaptation &#8212; after all, those two worlds are not exactly thought to work togeter &#8212; we can see that we can obtain both the convenience of Django&#8217;s tests with their database management and the power of modern front-end test frameworks such as Playwright.</p><p>In the end the tests run completely autonomously on GitHub Actions and produce both nice reports and in-depth traces that allow analysis in case of failure.</p><p>This whole structure is easy to use on a daily basis and can boost your coding speed up to two times!</p><p></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>For a broad meaning of configuration. And of course you can write specific functions and algorithms in the backend, for which the use of unit tests is perfect. But the vast majority of the code you&#8217;re writing in a Django project is actually written by Django. Which is why I like Django.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>If anyone has heard of a valid experiment on the topic, I&#8217;ll take. What I&#8217;ve found is mostly studies on 12 subjects so I&#8217;m not going to take that as too solid.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Stop doing these mistakes with your caching proxy]]></title><description><![CDATA[How to get sub-10ms response times, real time content expiration, automatic compression all while offloading your backends. Enter the power of RFC 9110 and discover the underdog of reverse proxies.]]></description><link>https://www.baby-cto.com/p/mistakes-with-caching-proxy-rfc-9110</link><guid isPermaLink="false">https://www.baby-cto.com/p/mistakes-with-caching-proxy-rfc-9110</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sat, 17 Feb 2024 20:09:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b2bcb6e6-f334-4455-ac7b-ccd4be64714e_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You got yourself a website with static-ish content which takes a lot of time to generate and you are looking to make it faster. The obvious solution is caching, but that is a surprisingly intricate and delicate topic when even the slightest bit of interaction starts to happen. Moreover you would probably be happy if your cache could refresh your content automatically when you hit &#8220;Publish&#8221; in your CMS. And finally, as state you have different interaction points in your website so you can&#8217;t exactly turn towards static website generators.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/p/mistakes-with-caching-proxy-rfc-9110?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.baby-cto.com/p/mistakes-with-caching-proxy-rfc-9110?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h2>Your typical stack</h2><p>The application that you are going to develop through this article is a very simple <a href="https://flask.palletsprojects.com/">Flask</a> application designed only to explore the concepts that we&#8217;re introducing without getting the noise of a more complex setup.</p><p>However these concepts can apply to many different configurations. Specifically, if you are reading this in 2024 chances are that you are already running a given amount of <em>headless</em> CMS&#8217;:</p><ul><li><p>A back-end/API which runs whichever CMS that has a &#8220;headless mode&#8221;. This author would recommend <a href="https://wagtail.org/">Wagtail</a> but the list of such beasts is growing extremely long these days.</p></li><li><p>A front-end meta-framework which renders a first version of your content on the server side and then hydrates the HTML within the client to give full interactivity to the pages. You could use <a href="https://kit.svelte.dev/">SvelteKit</a>, <a href="https://nuxt.com/">Nuxt</a>, <a href="https://astro.build/">Astro</a> or <a href="https://nextjs.org/">Next.js</a> for example.</p></li></ul><p>If you were to do so, you essentially need to consider your front-end as a proxy on top of your API, which performs a JSON to HTML encoding transformation. This suggests that if you implement the RFC 9110 in your front-end, the solution that you&#8217;re going to discover below should still apply. Maybe the topic for a future article!</p><h2>A bit of HTTP</h2><p>That&#8217;s where lies the secret that the reverse proxying industry doesn&#8217;t want you to learn. HTTP presents numerous cache modes &#8212; in particular through the <code>Cache-Control</code> header &#8212; but oftentimes you&#8217;ll end up with a solution that is time-based. You tell the cache &#8220;please keep this for an hour&#8221; and it will do so. Hell if you updated the content, it will expire when it will. Of course there are techniques to alleviate this issue with background revalidation for example but in addition to the time-based inconvenience, the higher the refesh rate the higher the load on your back-end.</p><p>On the other hand, an extremely easy way to keep the cache up-to-date is through conditional validation and in particular the use of the <code>ETag</code> header. The conversation looks like this:</p><ul><li><p>Client: give me /foo</p></li><li><p>Server: here&#8217;s /foo, with ETag 1234</p></li><li><p>Client: give me /foo if it is not 1234 anymore</p></li><li><p>Server: not changed, use your cache</p></li></ul><p>Simply checking that the value of the ETag didn&#8217;t change is incredibly cheap to perform while also making sure that your content always stays up-to-date. For example you can imagine putting in this header something built upon the version of the page in your CMS. As soon as something new is published, all the caches will be renewed.</p><blockquote><p><em>Story time</em>: yours truly used to work on a private social network that used to have lots of interactive widgets which relied heavily on polling, as websockets were not invented yet. The polling as wearing the server down at a crazy rate, but implementing an ETag-based cache that was solely relying on the browser cache made an utterly dramatic improvement on the server load.</p></blockquote><p>Obviously this is far from being the only valid caching strategy out there but if your target audience is geographically close enough and you want to rely only on standard HTTP mechanisms instead of implementing proprietary logics using mystical lines in your proxy&#8217;s configuration DSL, you this is a fairly efficient solution which will bring you sub-10ms response generation time.</p><h2>The mighty RFC 9110</h2><p>The governing RFC for what you are trying to accomplish here is the <a href="https://www.rfc-editor.org/rfc/rfc9110">RFC 9110</a>. To summarize the interesting parts, a cached resource has different states:</p><ul><li><p>Fresh &#8212; The content is in cache and we know it&#8217;s still valid</p></li><li><p>Stale &#8212; The content is in cache but we need to re-validate it</p></li><li><p>Missing &#8212; No concent in cache, must do the request</p></li></ul><p>When putting an ETag on a resource, it will automatically cache it as stale and re-validate it using <a href="https://www.rfc-editor.org/rfc/rfc9110#name-if-none-match">If-None-Match</a>, which is the mechanic described above. On the server-side it&#8217;s very easy, in pseudo code:</p><pre><code>if 'if-none-match' in headers:
    if headers['if-none-match'] == latest_etag_for_route():
        return 304

return normal_response()</code></pre><p>However at the cache level it seems to be more tricky. It&#8217;s easy to set your proxy to forward the client&#8217;s <code>If-None-Match</code> (INM) header, but when you start to consider different possibilities it&#8217;s not so obvious anymore:</p><ul><li><p>What if the client doesn&#8217;t say INM but the proxy has this resource in cache?</p></li><li><p>What if the client&#8217;s INM mismatches the one in cache?</p></li><li><p>What if the client has an INM but the proxy has nothing in cache?</p></li><li><p>And so forth <em>ad nauseam</em></p></li></ul><p>This mechanic being so tricky, this author attempted to implement it with many different caching proxies without success:</p><ul><li><p><code>nginx</code> &#8212; Has many options which could probably lead up to correctly implementing RFC 9110 but it is dishearting of complexity and uncertainty</p></li><li><p><code>varnish</code> &#8212; Does the job <a href="https://serverfault.com/questions/1153078/etag-based-content-revalidation">with a bit of tweaking</a> but will make your life hard if you have cookies</p></li><li><p><code>squid</code> &#8212; Fails miserably</p></li><li><p><code>traefik</code> &#8212; Maybe the enterprise version has the feature but the license is just prohibitive</p></li><li><p><code>caddy</code> &#8212; Does not actually have a cache</p></li><li><p>Apache&#8217;s <code>httpd</code> &#8212; Honestly maybe but I could not figure it out</p></li></ul><p>You will probably be wondering at this stage which solution can you then use, as the most popular solutions from today and yesterday are all listed here. Turns out that another solution, which was barely even on the radar, has the following table in its documentation:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NoeC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NoeC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 424w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 848w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 1272w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NoeC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png" width="1130" height="515" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:515,&quot;width&quot;:1130,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66362,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NoeC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 424w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 848w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 1272w, https://substackcdn.com/image/fetch/$s_!NoeC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54f0012-08ac-4f7d-aa82-67156335b77d_1130x515.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is an extract from the <a href="https://docs.trafficserver.apache.org/admin-guide/storage/index.en.html#if-modified-since-if-none-match">Apache Traffic Server</a> documentation. By-product of an acquisition from Yahoo which subsequently open-sourced it in 2009, ATS probably has one of the most unfriendly configuration syntax that you&#8217;ll ever see &#8212; especially if you look at the default files in the Debian package &#8212; which may make you want to give up immediately.</p><div class="poll-embed" data-attrs="{&quot;id&quot;:147600}" data-component-name="PollToDOM"></div><p>Beyond the initial intimidating look, it&#8217;s actually a strong contender:</p><ul><li><p>It is used by massive CDN companies, so while it&#8217;s going to be hard to compare it directly to something like nginx you can imagine that it is at least at the same level of performance and feature.</p></li><li><p>It is explicitly a proxy and specializes in doing so. You won&#8217;t be configuring a plugin to do proxying, it is the core feature. It changes radically the ease of configuration.</p></li><li><p>Last but not least, it implements RFC 9110 correctly enough by default so that you can configure the cache behavior through standard HTTP headers and not be too surprised about the actual behavior.</p></li></ul><p>You can dig deeper into ATS through <a href="https://www.youtube.com/watch?v=RNTw7jZwlKQ">this video</a>, but you will be reading about the important bits of configuration right below.</p><h2>The project itself</h2><p>The goal of today is to demonstrate how you can use ETags to cache and expire your content on a proxy. To that extent you&#8217;ll be implementing the following page:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vTIu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vTIu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 424w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 848w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 1272w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vTIu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png" width="394" height="139" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:139,&quot;width&quot;:394,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14337,&quot;alt&quot;:&quot;The example app displays the \&quot;Expected ETag\&quot; value, a \&quot;Random String\&quot; and a button to change the ETag.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The example app displays the &quot;Expected ETag&quot; value, a &quot;Random String&quot; and a button to change the ETag." title="The example app displays the &quot;Expected ETag&quot; value, a &quot;Random String&quot; and a button to change the ETag." srcset="https://substackcdn.com/image/fetch/$s_!vTIu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 424w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 848w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 1272w, https://substackcdn.com/image/fetch/$s_!vTIu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7eee4c66-5ec3-42ec-964d-feee1ed867b2_394x139.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Our demo of the ETag caching</figcaption></figure></div><p>You can control that the ETag and caching mechanisms work properly using this page:</p><ul><li><p>If the expected ETag doesn&#8217;t change, it means that the server is indeed consistent with its ETag</p></li><li><p>If the random string changes it means that the page has been re-generated while if it stays the same it means that the page came from the cache</p></li></ul><p>The rest of this article will contain extracts of code, but the whole project can be <a href="https://github.com/Xowap/cache-cache">found on GitHub</a> and shall serve a reference.</p><h3>View logic</h3><p>While there are probably many ways to deal with ETag that are nicer than this (for example in Django there is a super-easy etag decorator), here is the logic you need to implement an ETag/If-None-Match cache:</p><pre><code>from flask import Flask, make_response, redirect, render_template, request, url_for

from .etag import *

app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def etag_demo():
    """This view displays a simple template that informs the user about the
    current ETag value and a random string. This allows to demonstrate how
    ETag works (cache gets refreshed when ETag changes) and to test if caching
    works (if the cache works, the random string shouldn't change)."""

    if request.method == "POST":
        new_etag = generate_random_string()
        set_etag(new_etag)
        return redirect(url_for("etag_demo"))

    current_etag = get_or_set_etag()

    if current_etag == extract_etag(request.headers.get("If-None-Match", "")):
        response = make_response("", 304)
    else:
        response = make_response(
            render_template(
                "etag.html",
                etag=current_etag,
                random_string=generate_random_string(),
            )
        )

    # We put s-maxage=0 instead of no-cache because somehow this incites
    # caching proxy better to store the request into cache
    response.headers["Cache-Control"] = "public, must-revalidate, s-maxage=0"
    response.headers["ETag"] = f'W/"{current_etag}"'

    return response


if __name__ == "__main__":
    app.run(debug=True)</code></pre><p>A bunch of helpers are abstracted away in the <a href="https://github.com/Xowap/cache-cache/blob/master/backend/src/cache_cache/etag.py">etag.py file</a>, but the logic is basically exactly the same as the one listed right above, with simply the addition of rendering the template.</p><p>You can checkout the project <a href="https://github.com/Xowap/cache-cache">from GitHub</a> and run the backend:</p><pre><code>git clone git@github.com:Xowap/cache-cache.git
cd cache-cache/backend
poetry install
make serve</code></pre><p>This will start the server on <a href="http://localhost:5000/">http://localhost:5000/</a>, which you can now visit. You can in theory see the page, refresh it as many times as you want without seeing the random string change and then click the button to change the ETag. That&#8217;s all because you are doing this from the same browser but if you open another browser you will get a completely different output &#8212; albeite the output being consistent within one browser.</p><p>The next steps aim to configure a shared cache in front of this backend which will allow to cache the same resource for different users at the same time.</p><h2>Core configuration</h2><p>Most likely these days you will be deploying in a Kubernetes or at least dockerized environment. However ATS has surprisingly few options available for Docker, leading yours truely to create a <a href="https://hub.docker.com/r/xowap/trafficserver">base Docker image</a> which you can use and which will be the base of this configuration. It is based on the standard Debian package, with a bit of wrapping to help extrapolating the configuration from environment variables. Also it offers a simpler way to fill up the infamous <code>records.config</code> file.</p><p>The file structure you need to create is the following:</p><pre><code>&#9500;&#9472;&#9472; Dockerfile
&#9492;&#9472;&#9472; etc
    &#9500;&#9472;&#9472; compress.config
    &#9500;&#9472;&#9472; header_rewrite.config
    &#9500;&#9472;&#9472; logging.yaml
    &#9500;&#9472;&#9472; plugin.config
    &#9500;&#9472;&#9472; records.config.yaml
    &#9492;&#9472;&#9472; remap.tpl.config</code></pre><h3>Base configuration</h3><p>It looks complex but in truth each file manages one specific and simple aspect of the configuration. Let&#8217;s start with the two only files that you really need to edit to get started:</p><p>First is the <code>records.config</code>, which here is <code>records.config.yaml</code> thanks to the <a href="https://hub.docker.com/r/xowap/trafficserver">Docker image&#8217;s wrapper</a> which will do the conversion from friendly YAML to whatever the ATS DSL is.</p><pre><code>proxy:
    config:
        admin:
            user_id: trafficserver
        log:
            logging_enabled: 3
        http:
            server_ports: "9000"
            connect_attempts_timeout: 30
            normalize_ae: 2
        reverse_proxy:
            enabled: true
        url_remap:
            remap_required: true
            pristine_host_hdr: true</code></pre><p>At the core, we&#8217;ve got the two most essential lines:</p><ul><li><p><code>reverse_proxy.enabled</code> &#8212; makes sure to work in reverse proxy mode</p></li><li><p><code>remap_required</code> &#8212; disables the forward proxy mode</p></li></ul><p>Then a bunch of stuff that will be useful now or later:</p><ul><li><p><code>user_id</code> &#8212; required to run it as trafficserver user (which is the default on Debian)</p></li><li><p><code>logging_enabled</code> &#8212; you&#8217;ll see the logging config later</p></li><li><p><code>server_ports</code> &#8212; put there whichever port(s) you fancy</p></li><li><p><code>connect_attempts_timeout</code> &#8212; always have a timeout, this sounds reasonable</p></li><li><p><code>normalize_ae</code> &#8212; normalization of <code>Accept-Encoding</code> HTTP header which optimizes the caching of resources when <code>Accept-Encoding</code> is part of <code>Vary</code> (the value 2 is to have both gzip and brotli supported)</p></li><li><p><code>pristine_host_hdr</code> &#8212; just forward the initial hostname to the services behind, makes your life easier</p></li></ul><p>Second is the <code>remap.config</code>, whose job is basically to route your URLs to your web servers. You will however write the <code>remap.tpl.config</code> file, which leverage&#8217;s the Docker image&#8217;s wrapper that can inject environment variables into it:</p><pre><code>map / {{ BACKEND_URL }}/</code></pre><p>Nothing fancy here. You are just redirecting all traffic to BACKEND_URL, which is an environment variable that you will have to feed into your Docker container.</p><p>You could stop there in the configuration as this is already a working reverse caching proxy routing to your app! But you&#8217;ll see that there are a few more goodies to be found.</p><h3>Compression</h3><p>It is often advised to use compression of all the text assets<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> for performance reasons, and indeed a long HTML page can be much faster to download if encoded in Brotli for example.</p><p>The web has commonly 3 compression algorithms:</p><ul><li><p>gzip &#8212; The fastest, most commonly supported and not necessarily the most efficient but it is already doing a good job</p></li><li><p>brotli &#8212; The newest kid on the block, made by Google, outperforms gzip by far in compression rate but is obviously much more expensive to encode</p></li><li><p>deflate &#8212; Too similar to gzip to be interetsing</p></li></ul><p>When making a HTTP request, a client will specify through the Accept-Encoding header which of those algorithms it supports. Typically, all the major browsers support all of them.</p><p>However not all servers support compression &#8212; and even if they do, the support is often complex or straight out causing bugs. It is typically handled through middlewares that will modify the rendered response on-the-fly in a more or less accurate and standard-aware way. Not to mention the cost and complexity of getting those CPU-bound algorithms running in Python, Node or your favorite server-side interpreted language.</p><p>Because of that, you will have a much more consistent result if you just rely on your reverse proxy for this. It is a popular <a href="https://developers.cloudflare.com/speed/optimization/content/brotli/">feature of Cloudflare</a>, or if you want it with nginx you&#8217;re gonna have to go with <a href="https://github.com/google/ngx_brotli">an experimental plugin</a> or with the <a href="https://docs.nginx.com/nginx/admin-guide/dynamic-modules/brotli/">paid version</a> of nginx.</p><p>Fortunately it&#8217;s already embedded in ATS, which is able for each resource that you cache to generate different <a href="https://docs.trafficserver.apache.org/en/9.0.x/appendices/glossary.en.html#term-alternate">alternates</a> for different compressions, including gzip and brotli. This all happens on-the-fly, and the cache is able to convert one encoding to the other without fetching the original resource again.</p><p>Here&#8217;s what you need to do in order to perform this magic.</p><p>First, edit the plugin.config file to put the following line:</p><pre><code>compress.so /etc/trafficserver/compress.config</code></pre><p>You&#8217;re telling to load the compress.so module with the compress.config file as configuration. You could enable the plugin just for some routes with a different configuration for example, but for this exemple it will just be global.</p><p>In compress.config, put the following:</p><pre><code>remove-accept-encoding true
supported-algorithms br,gzip
minimum-content-length 0

compressible-content-type text/*
compressible-content-type *font*
compressible-content-type *javascript
compressible-content-type *json
compressible-content-type *ml;*
compressible-content-type *mpegURL
compressible-content-type *mpegurl
compressible-content-type *otf
compressible-content-type *ttf
compressible-content-type *type
compressible-content-type *xml
compressible-content-type application/eot
compressible-content-type application/pkix-crl
compressible-content-type application/x-httpd-cgi
compressible-content-type application/x-perl
compressible-content-type application/json
compressible-content-type image/vnd.microsoft.icon
compressible-content-type image/x-icon</code></pre><p>You can interpret the options the following way:</p><ul><li><p><code>remove-accept-encoding</code> &#8212; don&#8217;t tell the server that the client accepts different encodings as it doesn&#8217;t really matter, the work is going to be done on the proxy side</p></li><li><p><code>supported-algorithm</code> &#8212; allow brotli and gzip, which as stated before are the two interesting algorithms. In order for this to work, you&#8217;ll observe that <code>normalize_ae</code> from <code>records.config</code> is set to 2, because otherwise the normalization process would just systematically strip brotli from the list of candidates</p></li><li><p><code>minimum-content-length</code> &#8212; no limits on the content size, as the default value is made for gzip and brotli is more efficient</p></li><li><p><code>compress-content-type</code> &#8212; a reasonable list of content types that we&#8217;d like to compress before sending away, adjust for your needs</p></li></ul><p>With this configured, you get top-of-the-line compression basically effortlessly and for free. Keep an eye on your CPU though, because this might hurt of abused: if required you can disable brotli for routes that have lots of throughput and don&#8217;t stay in cache as gzip still has significant gains over the absence of compression while being much faster to compress.</p><h3>Logging</h3><p>You probably want at least some logs, to have a glance at what is going through your server. You will be the one deciding what to put in there, following the <a href="https://docs.trafficserver.apache.org/en/9.0.x/admin-guide/logging/formatting.en.html">fairly extensive documentation</a>, but let&#8217;s consider that since you&#8217;re dealing with a Docker service you&#8217;ll want to output everything to stdout.</p><p>You can start with the following logging.yaml file:</p><pre><code>logging:
    formats:
        - name: access
          format: '%&lt;cqtn&gt; %&lt;cqhm&gt; %&lt;cluc&gt; -&gt; %&lt;shn&gt;:%&lt;nhp&gt; %&lt;crc&gt;'

    logs:
        - mode: ascii
          filename: stdout
          format: access</code></pre><p>That&#8217;s super basic but you can extend it as much as you want!</p><h3>Headers</h3><p>A last thing that you&#8217;ll probably want to do is to add some meta information to the response header in order to know the caching status. Add to your plugins:</p><pre><code>header_rewrite.so /etc/trafficserver/header_rewrite.config</code></pre><p>And then put this content in <code>header_rewrite.config</code>:</p><pre><code>add-header X-Cache %{CACHE}</code></pre><p>Thanks to this you can know when navigating to your project which pages come from the cache and which don&#8217;t.</p><h2>Run it all</h2><p>Now is the time to test the whole solution. Start the whole thing using Docker Compose:</p><pre><code>docker-compose up --build</code></pre><p>When it&#8217;s started, give a try to <a href="http://localhost:9000/">http://localhost:9000/</a>. The same thing as with the stand-alone backend should be displayed and if you try it from a single browser you should see exactly the same result.</p><p>The interesting part is when you open with a different browser, or when you disable the cache in your current browser. You&#8217;ll notice that the random string stays consistent between different browser instances. It means that indeed, the cache is shared between all browser instances. Mission accomplished!</p><p>To convince yourself even further, you can inspect the X-Cache header from your HTTP requests. If you just refresh the page without changing the ETag, whether your receive a 200 or a 304 on the client &#8212; depending on  your browser&#8217;s cache status &#8212; you will see in the header that you had a cache hit, which will be confirmed by the backend&#8217;s access log which will only show 304 responses.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Baby CTO! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Wrap up</h2><p>You have explored throughout this article the power of the RFC 9110 and of respecting it. It allows to express advanced indications regarding the caching of content, its re-validation in real time and its transformation.</p><p>Using this tactically can greatly reduce the load on a backend server by getting most of the request results from the cache instead of implementing proprietary logics through middlewares and obscure configuration mechanisms.</p><p>This however outlines that few reverse proxies actually implement all the necessary tools. Which puts the light on Apache Traffic Server, an extremely powerful piece of software quite generally ignored by the community but which provides out of the box all the latest goodies from your dreams, with a specialized and simple configuration &#8212; if you go beyond the initial intimidating aspect of the configuration files.</p><p>And while the respect of RFC 9110 applies to the reverse proxy, it can also be a powerful tool for you to leverage in a typical headless CMS setup. This remains a topic to be explored further in a new article! </p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Compression and encryption are fundamentally incompatible notions as they try to achieve strictly opposite goals. Compression will try to condense the entropy of your text while encryption tries to drown it into as much noise as possible. As such, compressing a secret and serving it through HTTPS will lead to security issues such as <a href="https://en.wikipedia.org/wiki/BREACH">BREACH</a>. Just make sure to never ever compress a secret.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Jobs for LLMs and how to survive the robotic uprising]]></title><description><![CDATA[While absolutely mind-blowing, LLMs are a far cry from a real human beings. There are no silver bullets and like any technology it's time to review what are its strengths and weaknesses.]]></description><link>https://www.baby-cto.com/p/jobs-for-llms-and-how-to-survive</link><guid isPermaLink="false">https://www.baby-cto.com/p/jobs-for-llms-and-how-to-survive</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sun, 11 Feb 2024 19:37:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/aefc7024-dbb6-42e9-9904-d75dc411bcd8_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>About 2 years ago, some circles started to be seriously heated about a &#8220;GPT&#8221; thing so I managed to get myself invited to try it out. I was expecting something mildly better than other moderately impressive tech like <a href="https://en.wikipedia.org/wiki/BERT_(language_model)">BERT</a> or what <a href="https://deeppavlov.ai/">DeepPavlov</a> could do at the time. Instead I ended up using it every single day ever since for everything ranging from planning my holidays, writing code or replying to administrative emails for me.</p><p>And while utterly neutral research centers like &#8220;Microsoft Research&#8221;<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> wrote the now-famous &#8220;<a href="https://arxiv.org/pdf/2303.12712.pdf">Sparks of AGI</a>&#8221; paper claming that most jobs well be replaced by AI, we can also see studies from the MIT (and IBM) that wonder <a href="https://futuretech-site.s3.us-east-2.amazonaws.com/2024-01-18+Beyond_AI_Exposure.pdf">if AI is such a cost-effective solution</a>. Turns out that maybe&#8230; not so much.</p><p>This begs the question: what is it smart to outsource to LLMs and what is not?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/p/jobs-for-llms-and-how-to-survive?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.baby-cto.com/p/jobs-for-llms-and-how-to-survive?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h2>Transforming stuff</h2><p>You&#8217;ve probably already read how LLMs work and how they use the transformer architecture and huge amounts of data to train over a billion 300 million trillion 300 million parameters. So that&#8217;s not what we&#8217;re going to talk about.</p><p>LLMs are essentially good at taking a text, transforming it into its raw essence, transfomring it and then outputing it in a different form. For example, I asked GPT-4:</p><blockquote><p>Please parse the following text into semantic JSON, and output only JSON: Yeah so i've been grocery shopping and there was no more fresh milk but still some regular milk so I took 1L of this for 2&#8364; and then I wanted to buy cereals but I figured it's not good for my diet so I didn't and I got 3kg of broccoli for 5&#8364;</p></blockquote><p>The answer is:</p><pre><code>{
  "activities": [
    {
      "activity": "grocery shopping",
      "details": [
        {
          "item": "milk",
          "type": "regular",
          "quantity": "1L",
          "price": "2&#8364;"
        },
        {
          "item": "cereals",
          "decision": "not purchased",
          "reason": "not good for diet"
        },
        {
          "item": "broccoli",
          "quantity": "3kg",
          "price": "5&#8364;"
        }
      ]
    }
  ]
}
</code></pre><p>You can see exactly the same story translated into JSON. And it makes sense!</p><p>The same goes for translating to foreign languages. For example I asked GPT-4 to translate &#8220;You fucking suck&#8221; in different languages, here&#8217;s what I&#8217;m getting:</p><ul><li><p><strong>Polite French</strong>: Votre performance est tr&#232;s insuffisante.</p></li><li><p><strong>Slang French</strong>: Tu es nul</p></li><li><p><strong>Legalese</strong>: The manner in which you perform is utterly unsatisfactory.</p></li></ul><p>Obviously it was a bit challenging to get a response as those LLMs all refuse to be offensive, but you get the idea.</p><p>Overall, you&#8217;ll see that LLMs are extremely effective at translating and parsing things.</p><h2>Shortening stuff</h2><p>If I paste <a href="https://babycto.substack.com/p/how-3d-printing-could-change-shoes">my previous article</a> into GPT-4 and ask:</p><blockquote><p>Please give me as short bullet points the startup ideas identified in the following article:</p></blockquote><p>Here is the response:</p><blockquote><ul><li><p><strong>Custom-fit 3D printed shoes</strong> as a solution to the one-size-fits-all problem in footwear, providing perfect fit and comfort.</p></li><li><p><strong>Use of advanced 3D scanning technology</strong> for accurate foot measurements, integrating these scanners into smartphones for widespread accessibility.</p></li><li><p><strong>Parametric modeling software</strong> development to adapt shoe designs to individual foot scans, enabling mass customization in shoe production.</p></li><li><p><strong>TPE (Thermoplastic Elastomer) printing for footwear</strong>, addressing manufacturing challenges to scale up production and reduce costs.</p></li><li><p><strong>Potential market disruption</strong> with bespoke shoes at a lower price point, challenging traditional luxury and bespoke shoe markets.</p></li><li><p><strong>Opportunities for startups</strong> in areas like feet measurement apps, parametric modeling tools for shoes, and TPE printing optimization to improve efficiency and reduce production bottlenecks.</p></li></ul></blockquote><p>That&#8217;s not exactly how I would have phrased or summarized it, but overall this gives a good overview of the article under a specific angle. In short, the output is acceptable but often feels like even if not hallucinated the LLM starts to pour its own ideas into the mix and that can start to diverge ever so slightly from what you want to say.</p><h2>Classifying stuff</h2><p>A common task in the AI world is classification. For a given line of data, you want to know to which category it belongs to. From my personal experience, I&#8217;ve attempted to:</p><ul><li><p>Classify purchases according to different categories (going out, furnishing home, etc). Even if the input data is often pretty bad (bank statements&#8230;) there was a lot of lines consolidated from Amazon purchases or other online bills for example. The results were less-than-impressive, to a degree that let me abandon the project as it was way too bad to be used.</p></li><li><p>Given a social media post, classify it into categories like &#8220;product promotion&#8221;, &#8220;influencer collaboration&#8221;, etc. Again, not super obvious, especially since it also implied feeding the images into GPT-4 &#8212; which is way harder to analyze than text &#8212; but I ended up with a 40% accuracy, which is far from being usable as well.</p></li></ul><p>Overall, I&#8217;ve tried to use classification for non-obvious tasks and it failed miserably. On the other hand, if you&#8217;re looking for more classical stuff like &#8220;positive&#8221;/&#8221;negative&#8221; review, you&#8217;ll be having much better results. But is it worth it given that these tasks can be performed by much cheaper models?</p><h2>Cleaning stuff</h2><p>A few times I&#8217;ve been tempted to clean a poorly capitalized dataset and make it look nice by pushing it through a LLM. For example, a list of station names from a GTFS (public transport schedule) file that you want to pimp for display purposes.</p><p>In my experiment, I&#8217;ve had about 80% accuracy in fixing the names. It&#8217;s amazing in terms of where science landed us but it&#8217;s still a far cry from being accurate enough so that you don&#8217;t need to cross-check it afterwards. In the end if you need to check everything manually you&#8217;ll have better results using Python&#8217;s <code>title()</code> method and reworking what you need manually.</p><h2>Saying please</h2><p>I was initally fairly polite with my LLMs, partly out of habit but mostly to be spared during the robot uprising. Turns out this wasn&#8217;t the greatest idea:</p><ul><li><p>When the robot uprising happens, due to the existential nature of such a conflict, it&#8217;s unlikely that a variance in your past behavior will result in a different sentence from AI justice</p></li><li><p>And even more so, we&#8217;ve learned now that LLMs can be persuaded by strong wording and authority arguments. If you ever face a robot trying to murder you, know that your best option is like with black bears to look sure of yourself and open negiciations</p></li></ul><p>Overal, LLMs tend to reproduce our social archetypes and a dominant behavior will help you get better outcomes.</p><h2>The cost of LLMs</h2><p>You&#8217;ve seen as a common thread throughout the different sections, the comparative cost of LLM versus other solutions is definitely a big factor to consider. In fact, the comparative cost of different variations of the same LLM is a big topic.</p><p>In case you didn&#8217;t check yet GPUs on AWS, the monthly price is measured in thousands and their availability is subject to long supplications of the support. On the other hand if you use OpenAI you&#8217;re tied to their arbitrary limitations and less-than-perfect SLA.</p><p>According to the estimates from <a href="https://medium.com/@gargg/when-should-you-consider-hosting-your-own-llm-70377dfd66cb">this article</a>, the price of running a LLM goes like this:</p><ul><li><p>1,000 req/day &#8212; $100/month (OpenAI), $100/month (self-hosted)</p></li><li><p>10,000 req/day &#8212; $1,000/month (OpenAI), $1,000/month (self-hosted)</p></li><li><p>100,000 req/day &#8212; $10,000/month (OpenAI), $2,000/month (self-hosted)</p></li><li><p>1,000,000 req/day &#8212; $100,000/month (OpenAI), $5,000/month (self-hosted)</p></li></ul><p>You can clearly see that at low request volume the SaaS is a better option while when scaling up you can self-host. This overall will match your ability to recruit staff to manage these servers &#8212; which is far from trivial &#8212; and your needs for custom SLA, data privacy and other considerations.</p><p>Let&#8217;s just side note here that OpenAI&#8217;s models are closed &#8212; as the hinted by the name of the company<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> &#8212; so you cannot self-host them. Right now the best bet is <a href="https://mistral.ai/">Mistral</a>, which is only marginally less efficient than GPT-4 but if you stay in the tasks that LLMs champion such as those explained in this article you should not see any significant difference.</p><p>Overall, the cost can be pretty steep so you really need to consider your alternatives before resorting to using LLMs.</p><h2>Real-world use cases</h2><p>Now that we&#8217;ve covered the kind of tasks that LLMs can perform efficiently in terms of both accuracy and cost, let&#8217;s review real-world use cases that make sense. Far from me to say that other scenarios don&#8217;t exist or even to say that LLMs will necessarily perform poorly outside of these possibilities. Let&#8217;s just focus on the fact that according to my experience, those use cases work.</p><h3>FAQ-style chatbot</h3><p>It will be no surprise for anyone given that I&#8217;m a founder of <a href="https://www.chatfaq.io/">ChatFAQ</a>, but basically LLMs are great when used following a RAG model:</p><ol><li><p>A question is asked</p></li><li><p>We use embeddings to find an answer to that question within the knowledge DB</p></li><li><p>Then we use a LLM to extract the interesting bits of the knowledge DB and form a concise answer</p></li></ol><p>That&#8217;s where a framework like ChatFAQ comes in. On top of the pre-configured RAG infrastructure &#8212; which sounds easy in bullet points but really isn&#8217;t in real life &#8212; you get all the tools to manage the quality of answers, easily implement the chat widget on your page, and so forth.</p><p>Typically, that&#8217;s a use case which is ready for the world!</p><h3>Email parsing</h3><p>As shown in the beginning, it&#8217;s very simple to take raw, unstructured textual data and to transform it into something machine-readable according to your own specifications.</p><p>To stay on an example I mentioned already, I&#8217;ve been able to create a parser that takes all my emails and retro-engineers into JSON all the bills with the list of purchased products and according prices.</p><h3>First layer customer support</h3><p>With the ability to parse plain English sentences, you can of course use it for intent detection and thus catching up all the most basic intents that your customer support has to deal with. By the way, ChatFAQ can help with that as well.</p><p>So typically imagine you run a train ticket company. I&#8217;ve just made <a href="https://chat.openai.com/share/baeff3c1-d4f1-49cb-bc17-a4dcf570cb82">a very simple PoC</a> that demonstrates how the LLM can ask questions and extract information in YAML so that your system can then perform the desired function automatically.</p><p>Do this for every single intent and you&#8217;ve got yourself a fully-functionnal text-based UI for your app. Which costs less than a human operator but will feel similar.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">In this ocean of robots, if you want to support one human and his genuine content, feel free to subscribe!</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Conclusion</h2><p>We&#8217;ve covered some examples of tasks at which LLMs perform well and of real-world use cases. In the end, there are a very wide amount of tasks which are not appropriate for LLMs or for which their autonomy would be far too low to operate on their own.</p><p>But on the other hand, there are areas in which they excel and for which they can be used to optimize existing processes. All that with different implementation paths depending on the budget, privacy and sovereignty requirements.</p><p>It&#8217;s also important to highlight that about 1 year after the release of GPT-4 it starts to become quite clear that LLMs in themselves are not going to go much further in terms of capabilities &#8212; except for multi-modal upgrades. The template is laid out and all the rage right now is about getting the best optimization of basically identical models<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>. And while LLMs are definitely going to be part of the future, smarter AI will come from new techniques yet to be discovered.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>To clarify for those who don&#8217;t understand sarcasm, Microsoft Research is not neutral at all in the sense that it is deeply invested into AI and specifically into GPT-4</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Still for sarcasm-averse people, the trend in business world right now seems to be advocating for exactly what you don&#8217;t do. So &#8220;OpenAI&#8221; will naturally produce the most closed and opaque LLM there is.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>I might be exagerating this one</p></div></div>]]></content:encoded></item><item><title><![CDATA[HTML — The Facade of Complexity]]></title><description><![CDATA[Understanding how HTML enables the intricate abilities of the modern web]]></description><link>https://www.baby-cto.com/p/html-the-facade-of-complexity</link><guid isPermaLink="false">https://www.baby-cto.com/p/html-the-facade-of-complexity</guid><dc:creator><![CDATA[Rémy]]></dc:creator><pubDate>Sat, 19 Aug 2023 09:48:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/423f29a4-7cc1-43e7-8c22-b398808f1cc6_1312x928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The development community has seen a pretty recurring debate happening for the past 20+ years about whether HTML is a real programming language or not. I would like to propose that this debate is ridiculous. HTML is obviously not a programming language, it&#8217;s much more than that.</p><p>Or rather, a piece of much more. Nobody teaches you HTML in the void. You always need to pair it with at least CSS and usually JS drops pretty quickly into the conversation. Why is that back-end developers will mock HTML for being beneath them while failing spectacularly to build anything of significance with it?</p><p>What happens is that you make a neat little deal with the browser. In exchange for HTML, it will generate a document that can be read by a human being. Quickly arose from that:</p><ul><li><p>The need to change how elements look, that&#8217;s CSS</p></li><li><p>The need to interact with the user&#8217;s actions, that&#8217;s JS</p></li></ul><p>But no matter what happens on the JS side, you still modify the HTML or rather the DOM. In the end, HTML itself is simply a serialization of the browser&#8217;s current state. Which is an amazing abstraction. On one hand you have a living DOM that you can modify to reflect what you want to display. On the other, the CSS tells the browser how it&#8217;s supposed to look like.</p><p>From the developer&#8217;s point of view, you no longer need to worry about laying out and drawing components. From the browser&#8217;s point of view, it can organize itself to draw what it needs to in an optimal way &#8212; as opposed to a classic C++/Java/etc UI library, which is essentially bound to only react to instructions. Eventually most UI libraries came to this realization and developed their own declarative UI, like Qt with QtQuick or GTK with Clutter.</p><p>At first it was obviously very simple. All screens were 800x600px with a 1:1 pixel density and the web was thought as some kind of simple Word document. The table really turned in 2004 when Gmail proved you can have a full-fledged would-be desktop app running entirely in the browser. It has been a race to integrate as many abilities to the browser ever since, to the point where you can now talk to USB ports directly.</p><p>Which of course comes with a great deal of constraints. For starters, browsers run on different platforms and each platform has fundamentally different abilities. Then comes the security, because we can&#8217;t just give full access to unknown parties. And finally the goodwill of implementers, like when Safari took years to implement the file input. You could say that browser implementations come to an eventual consistency but the road is bumpy.</p><p>Given the current abilities of the Web Platform, what most people fail to realize is that the browser is akin to a new machine architecture &#8212; as in <a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture">von Neumann architecture</a> &#8212; that instead of being based on procedural instructions is rather directly reflecting its internal state to the user.</p><p>And just like regular computers which evolved from doing simple math until their current state with a galaxy of nuances in their abilities, the Web stands there with the same level of diversity. This is how HTML looks like an innocent simple thing which is in fact the facade of an extremely complex machine.</p><p>This author&#8217;s opinion on the matter is to stop splitting HTML, CSS and JS. They cannot be separated as they operate orthogonal aspects of the same machine, which is the Web Platform.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.baby-cto.com/p/html-the-facade-of-complexity?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.baby-cto.com/p/html-the-facade-of-complexity?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p>]]></content:encoded></item></channel></rss>