Struggling to trace side effects in Next.js API Routes?
Tired of global middleware, nested folders, and 300ms cold starts?
Tirne is here to change that. It's not just a framework. It's a declarative, fetch-native architectural DSL for building edge-first APIs. Tirne doesn't just run your code. It structures it.
✨ Core philosophy
Declarative routes: Define your API like a schema, not scattered handlers
Explicit side effects: Middleware is opt-in, visible, testable
Edge-native speed: Designed for Bun, Workers, and zero-cold-start runtimes
Type-aligned logic: Middleware and handlers work seamlessly with types
🛠️ Quick start
npx create-tirne-app
✔ Choose your target environment: › Bun / Workers
✔ Project folder: … my-tirne-app
cd my-tirne-app
bun install or npm install
npm run dev or wrangler dev

Your API will be available at http://localhost:3000.
Project Structure:
index.ts: Entry point using fetch-compatible interface
package.json: Preconfigured for Bun or Workers
tsconfig.json: Minimal but typed setup
⚡️ Performance benchmarks
bunx autocannon -c 100 -d 10 <http://localhost:3000/>
Cold Start: Tirne (Bun) 0.02 ms vs Next.js API Routes ~300 ms
First Request: Tirne (Bun) 0.79 ms vs Next.js API Routes 20-30 ms
Requests/sec: Tirne (Bun) 90,000+ rps vs Next.js API Routes 8,000-10,000 rps
Avg Latency: Tirne (Bun) <1ms vs Next.js API Routes ~15ms+
Tirne is 10x faster than Next.js API Routes — and that's before tuning.
📀 Hello Tirne (structured example)
import { Server } from "tirne";
const server = new Server([
{ method: "GET", path: "/health", handler: () => new Response("✅ OK") }
]);
export default {
fetch: (req: Request) => server.fetch(req),
};
Compare that to a full folder in /pages/api/health.ts and global middleware.
🔒 Real auth, architected
import { Server, json, setCookie, requireAuth } from "tirne";
import type { Route } from "tirne";
const routes: Route[] = [
{
method: "GET",
path: "/login",
handler: () => {
const headers = new Headers();
headers.append("Set-Cookie", setCookie("auth", "valid-token", {
httpOnly: true,
path: "/",
maxAge: 3600,
}));
return json({ message: "Logged in" }, 200, headers);
},
middleware: [],
},
{
method: "GET",
path: "/private",
handler: () => json({ message: "Secret" }),
middleware: [requireAuth],
},
];
const server = new Server(routes);
export default {
fetch: (req: Request) => server.fetch(req),
};
Auth isn't magical. It's explicit, testable, and architectural.
❗️ Built-in error handling
// index.ts
import type { Route } from "tirne";
import { Server, TirneError } from "tirne";
const routes: Route[] = [
{
method: "GET",
path: "/",
handler: (req) => {
const name = new URL(req.url).searchParams.get("name");
if (!name) {
throw new TirneError("Missing name", {
status: 400,
type: "bad_request",
expose: true,
});
}
return new Response(`Hello, ${name}`);
},
},
];
const server = new Server(routes);
export default {
fetch: (req: Request) => server.fetch(req),
};
TirneError gives you structured error responses
Built-in error middleware maps exceptions to HTTP responses
No try/catch needed — control flow stays clean and testable
Clean APIs include clean error boundaries.
🚀 Build APIs you can actually reason about
If you think API code should be structured, testable, and explicit — not scattered and magical — Tirne was made for you.
[Star on GitHub](https://github.com/Tirne-ts/Tirne) — 10× faster than Next. 100× clearer.
No hidden context. No global traps.
Just architecture you can trace, test, and trust.
Star it if you’re done guessing how your middleware works.
We built Tirne because we were, too.
We don’t need bigger frameworks.
We need smaller, sharper ones.
Less framework. More logic.