DEV Community

Slee Woo
Slee Woo

Posted on

In the AI Agents Era, Why Waste Time Building a Framework?

There is no answer to that question. Because the question is about the result - a pragmatically calculated, measurable, expected result.

But building is about the process.

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.

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:

export default defineRoute(({ POST }) => [
  POST<{
    json: {
      email: TRefine<string, { format: "email" }>;
      age: TRefine<number, { minimum: 18 }>;
    }
  }>(async (ctx) => {
    const { email, age } = ctx.validated.json;
    // validated before reaching here - no lib syntax, just TypeScript
  }),
]);
Enter fullscreen mode Exit fullscreen mode

And the breeze seems here to stay. But no - another annoyance starts to rise from the depths.

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

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!

Ok, let's sketch how it would look compared to the actual mess...

Ten minutes later: holy structure. What the order!

api/
  users/
    [id]/
      index.ts      ← handler for /api/users/:id
      helpers.ts    ← clearly not a route
Enter fullscreen mode Exit fullscreen mode

A couple of weekends of spare time later: another annoyance is behind. Another minute of glory.

So, is the breeze here forever? For a couple of hours perhaps, but...

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

Time for a new zen. Turns out path-to-regexp 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:

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
Enter fullscreen mode Exit fullscreen mode

So far so good.

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

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

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

api/
  admin/
    use.ts          ← auth runs for every route under /admin
    users/
      index.ts      ← inherits automatically
      [id]/
        index.ts    ← inherits automatically
Enter fullscreen mode Exit fullscreen mode

No more wasted weekends because someone forgot to wire something.
Now they'll be wasted for far more reasonable reasons.

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

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?

import fetchClients from "_/front/fetch";

// fully typed, validated client-side before the request is even sent
const user = await fetchClients["users/[id]"].GET([123]);
Enter fullscreen mode Exit fullscreen mode

A couple of weekends later: so productive now with type-safe, client-side validated fetch clients!

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

Multiple "quickly wrap a script" iterations later: how can such a simple task need so much manual work?
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.

And that zen doesn't come alone. It's accompanied by the sincere wow of customers who discover they got detailed OpenAPI for free.

And that's pretty much a lot - much more than any pragmatically calculated result expected from an apparently pointless effort.

Because the real result is the sum of all those micro-achievements that push you forward.

And no - the breeze isn't supposed to arrive once and stay forever.
It lives in the movement, not the stillness.

Top comments (0)