Eric Crooks - Software Engineer - Raleigh, North Carolina
Eric Crooks
Software engineer, Nasdaq trader, and US Army Veteran. Based in Apex, North Carolina.
Back to all journal entries

Drash Is Back—Without the Framework Tax

Version 3.x released. Built for people. Programmable by agents. Native to the web.

The same digestible documentation and practical examples—now updated to move at the speed of the web and AI. Here's what it is and why I picked it back up.

The new Drash homepage Drash - Built for people. Programmable by agents. Native to the web.

Start building: https://drash.crookse.com

The Framework Tax#

Frameworks are worth using, but you should choose them knowing you're also choosing their maintenance burden. As both a framework user and builder, I believe a framework's goal should always be to help solve real problems. Good ones earn their place within their communities and bad ones fade away. Either way, they come with taxes. Often times those taxes are hidden behind npm install, transitive dependencies, and time constraints to ship the next feature.

No matter what framework you use, you're paying a tax. You pay for it through:

  • Dependencies you never audited.
  • Extra packages you add just to make the framework behave the way you need.
  • Framework-specific APIs that don't transfer well to your next project or job.
  • Breaking changes and recurring upgrades that pull your team away from building.

The tax I care about most is the transfer tax. When you learn a framework's abstractions, you learn that framework and it becomes your niche. For example, you work with Flutter for 5 years and everyone thinks you're just a Flutter dev even though that's not true. When you learn the web's abstractions, you learn the web. Those are not the same investment, and only one of them keeps paying dividends: the web.

The transfer tax is one of the main reasons I started Drash. You build with it while learning web fundamentals that transfer across every stack.

What Drash Does Instead#

Web APIs#

Drash is a strongly typed, runtime-agnostic web framework built on Web Standards, with zero dependencies. A Request goes in. A Response comes out. That's the whole contract.

class Home extends Resource {
  paths = ["/"];
 
  GET(request) {
    return new Response("Oh so easy");
  }
}
 
const app = Application
  .builder()
  .resource(Home)
  .build();
 
Bun.serve({
  fetch(request) {
    return app.handle(request);
  },
});

There are no runtime-specific request wrappers, middleware arguments, or framework-only special cases to learn. Drash uses standard Request and Response Web APIs—the same primitives behind browser fetch.

For example, like the MDN and RFC, a resource in Drash is the target of an HTTP request. Resources in Drash are represented as classes with pathnames and HTTP request methods:

class MyResource extends Resource {
  paths = ["/teas"];    // These are the pathnames (we use `paths` for short)
 
  GET(request) {...}    // Handles GET /teas
  POST(request) {...}   // Handles POST /teas
  PUT(request) {...}    // Handles PUT /teas
  DELETE(request) {...} // Handles DELETE /teas
 
  // ... and other HTTP request methods
}

The syntax reflects how the web works: a request targets a resource, and a response comes back—no magic involved.

Programmable by Agents#

Here's what makes this version better than the last one:

It's programmable by agents.

I didn't put that on the landing page because "AI" is a good marketing keyword. It's there because a design decision I made for portability had an unexpected payoff years later: it makes Drash easier for coding agents to work with.

Agents are only as good as the patterns and context they know. Give one a framework-specific DSL and it may invent APIs, object shapes, or configs that do not exist. Give it a handler that takes a Request and returns a Response, and it has a contract it has seen millions of times.

For example, I just asked Perplexity to write me one. Here's what it gave me:

export function handler(request: Request): Response {
  const url = new URL(request.url);
 
  if (request.method === "GET" && url.pathname === "/hello") {
    return Response.json({ message: "Hello, world!" });
  }
 
  return new Response("Not found", { status: 404 });
}

And Claude gave me:

async function handler(request: Request): Promise<Response> {
  const url = new URL(request.url);
  const id = url.searchParams.get("id");             // ?id=123
  const auth = request.headers.get("authorization");
  const body = await request.json();                 // also .text(), .formData(), .blob()
 
  return Response.json({ id, received: body }, { status: 201 });
}

Notice anything between these two code blocks? The code is native to the web.

Everything in Drash is written to be read by machines as much as people. Point an agent at drash.crookse.com and you get working code—not a hallucinated API.

What's On The Site#

  • Docs — guides, from install to something running.
  • Examples — working code, per runtime, that you can copy without editing.
  • Reference — the full API surface.

It's made using Nextra, so if you're familiar with that (or Next.js docs), navigating the pages should be familiar.

Drash was quiet for a while. It's not anymore.

If you've ever opened a framework's dependency tree and thought, "I have no idea what the hell is going on," Drash is for you. It sets you to develop with confidence.

https://drash.crookse.com

open sourcedrashweb standardsjavascript

Looking to connect?