Tirne
Tirne — from Old English “structure, strength” — a minimal web framework for Bun with Go-like control and zero boilerplate.
🚀 Quickstart
Tirne is the fastest way to build typed, structured web APIs and apps using Bun — with Go-like patterns, no magic, and full control.
bun init
bun add tirne// index.ts
import { createRouter,json } from "tirne";
const routes = [
{
method: "GET",
path: "/",
handler: ({ }) => json({ message: "Hello, Tirne!" }),
},
];
const router = createRouter(routes);
Bun.serve({ fetch: router });
console.log("Server is running on http://localhost:3000");bun run index.tsYou’re now running a structured, typed, Bun-native web server. No scaffold, no CLI, no boilerplate.
💡 Works on Bun, Node.js (via adapter), and Cloudflare Workers (via fetch-compatible API).
🔧 Philosophy
Tirne is built on 5 core principles:
- Structure without abstraction — Everything visible, understandable, no magic.
- Functions over frameworks — Middleware, handlers, routers are just plain functions.
- Return errors, don’t catch them — Like Go: errors as values, not exceptions.
- Composition is explicit — No decorators or global state. Just
compose(middleware[]). - Run anywhere — Bun-native by design, but fetch-compatible for Node, Workers, Deno.
✨ Features
- ✅ Minimal HTTP router with dynamic path support
- ✅ Go-style middleware composition with full control
- ✅ First-class context: method, query, params, signal, env
- ✅ Response helpers:
json(),html(),text(),error() - ✅ Optional
Result<T, E>pattern for safe error handling - ✅ Simple structured parallelism:
runParallel(),waitAll() - ✅ Fully fetch-based — runs on Bun, Node, Cloudflare Workers, Deno
- ✅ No CLI, no file structure, no default config — just code
🧱 Core Responsibilities (Go-style)
| # | Responsibility | Description | Go Equivalent |
|---|---|---|---|
| 1 | Router | Static/dynamic route definition, method matching | http.ServeMux |
| 2 | Middleware Composition | compose() to separate and chain concerns |
http.Handler |
| 3 | Context | ctx = { req, res, env, signal, params } |
context.Context |
| 4 | Error Handling | Result<T, E> or handleError() structure |
error, if err != nil |
| 5 | Response Utility | json(), html(), text(), error() |
encoding/json, template |
| 6 | Parallelism | runParallel(), waitAll() async control |
go func(), WaitGroup |
🧱 Example with Middleware
import { createRouter, compose, json, error } from "tirne";
const logger = async (ctx, next) => {
console.log(`[${ctx.method}] ${ctx.url.pathname}`);
return await next();
};
const routes = [
{
method: "GET",
path: "/",
handler: compose([logger], ({ req }) => json({ hello: "Tirne" })),
},
{
method: "GET",
path: "/fail",
handler: () => error("Something went wrong", 500),
},
];
Bun.serve({ fetch: createRouter(routes) });🔥 Tirne Philosophy – The 5 Articles of Bun-Style Backend
A web framework should be your toolbox, not your leash. Tirne follows five unapologetically minimal principles:
No abstractions you can’t inline Don’t hide behind magic. If it can’t be written in 5 lines, it probably shouldn’t exist.
Functions first. Frameworks last Apps should work without frameworks. Tirne is just the helper, never the master.
Handle failure like a grown-up No try/catch gymnastics. Return errors like Go. Predictable. Traceable. Safe.
Parallelism is a first-class citizen Use your CPU like a pro. Tirne assumes your code will scale, so it doesn’t get in the way.
Zero startup, zero lock-in, zero shame Start small, stay small if you want. Tirne doesn’t force structure or ceremony. One file is enough.
🔍 Tirne vs Hono vs Elysia — Key Differences
| Axis | Tirne ✨ | Hono 🌿 | Elysia 🧠 |
|---|---|---|---|
| Philosophy | Structure and control (Go-inspired) | Developer Experience and simplicity (Express-like) | Type-centric & macro-driven (type-first) |
| Routing | Function array (explicit structure) | app.get("/foo") chaining style |
app.get("/foo", {...}) with macros |
| Middleware | compose(fn[]) explicit composition |
app.use() global style |
onBeforeHandle and plugin/macro-driven |
| Type Safety | Lightweight, composable | Medium (some constraints) | Super strong, but complex |
| Response API | json(), error() as return values |
c.json(), c.text() methods |
set.response() — implicit injection |
| Extensibility | Functional middleware composition | Plugin-based | Decorator & macro-based |
| Dependencies | 🟢 Zero (100% custom) | 🟡 Lightweight (just Hono) | 🔴 Many (valibot, macros, swc, etc.) |
| Runtime Support | ✅ Bun / Node / Workers | ✅ Bun / Node / Workers | ❌ Bun-only (not Deno-compatible) |
| Ideal Users | Go developers / Bun engineers | Express graduates / DX lovers | TypeScript-heavy / type maximalists |
Tirne is for those who value explicit control, minimalism, and portability over magic or tooling complexity.
Tirne keeps things predictable, portable, and programmable — not magic-driven.
📦 Install
bun add tirneTo use in Node.js:
npm install tirneTo use in Workers or Deno:
- All core APIs are
fetch()-based — simply adapt routing to your runtime.
🤍 Use Cases
Tirne is ideal for:
- Bun-native HTTP APIs with zero setup
- Drop-in replacement for Express or Hono in Bun projects
- Cloudflare Worker endpoints (with fetch-compatible handlers)
- Building edge-friendly backends with structured code
🌟 Support Tirne
If you find Tirne useful — elegant, minimal, and empowering — consider giving it a ⭐️ on GitHub.
Every star helps us reach more developers who believe in control over complexity, structure over magic. Your support fuels future features, better docs, and faster performance.
Thank you for being part of the minimalist web revolution.
📜 License
MIT