{"id":51156070,"url":"https://github.com/rodrigooler/fearless","last_synced_at":"2026-06-26T10:30:51.475Z","repository":{"id":356464357,"uuid":"1188381607","full_name":"rodrigooler/fearless","owner":"rodrigooler","description":"A TypeScript microframework with a hybrid Rust+Bun runtime. You write idiomatic TS; declarative routes execute on a Rust core at multi-million req/s, and function handlers execute on Bun. Both ship in the same deployable unit.","archived":false,"fork":false,"pushed_at":"2026-05-08T06:18:08.000Z","size":178,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-08T08:23:54.583Z","etag":null,"topics":["framework","javascript","microframework","performance","rust","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/rodrigooler/fearless","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodrigooler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-22T01:54:30.000Z","updated_at":"2026-05-08T06:18:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rodrigooler/fearless","commit_stats":null,"previous_names":["rodrigooler/fearless"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rodrigooler/fearless","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigooler%2Ffearless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigooler%2Ffearless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigooler%2Ffearless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigooler%2Ffearless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodrigooler","download_url":"https://codeload.github.com/rodrigooler/fearless/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigooler%2Ffearless/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34813782,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","javascript","microframework","performance","rust","typescript"],"created_at":"2026-06-26T10:30:50.935Z","updated_at":"2026-06-26T10:30:51.468Z","avatar_url":"https://github.com/rodrigooler.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fearless\n\nA TypeScript microframework with a hybrid Rust+Bun runtime. You write idiomatic TS; declarative routes execute on a Rust core at multi-million req/s, function handlers execute on Bun, and `await db.queryOne(sql\\`...\\`)` compiles to native Rust async via the io_uring + tokio bridge. Everything ships in the same deployable unit.\n\n\u003e **About the benchmarks:** plaintext / JSON routes hit ~9.7M req/s pipelined on a Mac OrbStack VM (Rust core). Typed SQL handlers hit ~42k req/s end-to-end against local Postgres — same throughput as hand-written Rust (Docker NAT is the ceiling; bare-metal projection 200k+). Bun-fallback handlers hit Bun-class throughput (~400-800k req/s). Be honest with yourself about which path your endpoints actually take — see [Performance](#performance) for the breakdown.\n\n## Quick start\n\n### Install\n\nFearless is currently distributed from git (the npm package name is `microu`, but it is not yet published to the registry).\n\n```bash\nnpm install git+https://github.com/rodrigooler/fearless.git#main\n```\n\n### Hello, World\n\nCreate `server.ts`:\n\n```ts\nimport { App } from \"fearless\";\n\nconst app = new App({ port: 3000 });\n\napp.get(\"/\", (ctx) =\u003e ctx.json({ ok: true }));\n\napp.get(\"/users/:id\", (ctx) =\u003e {\n  return ctx.json({ id: ctx.params.id, name: \"Alice\" });\n});\n\napp.post(\"/users\", async (ctx) =\u003e {\n  const body = await ctx.body\u003c{ name: string }\u003e();\n  return ctx.status(201).json({ id: \"new\", name: body.name });\n});\n\napp.listen((started) =\u003e {\n  if (started) console.log(\"Listening on http://localhost:3000\");\n});\n```\n\n### Run\n\n```bash\nnpx tsx server.ts\n```\n\nThat's it. No config files, no decorators, no plugins to wire up.\n\n## Why Fearless\n\n- **Hybrid runtime.** Declarative routes run on a Rust core; function handlers run on Bun. Both in the same process group — no operational split.\n- **Typed SQL at native speed.** `await db.queryOne(sql\\`...\\`)` compiles to native Rust async (Postgres prepared statements + io_uring/tokio bridge). Same throughput as hand-written Rust. See [Typed SQL handles](#typed-sql-handles).\n- **Functional handler API.** `(ctx) =\u003e Response` — return a `Response`, no `(req, res, next)` mess.\n- **Hooks for everything else.** `onRequest` for auth, `onResponse` for logging, `onError` for recovery.\n- **Small surface.** ~10 public types. No DI container, no decorator soup, no required validation library.\n- **Optional strict linting.** Ship-included [oxlint config](#linting-optional) catches sloppy patterns early — opt in if you want it, ignore if you don't.\n\n## Routing\n\n```ts\napp.get(\"/health\", (ctx) =\u003e ctx.json({ ok: true }));\napp.post(\"/users\", (ctx) =\u003e ctx.status(201).json({ id: \"new\" }));\napp.put(\"/users/:id\", (ctx) =\u003e ctx.json({ id: ctx.params.id }));\napp.patch(\"/users/:id\", (ctx) =\u003e ctx.json({ id: ctx.params.id }));\napp.delete(\"/users/:id\", (ctx) =\u003e ctx.noContent());\napp.options(\"/users\", (ctx) =\u003e ctx.noContent());\napp.head(\"/users/:id\", (ctx) =\u003e ctx.noContent());\n```\n\nPath params are exposed on `ctx.params`. Query strings are pre-parsed on `ctx.query` (values are `string | string[]`).\n\n```ts\napp.get(\"/search\", (ctx) =\u003e {\n  const q = ctx.query.q;       // \"hello\"\n  const tags = ctx.query.tags; // [\"a\", \"b\"] when repeated\n  return ctx.json({ q, tags });\n});\n```\n\n`HEAD` requests automatically fall back to the matching `GET` handler if no explicit `HEAD` route is registered.\n\n## Handler context (Ctx)\n\n`Ctx` is the single object every handler and hook receives. It bundles request inputs, lazy body parsers, response builders, and a per-request `state` bag.\n\n### Inputs\n\n```ts\napp.get(\"/inspect/:id\", (ctx) =\u003e {\n  ctx.method;   // \"GET\"\n  ctx.path;     // \"/inspect/42\"\n  ctx.params;   // { id: \"42\" }\n  ctx.query;    // parsed query string\n  ctx.headers;  // normalized lowercase header map\n  ctx.ip;       // client IP\n  ctx.state;    // mutable bag shared across hooks + handler\n  return ctx.json({ ok: true });\n});\n```\n\n### Body parsing\n\nAll body parsers are lazy. They consume the stream on first call.\n\n```ts\nawait ctx.body\u003cMyShape\u003e();   // JSON, throws HttpError(400) on invalid\nawait ctx.text();            // raw text\nawait ctx.formData();        // multipart/form-data\nawait ctx.validate(parser);  // parse + validate; throws ValidationError (422)\n```\n\n### Response builders\n\nEvery builder returns a standard web `Response`.\n\n```ts\nctx.json({ ok: true });               // 200 application/json\nctx.json(data, 201);                  // status as second arg\nctx.html(\"\u003ch1\u003ehi\u003c/h1\u003e\");              // 200 text/html\nctx.raw(\"raw body\", { status: 200 }); // anything `new Response()` accepts\nctx.redirect(\"/login\", 302);          // 301 | 302 | 303 | 307 | 308\nctx.notFound(\"user not found\");       // 404\nctx.noContent();                      // 204\n```\n\n### Chainable status / headers\n\nApply once, consumed by the next builder call.\n\n```ts\nreturn ctx\n  .status(202)\n  .header(\"x-trace\", id)\n  .setHeaders({ \"cache-control\": \"no-store\" })\n  .json({ accepted: true });\n```\n\n## Validation\n\n`ctx.validate` runs your validator and throws a `ValidationError` (422) on null/undefined.\n\n```ts\ntype User = { name: string; email: string };\n\nfunction parseUser(data: unknown): User | null {\n  if (!data || typeof data !== \"object\") return null;\n  const c = data as Record\u003cstring, unknown\u003e;\n  if (typeof c.name !== \"string\" || typeof c.email !== \"string\") return null;\n  return { name: c.name, email: c.email };\n}\n\napp.post(\"/users\", async (ctx) =\u003e {\n  const user = await ctx.validate(parseUser);\n  return ctx.status(201).json(user);\n});\n```\n\nUse any validator that returns `T | null`. Hand-written guards work; so does Zod's `.safeParse` (just adapt `success ? data : null`). The framework does not bind to a schema library.\n\n## Errors\n\nThrow `HttpError` from any handler or hook and it becomes a structured response.\n\n```ts\nimport { HttpError } from \"fearless\";\n\napp.get(\"/users/:id\", (ctx) =\u003e {\n  const user = users.get(ctx.params.id);\n  if (!user) throw HttpError.notFound(`user ${ctx.params.id} not found`);\n  return ctx.json(user);\n});\n```\n\nFactories:\n\n| Factory | Status |\n|---|---|\n| `HttpError.badRequest(msg, details?)` | 400 |\n| `HttpError.unauthorized(msg?)` | 401 |\n| `HttpError.forbidden(msg?)` | 403 |\n| `HttpError.notFound(msg?)` | 404 |\n| `HttpError.conflict(msg, details?)` | 409 |\n| `HttpError.internal(msg?, cause?)` | 500 |\n| `ValidationError` (extends `HttpError`) | 422 |\n\n4xx responses include the message you pass. 5xx responses hide the message and surface a generic body — your `cause` stays in the log, not on the wire.\n\n## Middleware (hooks)\n\nThree lifecycle points. Hooks run in registration order.\n\n### `onRequest` — runs before the handler\n\nReturn a `Response` to short-circuit (auth, rate limits). Return `void` to continue.\n\n```ts\napp.onRequest((ctx) =\u003e {\n  ctx.state.startTime = Date.now();\n});\n\napp.onRequest((ctx) =\u003e {\n  if (!ctx.headers.authorization) {\n    return ctx.status(401).json({ error: \"unauthorized\" });\n  }\n});\n```\n\n### `onResponse` — runs after the handler\n\nInspect or replace the response. Return `void` to keep it as-is.\n\n```ts\napp.onResponse((ctx, response) =\u003e {\n  const ms = Date.now() - (ctx.state.startTime as number);\n  console.log(`${ctx.method} ${ctx.path} ${response.status} (${ms}ms)`);\n});\n```\n\n### `onError` — runs when anything throws\n\nReturn a `Response` to recover. Return `void` to fall through to the next error hook (or the default 500).\n\n```ts\napp.onError((ctx, error) =\u003e {\n  if (error instanceof HttpError) return error.toResponse();\n  console.error(error);\n  return ctx.status(500).json({ error: \"Internal\" });\n});\n```\n\n`ctx.state` is shared across all hooks and the handler within a single request. Use it for request id, auth subject, timing, etc.\n\n## Built-in helpers\n\n```ts\nimport { App, cors, securityHeaders } from \"fearless\";\n\nconst app = new App({ port: 3000 });\n\napp.use(cors());\napp.use(securityHeaders());\n```\n\nBoth are intentionally minimal. They configure CORS preflight and a default set of security headers and — when the app shape allows it — get compiled into the Rust manifest so they cost nothing on the hot path.\n\n## Templates and the Rust hot path\n\nDeclarative template routes have no handler function — the response shape is known at registration time:\n\n```ts\napp.text(\"/plaintext\", \"Hello, World!\");\napp.json(\"/json\", { message: \"Hello, World!\" });\napp.html(\"/welcome\", \"\u003ch1\u003eWelcome\u003c/h1\u003e\");\n```\n\nThese routes are served by the Rust core at 8M+ RPS pipelined because there is no JS function to call per request. Mix templates and handlers freely — handler routes automatically run on the Bun side; template routes stay in Rust.\n\nTemplate tokens interpolate request data:\n\n```ts\napp.json(\"/users/:id\", { id: \"{{ params.id }}\" });\n```\n\nUse templates for: health checks, version endpoints, well-known paths, static API responses. Use handlers for: anything with branching, validation, IO, or business logic.\n\n## Typed SQL handles\n\nAsync handlers that talk to Postgres can compile to native Rust — same code path as the hot Rust core, no Bun round-trip. The pattern:\n\n```ts\nimport { App, fearless, sql } from \"fearless\";\n\nconst db = fearless.sql(\"primary\");\n\nconst app = new App({ port: 3000 });\n\napp.get(\"/db\", async (ctx) =\u003e\n  await db.queryOne(\n    sql`SELECT id, randomnumber FROM world WHERE id = ${Math.floor(Math.random() * 10000) + 1}`\n  ).then(row =\u003e\n    row == null ? ctx.notFound() : ctx.json({ id: row.id, randomNumber: row.randomnumber })\n  )\n);\n```\n\nWhat happens at build time:\n\n1. The analyzer accepts the handler if every `await` targets a registered handle (`db.queryOne`, `db.queryMany`, `db.execute`)\n2. The transpiler emits `pub async fn handler_X(ctx, handles) -\u003e Vec\u003cu8\u003e` calling `handles.sql.get(\"primary\").query_one(STMT_KEY, \u0026[\u0026p1]).await` with manual JSON construction (no serde overhead)\n3. The build collects every `sql\\`...\\`` literal into a `phf::phf_map` STATEMENTS table and prepares them at startup against the connection pool\n4. The dispatcher routes via `AotRouteTable + HandlerKind::Async` through the io_uring + tokio bridge — connection parks during the await, eventfd CQE wakes the loop when the response is ready\n\nSet `FEARLESS_SQL_PRIMARY=postgres://user:pw@host:5432/db` at startup. Pool size defaults to 128 (override with `FEARLESS_SQL_PRIMARY_POOL_SIZE`).\n\n### What's supported (Phase 1.2)\n\n- `fearless.sql(\"name\")` → SQL handle\n- Methods: `queryOne(sql\\`...\\`)`, `queryMany(sql\\`...\\`)`, `execute(sql\\`...\\`)`\n- Bind params: `${ctx.params.X}`, `${ctx.query.X}`, `${Math.floor(Math.random() * NUM) + NUM}`\n- Single `await` per handler body\n- Response: `ctx.json({ field: row.col, ... })`\n\n### What's not yet supported (Phase 1.3+)\n\n- `fearless.kv(\"name\")` (Redis/Dragonfly) and `fearless.http(\"name\")` handles\n- Multiple awaits per handler (cache-then-DB pattern)\n- Computed bind expressions beyond `Math.random` (locals, `parseInt`, etc.)\n- Schema validation (auto-derive Row types from `psql --describe`)\n- Transactions (`BEGIN`/`COMMIT`)\n\nIf your handler doesn't fit the supported subset, the analyzer rejects it with a clear reason and the route falls back to Bun (still 30-80k req/s with a Postgres pool — the comparison just isn't to the Rust ceiling anymore).\n\n## Examples\n\n| Example | Shows |\n|---|---|\n| [hello-world](./examples/hello-world/) | Smallest possible app. |\n| [rest-crud](./examples/rest-crud/) | In-memory CRUD with validation, status codes, and `HttpError`. |\n| [with-middleware](./examples/with-middleware/) | Auth, logging, and error handling via hooks. |\n| [microservice](./examples/microservice/) | Health probes, Prometheus metrics, graceful shutdown. |\n\nRun any example:\n\n```bash\nnpx tsx examples/\u003cname\u003e/server.ts\n```\n\n## Runtime selection\n\n```ts\nnew App({ runtime: \"auto\" }); // prefer Rust \u003e Bun \u003e Node (default)\nnew App({ runtime: \"node\" }); // force Node\nnew App({ runtime: \"rust\" }); // force Rust (template-only apps)\n```\n\nFor HTTPS, pass `keyFileName` and `certFileName`. For HTTP/2, set `httpVersion: \"2\"` (requires HTTPS).\n\n```ts\nnew App({\n  port: 443,\n  keyFileName: \"./key.pem\",\n  certFileName: \"./cert.pem\",\n  httpVersion: \"2\",\n});\n```\n\n## Performance\n\nBe honest with yourself about which path your endpoints take. The Rust core is fast; the Bun fallback is also fast (top of the JS-runtime tier); but they are not the same speed. The number that matters for your app is the one for the path your handlers actually run on.\n\n### Template routes (Rust core)\n\n`app.text` / `app.json` / `app.html`. Local rig: Mac M-series + OrbStack VM (10 vCPU), `wrk` over Docker host network.\n\n| Workload | Throughput |\n|---|---|\n| `/plaintext` non-pipelined c=64  | 441k req/s |\n| `/plaintext` pipeline-32 c=256   | 9.79M req/s |\n| `/json` non-pipelined c=64       | 448k req/s |\n| `/json` pipeline-32 c=256        | 9.82M req/s |\n\nFor context, the older TFB Citrine baseline this repo tracked was ~2.78M req/s plaintext and ~1.36M req/s JSON on bare metal. Fearless meets and exceeds that on weaker hardware via OrbStack.\n\n### Typed SQL handlers (Rust async via io_uring + tokio bridge)\n\n`async (ctx) =\u003e await db.queryOne(sql\\`...\\`)`. Same rig + Postgres 16 in Docker.\n\n| Workload | Throughput |\n|---|---|\n| `/db` (single SELECT WHERE id=$1) c=64  | 39k req/s |\n| `/db` c=256                              | 42.5k req/s |\n\nThe OrbStack rig caps at ~42k for /db (Docker NAT + macOS networking, not the framework). pgbench reaches ~14k TPS on the same rig — we exceed that via tokio's task-level concurrency over the pool. **Bare-metal projection: 200k-400k req/s** (eliminates Docker NAT, pinned cores, real NIC queues).\n\n### Handler routes (Bun fallback)\n\nFunction handlers that DON'T fit the AOT subset run on Bun's native HTTP server:\n\n| Handler shape | Approximate throughput |\n|---|---|\n| Trivial (no IO, return literal) | 400k - 800k req/s |\n| With validation + business logic | 100k - 300k req/s |\n| With single DB query (Postgres pool) | 30k - 80k req/s |\n\nThese are limited by Bun + your IO, not by Fearless.\n\nSee [`bench-history.json`](./bench-history.json) for raw run data, [`docs/deployment-tuning.md`](./docs/deployment-tuning.md) for the host-side knobs that unlock 2-5x extra throughput on bare-metal Linux (IRQ affinity, RPS/RFS, sysctl, container caps), and [`docs/deploy-citrine.md`](./docs/deploy-citrine.md) for the bare-metal validation playbook.\n\n## Linting (optional)\n\nFearless ships an [`oxlint`](https://oxc-project.github.io/) config (`.oxlintrc.json`) tuned for type safety and predictable patterns:\n\n```bash\nnpm run lint        # report\nnpm run lint:fix    # autofix what's safe\n```\n\noxlint is Rust-based and runs in single-digit milliseconds across the codebase. The config is intentionally conservative (most rules are warnings, not errors) so it slots into existing projects without breaking the build. Ratchet to errors as your codebase matures.\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm test\nnpm run lint\nnpm run bench:tfb\n```\n\n## License \u0026 Contributing\n\n- [LICENSE](./LICENSE)\n- [CONTRIBUTING.md](./CONTRIBUTING.md)\n- [CONTRIBUTORS.md](./CONTRIBUTORS.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigooler%2Ffearless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodrigooler%2Ffearless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigooler%2Ffearless/lists"}