drash: A Web Standards Web Framework for Node, Deno, Bun and Workers
A strongly typed, runtime-agnostic web framework for building structured HTTP services in JavaScript, built on Web Standards.
At a glance
- What is it?
- Drash is a strongly typed web framework built on Web Standards, where you define resources as classes and hand the framework a context or a Request. The same application code runs on Node, Deno, Bun and Cloudflare Workers, but you supply the server integration and, on Node, the context object yourself.
- Who is it for?
- Drash fits a team that wants Web Standards request and response objects on every runtime, with resources as ordinary classes and a typed API, and it does not fit a team that wants the framework to own the server, the request parsing and the context object, because in Drash those are yours to write.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The Framework Does Not Own the HTTP Server
Drash describes itself as a strongly typed, runtime-agnostic web framework for building structured HTTP services in JavaScript, built on Web Standards. The important qualifier is what it does not do. It does not start a server, it does not parse an incoming request into a context for you, and it does not own the process.
What it does is give you a resource model, a builder for the application, and a handle function that takes a request or a context and returns a response. Everything around that is your code, on the runtime of your choice.
That design is what makes the runtime-agnostic claim hold. The application code is the same everywhere, and only the server around it changes, which the README states explicitly. The installation table makes the same point in practice, with an initialisation command and an install command per runtime:
| Runtime | Initialize | Add Drash | | --- | --- | --- | | Node | `npm init -y` | `npm install @drashland/drash` | | Deno | `deno init` | `deno add jsr:@drashland/drash` | | Bun | `bun init -y` | `bun add @drashland/drash` | | Cloudflare Workers | `npm init -y` | `npm install @drashland/drash` |
For Cloudflare Workers, the README notes you also need Wrangler, the Cloudflare CLI, to run a Worker locally.
Resources Are Classes and the Application Is a Builder
The model is two small pieces. A resource is a class with a paths property and methods named after HTTP verbs. The application is built through a fluent builder, and the resources are registered on it.
Here is the whole thing, as it appears in the Deno example:
import {
Application,
Resource,
} from "jsr:@drashland/drash/modules/http.native";
class Home extends Resource {
paths = ["/"];
GET(request: Request) {
return new Response("Oh so easy");
}
}
const app = Application
.builder()
.resources(Home)
.build();The paths array declares what the resource answers on, and the GET method is the handler. This is close enough to a routing table that reading it is instant, and the fact that a resource is a class means you can hold state on it, subclass it, or register the same class on several path sets.
The builder call is a single expression, and the type parameters follow from the resources you registered, which is where the strongly typed part of the description comes from. The full documentation is hosted separately at drash.crookse.com.
Four Runtimes, Four Server Integrations
The runtime sections of the README show the same application against four different servers, and the differences are informative. On Deno the handler receives a standard Request and returns a standard Response, with the server supplied by Deno.serve:
Deno.serve({
hostname: "localhost",
port: 1447,
handler: (request: Request): Promise<Response> => {
return app
.handle<Response>(request)
.catch(() => {
return new Response("Sorry, but we hit an error!", {
status: 500,
statusText: "Internal Server Error",
});
});
},
});On Bun, the equivalent is Bun.serve with a fetch function, and it takes the same app.handle<Response>(request) call. On Node, the picture changes, and that is the point of the next section: Node's http module gives you IncomingMessage and ServerResponse rather than a Web Request, so the adapter has to build a context.
The error handling pattern is identical across all of them, a catch that turns a rejected handle call into a 500 response with a status text. The example port is 1447 and the hostname is localhost in every variant, which makes the four examples directly comparable.
Two Entry Points: http.polyfill and http.native
The import path is where the runtime differences show up, and there are two of them. The polyfill entry point is imported as @drashland/drash/modules/http.polyfill, and the native one as jsr:@drashland/drash/modules/http.native. The README explains the reason in a comment on the Node example: Node's URLPattern support varies by version, so the polyfill entry point is used there. The Deno example uses the native entry point.
The Deno, Bun and Workers cases work with Web primitives directly, so they need no adaptation layer for request and response handling. The Node case does, and the README is explicit that Drash does not convert IncomingMessage and ServerResponse for you. You hand the app a context object carrying whatever your resources need, and the app only requires url and method.
That is a real difference in ergonomics between runtimes, and it is worth naming in an evaluation. On Deno and Bun a resource handler receives a Request and returns a Response. On Node with the polyfill entry point, the handler receives the context you constructed, and the example resource uses context.response.end. If your resources are written against the Web Request type, the Node path takes more adapter code than the others.
npm and JSR Carry Different Import Rules
Drash is published to both npm and JSR, and the two carry different rules that will bite you on the first import. The JSR specifiers carry no file extension, because JSR resolves through the package's export map, while the npm ones need the .js extension.
In practice that means the Deno example imports from jsr:@drashland/drash/modules/http.native with no extension, the Bun example imports from @drashland/drash/modules/http.polyfill.js with one, and the Node CommonJS example uses require with the extension dropped, because the comment in that block notes that the CommonJS specifier drops it while the ESM one keeps it.
The Node examples also differ in module system. npm init -y produces a CommonJS project, which is what the first example expects, and for ESM the README says to add npm pkg set type=module and import rather than require.
On Deno, the README says both steps are optional, because the example imports the full jsr: specifier, which resolves without a deno.json. Running deno add is for when you want the version pinned there and would rather import the bare @drashland/drash/... specifier.
Where the Framework Stops: The Context Object Is Your Responsibility
The limits of Drash are the same as the limits of its philosophy. It handles routing, resource dispatch and the shape of the request, and it stops there. On Node, that means you construct the context yourself, and the README shows exactly what the minimum looks like:
const context = {
url: `http://${hostname}:${port}${request.url}`,
method: request.method,
request,
response,
};The app only requires url and method, and you can add whatever else your resources need, which is the extension point. The trade-off is that the framework gives you less than you might expect from a server framework, and there is no built-in body parsing, no middleware chain, no cookie handling and no static file serving in what the README documents.
That is not a criticism so much as a scope statement. A framework that stays out of the request pipeline is one you can drop into a runtime you already have, on Workers for example, where the platform gives you a Request and you have to return a Response and nothing else happens in between. What you give up is the convenience of a framework that assembles the request for you, and the migration cost of moving an existing application onto Drash is exactly the cost of rewriting that assembly.
Version 3, the Node Engine Floor and the Test Matrix
The package metadata is precise about what it needs. The version is 3.0.2, the package manager is [email protected], and the engines field requires Node ^20.19.0 or >=22.12.0, which is a real constraint for anyone on an older Node in production.
The engine floor is worth connecting to the URLPattern comment in the Node example. If Node's URLPattern support varies by version, and the package now requires a fairly recent Node, then the polyfill entry point is there for the versions below the supported floor or for the ones where the built-in is incomplete.
The test setup shows how seriously the runtime-agnostic claim is taken. The repository has three vitest configuration files, one for Cloudflare, one for Node and a shared utils configuration, which means the same test suite runs against more than one runtime rather than being verified on one. There is also a tests/ directory, an examples/ directory split into frameworks, platforms and runtimes, a src/ directory, a docs/ directory and a scripts/ directory.
The release history shows the 3.0 line landing in quick succession. v3.0.0 was tagged on 2026-08-25, with v3.0.1 and v3.0.2 both on 2026-08-27, and the last push was on 2026-10-12, so the repository is not archived and the 3.x line is the current one. The package is licensed GPL-3.0-or-later, which is a heavier obligation for an embedded library than the MIT or Apache licences most frameworks use, and the repository carries a COPYING file, a SECURITY.md and an AUTHORS file.
Editorial conclusion
Drash fits a team that wants Web Standards request and response objects on every runtime, with resources as ordinary classes and a typed API, and it does not fit a team that wants the framework to own the server, the request parsing and the context object, because in Drash those are yours to write. Before adopting it, confirm your Node version satisfies the package.json engine floor of ^20.19.0 or >=22.12.0, and decide whether to use the http.polyfill entry point on Node, where URLPattern support varies by version, or the http.native one. The package is published on npm and on JSR as @drashland/drash, it is GPL-3.0-or-later licensed, the newest release is v3.0.2 from 2026-08-27, and the last push was on 2026-10-12.
Frequently asked questions
How do I install Drash on Node, Deno or Bun?
On Node, run `npm init -y` and `npm install @drashland/drash`. On Deno, `deno init` and `deno add jsr:@drashland/drash`. On Bun, `bun init -y` and `bun add @drashland/drash`. Cloudflare Workers uses the npm commands, plus `npm install --save-dev wrangler` to run a Worker locally.
What is the difference between the http.polyfill and http.native entry points?
The polyfill entry point is used where URLPattern support may be incomplete, and the README uses it in the Node examples because Node's URLPattern support varies by version. The native entry point is used in the Deno example, where the platform provides the Web primitives directly.
Do JSR and npm imports differ?
Yes. The JSR specifiers carry no file extension, because JSR resolves through the package's export map, while the npm ones need the .js extension. The CommonJS require path drops the extension and the ESM import path keeps it.
Does Drash start an HTTP server for me?
No. The README says the same application code runs everywhere and only the server around it changes. You call Deno.serve, Bun.serve or Node's createServer yourself, and on Node you build a context object with url and method because Drash does not convert IncomingMessage and ServerResponse for you.
Which Node versions does Drash support?
The package.json engines field requires Node ^20.19.0 or >=22.12.0. The package manager is [email protected], the package version is 3.0.2, and the licence is GPL-3.0-or-later.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/drashland-drash)Was this useful?