<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Akash Singh</title>
    <description>The latest articles on Forem by Akash Singh (@akashxlabs).</description>
    <link>https://forem.com/akashxlabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3799544%2F5b754fac-4cfb-413c-bd44-c21d61f360b1.jpeg</url>
      <title>Forem: Akash Singh</title>
      <link>https://forem.com/akashxlabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/akashxlabs"/>
    <language>en</language>
    <item>
      <title>One Jinja2 template, 147 calculator pages — here's the architecture</title>
      <dc:creator>Akash Singh</dc:creator>
      <pubDate>Thu, 19 Mar 2026 06:53:58 +0000</pubDate>
      <link>https://forem.com/akashxlabs/one-jinja2-template-147-calculator-pages-heres-the-architecture-137g</link>
      <guid>https://forem.com/akashxlabs/one-jinja2-template-147-calculator-pages-heres-the-architecture-137g</guid>
      <description>&lt;p&gt;Last month someone on Dev.to asked how many templates I use for my calculator pages. When I said "one" they thought I was joking.&lt;/p&gt;

&lt;p&gt;I'm not. One Jinja2 template renders 147 different calculator pages. BMI calculators, unit converters, loan estimators, percentage tools — all the same HTML file. Here's the pattern that makes it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with 147 separate files
&lt;/h2&gt;

&lt;p&gt;Imagine this: you want to change the layout of your calculator result section. With 147 separate template files, that's 147 edits. Miss one and now your kilometers-to-miles converter has a different layout than everything else. And good luck catching that in code review.&lt;/p&gt;

&lt;p&gt;I learned this the painful way. My first 20 calculators each had their own template. By calculator 21, I was copy-pasting so much that I introduced 3 different bugs from stale HTML. Something had to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture (4 layers)
&lt;/h2&gt;

&lt;p&gt;The whole pattern comes down to four layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL request → Route handler → Data config → Universal template → Page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me walk through each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: The route handler
&lt;/h3&gt;

&lt;p&gt;One FastAPI route catches every calculator URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/{category_slug}/{calc_slug}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTMLResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calc_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calc_slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CALCULATORS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;category_slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Calculator not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CALC_CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_related_calculators&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc_slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemplateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calc_tool.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category_slug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;related&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;related&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;/calc/length/cm-to-inches&lt;/code&gt;, &lt;code&gt;/calc/health/bmi-calculator&lt;/code&gt;, &lt;code&gt;/calc/finance/percentage-calculator&lt;/code&gt; — they all hit this same function. The URL tells it which data to pull, and the template handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: The data configuration
&lt;/h3&gt;

&lt;p&gt;This is where the magic actually lives. Every calculator is a Python dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CALCULATORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bmi-calculator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multi_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BMI Calculator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BMI Calculator — Body Mass Index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;70&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;175&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;formula_js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight / Math.pow(height / 100, 2)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your BMI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kg/m²&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ranges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;18.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Underweight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#38bdf8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;24.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Normal weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#22c55e&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is BMI?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BMI is a measure...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;formula_js&lt;/code&gt; field — that's a JavaScript expression stored in Python config. The template injects it into a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag on the page. The browser does the actual math. My server never touches user data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Auto-generating converters
&lt;/h3&gt;

&lt;p&gt;Manually writing 147 of those dictionaries would defeat the purpose. So I wrote helper functions that generate converter pairs automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;to_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generate BOTH directions of a converter.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_conv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;to_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_conv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;from_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_abbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line of code creates two calculators. "cm to inches" and "inches to cm" from a single &lt;code&gt;_pair()&lt;/code&gt; call. 12 pairs give me 24 length converters. Do that across length, weight, temperature, volume, area, speed, data, and time — and you get most of those 147 pages from maybe 80 lines of config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: The universal template
&lt;/h3&gt;

&lt;p&gt;The Jinja2 template branches on &lt;code&gt;calc.type&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if calc.type in ['converter', 'temperature'] %}
    &amp;lt;input type="number" id="calc-input" placeholder="Enter value"&amp;gt;
    &amp;lt;div id="calc-result-value"&amp;gt;—&amp;lt;/div&amp;gt;

    {% if calc.quick_table %}
    &amp;lt;table class="calc-ref-table"&amp;gt;
        {% for val in calc.quick_table %}
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;{{ val }}&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;—&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
        {% endfor %}
    &amp;lt;/table&amp;gt;
    {% endif %}

{% elif calc.type == 'multi_input' %}
    {% for inp in calc.inputs %}
    &amp;lt;label&amp;gt;{{ inp.label }} ({{ inp.unit }})&amp;lt;/label&amp;gt;
    &amp;lt;input type="number" id="calc-{{ inp.id }}"
           data-var="{{ inp.id }}"
           placeholder="{{ inp.placeholder }}"&amp;gt;
    {% endfor %}

{% elif calc.type == 'percentage' %}
    {% for mode in calc.modes %}
    &amp;lt;button class="calc-mode-tab"
            data-formula="{{ mode.formula_js }}"&amp;gt;
        {{ mode.label }}
    &amp;lt;/button&amp;gt;
    {% endfor %}

{% elif calc.type == 'date' %}
    &amp;lt;input type="date" id="calc-date-input"&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converters get a single input field with a reference table. Multi-input calculators (BMI, EMI) get dynamic input fields generated from the config. Percentage calculators get tabbed modes. Date tools get date pickers.&lt;/p&gt;

&lt;p&gt;One file. Every layout variation handled by &lt;code&gt;{% if calc.type %}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's good about this pattern
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Adding a calculator takes 30 seconds.&lt;/strong&gt; I open &lt;code&gt;calculator_data.py&lt;/code&gt;, add a dictionary or a &lt;code&gt;_pair()&lt;/code&gt; call, and it's live. No new template, no new route, no new JavaScript file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency is automatic.&lt;/strong&gt; Change the FAQ layout? One edit, 147 pages update. Change the color scheme? Same. Every calculator looks and works identically because they all render from the same template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO with zero extra work.&lt;/strong&gt; Each dictionary has &lt;code&gt;meta_desc&lt;/code&gt;, &lt;code&gt;h1&lt;/code&gt;, and &lt;code&gt;title&lt;/code&gt; fields. 147 pages with unique meta descriptions — all generated from the config. Google sees unique content on every page because the FAQs, formulas, and reference tables are all different.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks
&lt;/h2&gt;

&lt;p&gt;I won't pretend this is perfect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge cases get messy.&lt;/strong&gt; Temperature conversion isn't a simple multiply-by-factor operation (Celsius to Fahrenheit needs &lt;code&gt;(C × 9/5) + 32&lt;/code&gt;). I had to add a &lt;code&gt;temperature&lt;/code&gt; type separate from &lt;code&gt;converter&lt;/code&gt;. Every exception adds another &lt;code&gt;{% elif %}&lt;/code&gt; to the template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The template got long.&lt;/strong&gt; It started at 80 lines. It's now over 200. Those conditional blocks accumulate. At some point you start wondering if maybe you should split into 3-4 sub-templates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging sucks sometimes.&lt;/strong&gt; When a &lt;a href="https://nexuslabs.website/calc/length" rel="noopener noreferrer"&gt;calculator page&lt;/a&gt; renders wrong, you have to check: is it the data config? The template branch? The JavaScript formula? The CSS? Four layers means four places bugs can hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use this pattern?
&lt;/h2&gt;

&lt;p&gt;If you're building 5 pages that share a layout — probably don't bother. Copy-paste is fine for 5 pages.&lt;/p&gt;

&lt;p&gt;If you're building 20+ and they follow predictable variations? Data-driven templating saves you from drowning in maintenance. The rule I follow: when you catch yourself copy-pasting a template for the third time, stop and extract the config.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nexuslabs.website/calc/" rel="noopener noreferrer"&gt;calculators hub&lt;/a&gt; runs 147 pages on this pattern. Adding number 148 would take me about 30 seconds. That's DRY taken to its logical extreme — and honestly, it's one of the better architectural decisions I've stumbled into.&lt;/p&gt;

&lt;p&gt;What's the most DRY thing you've ever built? I'm curious whether anyone else has pushed this kind of template reuse further than they expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopkt9kii1cj4xyxd1x1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopkt9kii1cj4xyxd1x1m.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I built a regex tester that tells you what your pattern actually does</title>
      <dc:creator>Akash Singh</dc:creator>
      <pubDate>Thu, 19 Mar 2026 02:54:26 +0000</pubDate>
      <link>https://forem.com/akashxlabs/i-built-a-regex-tester-that-tells-you-what-your-pattern-actually-does-1efm</link>
      <guid>https://forem.com/akashxlabs/i-built-a-regex-tester-that-tells-you-what-your-pattern-actually-does-1efm</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Regex is powerful and unreadable. I built a tool that takes any regex pattern and breaks it into plain English, piece by piece. Below is how the explanation engine works, plus the three patterns every dev should actually understand.&lt;/p&gt;




&lt;p&gt;I wrote this regex last year:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&amp;amp;*])[A-Za-z0-9!@#$%^&amp;amp;*]{8,}$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four months later I came back to that codebase. Read the pattern. Had absolutely no memory of what it does or why I wrote it that way.&lt;/p&gt;

&lt;p&gt;And I wrote it.&lt;/p&gt;

&lt;p&gt;This is the universal regex experience. You understand it for about 20 minutes after writing it, then it becomes ancient hieroglyphics permanently.&lt;/p&gt;

&lt;p&gt;So I built a regex tester that doesn't just tell you "match" or "no match" — it explains every piece of the pattern in actual human words. You paste a regex, it tells you what each part does.&lt;/p&gt;

&lt;p&gt;Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the explanation engine works
&lt;/h2&gt;

&lt;p&gt;Collectively, the regex syntax boils down to maybe 15-20 token types. Anchors, character classes, quantifiers, groups, lookaheads. The engine walks the pattern character by character and classifies each segment.&lt;/p&gt;

&lt;p&gt;The core idea is a tokenizer that knows regex grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anchor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Start of string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anchor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;End of string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;charClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;charClass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;charClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;describeCharClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charClass&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findGroupEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lookahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lookahead&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lookahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Must contain: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;describeLookahead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lookahead&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... quantifiers, groups, escaped chars, etc.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;describeCharClass&lt;/code&gt; function is where it gets interesting. &lt;code&gt;[A-Z]&lt;/code&gt; becomes "any uppercase letter." &lt;code&gt;[0-9]&lt;/code&gt; becomes "any digit." &lt;code&gt;[A-Za-z0-9]&lt;/code&gt; becomes "any letter or digit." It knows common ranges and translates them.&lt;/p&gt;

&lt;p&gt;The quantifiers are simpler: &lt;code&gt;{8,}&lt;/code&gt; becomes "8 or more times," &lt;code&gt;*&lt;/code&gt; becomes "zero or more," &lt;code&gt;+&lt;/code&gt; becomes "one or more."&lt;/p&gt;

&lt;p&gt;Chain the descriptions together and you get human-readable output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's break down 3 common patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Email (simplified)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The explanation engine outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; → Start of string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a-zA-Z0-9._%+-]+&lt;/code&gt; → One or more: letter, digit, dot, underscore, percent, plus, or hyphen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@&lt;/code&gt; → Literal "@" character&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a-zA-Z0-9.-]+&lt;/code&gt; → One or more: letter, digit, dot, or hyphen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\.&lt;/code&gt; → Literal dot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a-zA-Z]{2,}&lt;/code&gt; → Two or more letters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$&lt;/code&gt; → End of string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; "A string that starts with alphanumeric/special chars, followed by @, followed by a domain name, followed by a dot and at least two letters."&lt;/p&gt;

&lt;p&gt;This won't catch every valid email — the actual email RFC is a nightmare that would need a regex the size of a paragraph. But for form validation? This handles 99% of real-world inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Phone number (US)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation engine output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; → Start of string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\(?&lt;/code&gt; → Optional opening parenthesis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\d{3}&lt;/code&gt; → Exactly 3 digits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\)?&lt;/code&gt; → Optional closing parenthesis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[-.\s]?&lt;/code&gt; → Optional separator (dash, dot, or space)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\d{3}&lt;/code&gt; → Exactly 3 digits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[-.\s]?&lt;/code&gt; → Optional separator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\d{4}&lt;/code&gt; → Exactly 4 digits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$&lt;/code&gt; → End of string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Matches: &lt;code&gt;(555) 123-4567&lt;/code&gt;, &lt;code&gt;555.123.4567&lt;/code&gt;, &lt;code&gt;555-123-4567&lt;/code&gt;, &lt;code&gt;5551234567&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The key insight: &lt;code&gt;?&lt;/code&gt; after each separator and parenthesis makes them optional. That one character handles four different phone formats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: URL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https?:\/\/[\w.-]+\.[a-zA-Z]{2,}(\/[\w./-]*)*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation engine output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https?&lt;/code&gt; → "http" followed by optional "s"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:\/\/&lt;/code&gt; → Literal "://"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[\w.-]+&lt;/code&gt; → One or more: word character, dot, or hyphen (the domain)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\.&lt;/code&gt; → Literal dot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a-zA-Z]{2,}&lt;/code&gt; → Two or more letters (TLD)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(\/[\w./-]*)*&lt;/code&gt; → Zero or more path segments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;s?&lt;/code&gt; at the start is doing the heavy lifting — it matches both &lt;code&gt;http://&lt;/code&gt; and &lt;code&gt;https://&lt;/code&gt; without needing to write the whole thing twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me: cron patterns
&lt;/h2&gt;

&lt;p&gt;While building the regex explainer, I realized the same concept works for cron expressions. &lt;code&gt;0 9 * * 1-5&lt;/code&gt; is equally unreadable for most people.&lt;/p&gt;

&lt;p&gt;So I built a cron expression builder that works the same way — paste a cron string, get a human explanation back. "At 9:00 AM, Monday through Friday."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nexuslabs.website/tools/cron-builder" rel="noopener noreferrer"&gt;nexuslabs.website/tools/cron-builder&lt;/a&gt; — same idea, different syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why regex explanations matter more than regex skills
&lt;/h2&gt;

&lt;p&gt;I used to think the goal was to get good enough at regex that I could read patterns fluently. After years of writing them, my honest conclusion: nobody reads regex fluently. People who claim they do are either lying or writing only basic patterns.&lt;/p&gt;

&lt;p&gt;The practical skill isn't writing complex regex from memory. It's being able to break down any pattern you encounter — in legacy code, in Stack Overflow answers, in config files — and understand what it's trying to match.&lt;/p&gt;

&lt;p&gt;That's the gap the tool fills. Not "learn regex" but "understand THIS regex right now."&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://nexuslabs.website/tools/regex-tester" rel="noopener noreferrer"&gt;nexuslabs.website/tools/regex-tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste any pattern. It breaks it down piece by piece. Runs in your browser — no server, no signup.&lt;/p&gt;




&lt;p&gt;What regex pattern took you the longest to debug? I once spent 45 minutes on a pattern that didn't work because of a single backslash. The kind of debugging where the fix is one character but finding it takes your entire afternoon.&lt;/p&gt;

</description>
      <category>regex</category>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I cancelled $25/month in dev tool subscriptions. Here's what I use for free now.</title>
      <dc:creator>Akash Singh</dc:creator>
      <pubDate>Sat, 14 Mar 2026 13:53:05 +0000</pubDate>
      <link>https://forem.com/akashxlabs/i-cancelled-25month-in-dev-tool-subscriptions-heres-what-i-use-for-free-now-2783</link>
      <guid>https://forem.com/akashxlabs/i-cancelled-25month-in-dev-tool-subscriptions-heres-what-i-use-for-free-now-2783</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I was paying for a JSON formatter ($8/mo), JWT decoder ($12/mo), and an API dashboard I forgot to cancel ($5/mo). Built browser-based replacements in a weekend. Zero cost. Data never leaves your machine. Links below.&lt;/p&gt;

&lt;p&gt;$300 a year.&lt;/p&gt;

&lt;p&gt;That's what I was spending on three dev tools that basically rearrange text.&lt;/p&gt;

&lt;p&gt;I sat down one weekend and actually looked at what the $8/month JSON formatter does when I click "Format." It runs &lt;code&gt;JSON.parse()&lt;/code&gt; and then &lt;code&gt;JSON.stringify()&lt;/code&gt; with indentation.&lt;/p&gt;

&lt;p&gt;That's the whole product. Three lines of JavaScript behind a paywall.&lt;/p&gt;

&lt;p&gt;So I cancelled everything and built my own. Let me walk you through what I actually use now.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 JSON Formatter — the one I use daily
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nexuslabs.website/tools/json-formatter" rel="noopener noreferrer"&gt;nexuslabs.website/tools/json-formatter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before: API response comes back as one massive minified line. Copy it, go to paid formatter site, wait for it to load, dismiss the newsletter popup, paste, format, copy the output.&lt;/p&gt;

&lt;p&gt;Now: Paste, format, done. Browser-only. No server involved.&lt;/p&gt;

&lt;p&gt;Here's the core logic. Seriously, this is it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines doing the actual work. The rest is syntax highlighting and a copy button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters beyond saving $8:&lt;/strong&gt; Your JSON never hits a server. No "we may use anonymized inputs to improve our service." Everything stays in your browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔑 JWT Decoder — because pasting tokens into random sites is sketchy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nexuslabs.website/tools/jwt-decoder" rel="noopener noreferrer"&gt;nexuslabs.website/tools/jwt-decoder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think about this for a second. Auth tokens contain user IDs, roles, permissions, expiration data. Actual sensitive production data.&lt;/p&gt;

&lt;p&gt;And the most common workflow is... paste that into a third-party website?&lt;/p&gt;

&lt;p&gt;My decoder splits header/payload/signature, shows expiration as a human-readable date, and flags expired tokens. In the browser. No network call. Under 5 seconds from paste to answer.&lt;/p&gt;

&lt;p&gt;I know jwt.io exists and it's fine. But it loads slow and the UI has a lot going on when all I need is: "is this token expired, and what's in the payload?"&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 CSV to JSON — the one I didn't expect to need
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nexuslabs.website/tools/csv-json" rel="noopener noreferrer"&gt;nexuslabs.website/tools/csv-json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Client sends a spreadsheet. Backend expects JSON. Doing this by hand for 200 rows at 4pm on a Friday is the kind of task that slowly erodes your will to keep programming.&lt;/p&gt;

&lt;p&gt;This converter handles headers-as-keys mapping, quoted fields, escaped commas — the weird edge cases that break naive parsers. Used it three times last week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual cost breakdown
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JSON formatter&lt;/td&gt;
&lt;td&gt;$8/mo&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JWT decoder&lt;/td&gt;
&lt;td&gt;$12/mo&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API dashboard&lt;/td&gt;
&lt;td&gt;$5/mo&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total/year&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$300&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to build&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1 weekend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where I draw the line
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend free replaces everything. Paid tools earn their price when they provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real infrastructure&lt;/strong&gt; — monitoring, CI/CD, hosted databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team collaboration&lt;/strong&gt; — actual multiplayer features, not "collaboration" as a checkbox&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex stateful workflows&lt;/strong&gt; — Postman with environments and variables for teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My rule: if the tool transforms data locally with zero server logic, you probably don't need to pay for it. If it manages infrastructure or requires persistent storage, the subscription might be justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick sanity check for your own subscriptions
&lt;/h2&gt;

&lt;p&gt;Look at your credit card statement. Find the dev tool charges. For each one, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does this tool need a server, or is it computing everything in my browser?&lt;/li&gt;
&lt;li&gt;Am I sending sensitive data to a third party for no reason?&lt;/li&gt;
&lt;li&gt;Could I build a basic version of this in an afternoon?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answers are "no server needed," "yes," and "yes" — you might be overpaying.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What dev tool subscription are you paying for that you suspect is overpriced?&lt;/strong&gt; Or the opposite — what paid tool is genuinely worth every cent?&lt;/p&gt;

&lt;p&gt;I've been thinking about this a lot since cancelling mine, and I'm curious what other people's lists look like.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built 500+ free tools in 10 days — here's what actually happened</title>
      <dc:creator>Akash Singh</dc:creator>
      <pubDate>Tue, 10 Mar 2026 06:02:23 +0000</pubDate>
      <link>https://forem.com/akashxlabs/i-built-500-free-tools-in-10-days-heres-what-actually-happened-1a7l</link>
      <guid>https://forem.com/akashxlabs/i-built-500-free-tools-in-10-days-heres-what-actually-happened-1a7l</guid>
      <description>&lt;p&gt;It started with a JSON formatter.&lt;/p&gt;

&lt;p&gt;I was sitting at my desk at 11 PM, pasting some messy API response into one of those "free online JSON tools" — and the page had 14 ads, two newsletter popups, and a cookie banner bigger than the actual text area. The JSON formatted fine. But I felt gross using it.&lt;/p&gt;

&lt;p&gt;So I closed the tab, opened VS Code, and started writing a JSON formatter. No plan. No Notion doc. Just annoyance and caffeine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How 1 tool became 500
&lt;/h2&gt;

&lt;p&gt;I finished the formatter in maybe 2 hours. And then I thought — okay, I already have this FastAPI route and this Jinja2 template... what if I just add a JSON validator too? Same skeleton, different logic.&lt;/p&gt;

&lt;p&gt;That's how it starts. You build one thing and suddenly you're going "well I need a YAML converter anyway for work" and "Base64 encoder would take like 20 minutes" and before you know it you've got 50 of these things.&lt;/p&gt;

&lt;p&gt;I hit 100 and genuinely lost count for a few days.&lt;/p&gt;

&lt;p&gt;The setup is dead simple. Python route, Jinja2 template, vanilla JS doing the actual work in the browser. Your data never hits my server — not because I had some grand privacy philosophy, but because processing stuff client-side meant I didn't have to worry about server load or storage. Lazy architecture choices that accidentally became a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that sucked
&lt;/h2&gt;

&lt;p&gt;Okay so Google basically pretends my site doesn't exist. 500+ pages, fresh domain, zero backlinks — I submitted my sitemap and... nothing. Like 7 pages indexed out of 500+. That's not a rounding error, that's Google telling you to go away.&lt;/p&gt;

&lt;p&gt;Also some of my tools are rough. I'm not gonna pretend otherwise. The percentage calculator works but it looks like something from 2009. I went wide instead of deep and some pages show it.&lt;/p&gt;

&lt;p&gt;The thing nobody tells you about building 500 pages? The META DESCRIPTIONS. And OG tags. And making sure nothing breaks on mobile. I spent more time on that nonsense than actually writing code. Building tools = fun. SEO housekeeping = slow death.&lt;/p&gt;

&lt;p&gt;Oh and I'm on Render's free tier so the first person who visits after the site goes idle gets a 15-second cold start. They probably think the site is broken. It's not. It's just cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stuff I did NOT see coming
&lt;/h2&gt;

&lt;p&gt;The kids math games page? Gets more traffic than my JSON formatter. I did not expect that at all. Apparently parents searching "free multiplication practice for kids" is a way bigger audience than devs looking for a regex tester. Who knew.&lt;/p&gt;

&lt;p&gt;Also — I have &lt;a href="https://nexuslabs.website/tools/essentials" rel="noopener noreferrer"&gt;147 calculators&lt;/a&gt;. From ONE template. BMI, loan payments, tip calculator, compound interest, all of them. Same engine under the hood, just different config. The DRY principle taken to its logical extreme.&lt;/p&gt;

&lt;p&gt;And the hash generators and encoding tools that I threw in because I needed to fill out the &lt;a href="https://nexuslabs.website/tools" rel="noopener noreferrer"&gt;tools page&lt;/a&gt;? Those are getting actual usage. The filler content became the main content. I still don't fully understand why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (for the curious)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python + FastAPI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates:&lt;/strong&gt; Jinja2 (server-rendered HTML in 2026, yeah I know)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vanilla JS, bit of Alpine.js where I needed reactivity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Render free tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; SQLite for the couple things that need state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; $0. Literally zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No React. No Next.js. No Vercel. A Python server spitting out HTML. Old school and it works fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm writing this now
&lt;/h2&gt;

&lt;p&gt;I've been building in silence for a while and honestly? Nobody knows this thing exists. Google doesn't index it. I have no social presence. The tools work but they're sitting in a void.&lt;/p&gt;

&lt;p&gt;So I'm trying something different — just writing about what I'm actually doing. What's working, what's broken, what I'm figuring out. No "10x your productivity" stuff. Just... here's what happened.&lt;/p&gt;

&lt;p&gt;The whole thing is at &lt;a href="https://nexuslabs.website/" rel="noopener noreferrer"&gt;nexuslabs.website&lt;/a&gt; if you want to poke around. Fair warning, some tools are polished and some look like a hackathon project at 4am. I'm working on it.&lt;/p&gt;

&lt;p&gt;But real question — has anyone else done a sprint like this? Built a bunch of stuff in a short window and then had to figure out what to do with it all? What's the craziest thing you've built in under 2 weeks?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>buildinpublic</category>
      <category>sideprojects</category>
      <category>python</category>
    </item>
  </channel>
</rss>
