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
🛠️ 1. 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
⚡️ 2. Performance Benchmarks
bunx autocannon -c 100 -d 10 <http://localhost:3000/>
Metric | Tirne (Bun) | Next.js API Routes |
---|---|---|
❄️ Cold Start | 0.02 ms | ~300 ms |
⚡️ First Request | 0.79 ms | 20-30 ms |
♻️ Requests/sec | 90,000+ rps | 8,000-10,000 rps |
📉 Avg Latency | <1ms | ~15ms+ |
🚀 Tirne is 10x faster than Next.js API Routes — and that’s before tuning.
📀 3. 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.
🔒 4. 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.
❗️ 5. 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 — 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.
Curious how others are thinking about API error boundaries in 2025.
Do you still wrap handlers in try/catch manually?
Or are you also shifting toward structured exceptions + middleware?
Would love to hear your approach.