<?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: Slee Woo</title>
    <description>The latest articles on Forem by Slee Woo (@sleewoo).</description>
    <link>https://forem.com/sleewoo</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%2F90673%2F6b66f289-4920-466b-abab-f36af2372191.png</url>
      <title>Forem: Slee Woo</title>
      <link>https://forem.com/sleewoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sleewoo"/>
    <language>en</language>
    <item>
      <title>In the AI Agents Era, Why Waste Time Building a Framework?</title>
      <dc:creator>Slee Woo</dc:creator>
      <pubDate>Wed, 11 Mar 2026 14:44:40 +0000</pubDate>
      <link>https://forem.com/sleewoo/in-the-ai-agents-era-why-waste-time-building-a-framework-oni</link>
      <guid>https://forem.com/sleewoo/in-the-ai-agents-era-why-waste-time-building-a-framework-oni</guid>
      <description>&lt;p&gt;There is no answer to that question. Because the question is about the result - a pragmatically calculated, measurable, expected result.&lt;/p&gt;

&lt;p&gt;But building is about the process.&lt;/p&gt;

&lt;p&gt;It's about that zen. That quiet delight when you finally solve something that's been annoying you for years. Maybe it annoys only you. Maybe you're solving a problem that doesn't exist for anyone else. But now it's solved, and you get your couple minutes of glory.&lt;/p&gt;

&lt;p&gt;Now you can ditch all those validation libs - each with its own syntax, its own limitations - and just write TypeScript that gets validated at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineRoute&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRefine&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRefine&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// validated before reaching here - no lib syntax, just TypeScript&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;And the breeze seems here to stay. But no - another annoyance starts to rise from the depths.&lt;/p&gt;

&lt;p&gt;After your 100th route, looking at a file-based routing tree is like a nightmare.&lt;br&gt;
What are all these files? They belong to what? Which is the master (handler), which is the servant (helper)?&lt;/p&gt;

&lt;p&gt;And the answer comes in the form of a question: why not organize routes as directories, with a handler file inside? Wait, but that's a lot of folders!&lt;/p&gt;

&lt;p&gt;Ok, let's sketch how it would look compared to the actual mess...&lt;/p&gt;

&lt;p&gt;Ten minutes later: &lt;em&gt;holy structure&lt;/em&gt;. What the order!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api/
  users/
    [id]/
      index.ts      ← handler for /api/users/:id
      helpers.ts    ← clearly not a route
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of weekends of spare time later: another annoyance is behind. Another minute of glory.&lt;/p&gt;

&lt;p&gt;So, is the breeze here forever? For a couple of hours perhaps, but...&lt;/p&gt;

&lt;p&gt;Why the heck are routes limited to monolithic segments like &lt;code&gt;posts/:id&lt;/code&gt;?&lt;br&gt;
How do you cover simple paths like &lt;code&gt;posts.json&lt;/code&gt; or &lt;code&gt;posts/1.json&lt;/code&gt; in a single route?&lt;br&gt;
Should you create a separate route for each? That's crazy!&lt;/p&gt;

&lt;p&gt;Time for a new zen. Turns out &lt;code&gt;path-to-regexp&lt;/code&gt; v8 is finally state-of-the-art in routing - so flexible, so delightful to integrate. And here it is, the new Power Syntax for routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;book{-:id}-info           ➜ /book-info or /book-123-info
locale{-:lang{-:country}} ➜ /locale, /locale-en, /locale-en-US
api/{v:version}/users     ➜ /api/users or /api/v2/users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far so good.&lt;/p&gt;

&lt;p&gt;Now, finally, a comfortable weekend! Or not?&lt;br&gt;
Wait - forgot to wire auth middleware into the latest routes.&lt;br&gt;
A couple of Neovim strokes and we're good.&lt;/p&gt;

&lt;p&gt;A couple of Neovim strokes later: wait, how could I forget about auth middleware, it's essential!&lt;br&gt;
And how is it that in the 21st century you still have to manually wire middleware into each route?&lt;br&gt;
That's nonsense, that's anti-progress!&lt;/p&gt;

&lt;p&gt;One wasted weekend later: who said stylesheets can be cascading but middleware cannot?&lt;br&gt;
It's that easy - create a &lt;code&gt;use.ts&lt;/code&gt; in any folder and all underlying routes automatically wire the exported middleware. No imports. No repetition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api/
  admin/
    use.ts          ← auth runs for every route under /admin
    users/
      index.ts      ← inherits automatically
      [id]/
        index.ts    ← inherits automatically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more wasted weekends because someone forgot to wire something.&lt;br&gt;
Now they'll be wasted for far more reasonable reasons.&lt;/p&gt;

&lt;p&gt;And one more annoyance has been harming the breeze for years.&lt;br&gt;
Whatever tricks or hacks tried to get a clean, type-safe, validated round-trip from client to server - none fully satisfies.&lt;/p&gt;

&lt;p&gt;Then a perfectly legal question hits: once there are typed validation targets on the server, why not use them to generate typed clients? Wait, even better - TypeBox runs perfectly in the browser, so why not use the same validation routines on the client?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetchClients&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_/front/fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// fully typed, validated client-side before the request is even sent&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetchClients&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users/[id]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of weekends later: so productive now with type-safe, client-side validated fetch clients!&lt;/p&gt;

&lt;p&gt;Time for some rest, finally. Wait - a customer asks for an OpenAPI spec for their API.&lt;br&gt;
Ok, let's quickly wrap a script that gets all routes and generates the spec...&lt;/p&gt;

&lt;p&gt;Multiple "quickly wrap a script" iterations later: how can such a simple task need so much manual work?&lt;br&gt;
No way, there has to be an automated solution. And another zen is on the road - taking the AST-parsed routes with their params, payloads, and responses, and gluing them together into an automated OpenAPI 3.1 spec generator.&lt;/p&gt;

&lt;p&gt;And that zen doesn't come alone. It's accompanied by the sincere &lt;em&gt;wow&lt;/em&gt; of customers who discover they got detailed OpenAPI for free.&lt;/p&gt;

&lt;p&gt;And that's pretty much a lot - much more than any pragmatically calculated result expected from an apparently pointless effort.&lt;/p&gt;

&lt;p&gt;Because the real result is the sum of all those micro-achievements that push you forward.&lt;/p&gt;

&lt;p&gt;And no - the breeze isn't supposed to arrive once and stay forever.&lt;br&gt;
It lives in the movement, not the stillness.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
