{"id":51192638,"url":"https://github.com/nifrajs/nifra","last_synced_at":"2026-06-27T17:00:46.043Z","repository":{"id":366014952,"uuid":"1274716443","full_name":"nifrajs/nifra","owner":"nifrajs","description":"Full-stack TypeScript framework built for AI agents: typed server and client with no codegen, multi-runtime, multi-framework SSR, and zero-JS islands.","archived":false,"fork":false,"pushed_at":"2026-06-27T16:51:02.000Z","size":2495,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-27T17:00:26.400Z","etag":null,"topics":["ai-agents","bun","deno","framework","full-stack","islands","ssr","type-safe","typescript","web-framework"],"latest_commit_sha":null,"homepage":null,"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/nifrajs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T20:15:54.000Z","updated_at":"2026-06-27T16:49:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nifrajs/nifra","commit_stats":null,"previous_names":["nifrajs/nifra"],"tags_count":195,"template":false,"template_full_name":null,"purl":"pkg:github/nifrajs/nifra","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nifrajs%2Fnifra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nifrajs%2Fnifra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nifrajs%2Fnifra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nifrajs%2Fnifra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nifrajs","download_url":"https://codeload.github.com/nifrajs/nifra/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nifrajs%2Fnifra/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34860895,"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-27T02:00:06.362Z","response_time":126,"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":["ai-agents","bun","deno","framework","full-stack","islands","ssr","type-safe","typescript","web-framework"],"created_at":"2026-06-27T17:00:26.477Z","updated_at":"2026-06-27T17:00:46.037Z","avatar_url":"https://github.com/nifrajs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nifra\n\n**The full-stack TypeScript framework built for AI agents — and for the humans who work alongside them.**\n\nCoding agents drift. They call an endpoint that moved, expect a response shape that changed, or hand-roll `fetch` with ad-hoc types that fall out of sync the moment a route changes. nifra removes that class of bug at the framework level:\n\n| | |\n|---|---|\n| **Typed client** | `client\u003ctypeof app\u003e` infers every path, param, body, and response from your server's TypeScript type. Any mismatch is a compile error. |\n| **`nifra check`** | Runs typecheck + typed-client lint in one command. Add it to CI — it fails the moment the frontend and backend drift. |\n| **AGENTS.md** | Every scaffold ships a conventions file. Agents (Claude Code, Cursor, Copilot) read it and follow nifra's rules from the first prompt. |\n| **`nifra context`** | Prints this project's real API surface — routes + schemas — as Markdown. Paste into any agent prompt, or let `nifra mcp` deliver it automatically. |\n| **`nifra mcp`** | An MCP server that feeds Claude Code, Cursor, and Copilot Chat this project's live route and schema data. |\n\nThe rest is a fast, contract-first full-stack TypeScript stack: routing, validated I/O, SSR, loaders/actions, auth, WebSockets, MDX, and multi-runtime deployment.\n\n```sh\nbun create nifra my-app\n```\n\n## The backend\n\n```ts\nimport { server } from \"@nifrajs/core\"\nimport { t } from \"@nifrajs/schema\"\n\nexport const app = server()\n  .get(\"/users/:id\", (c) =\u003e ({ id: c.params.id }))\n  .post(\"/users\", { body: t.object({ name: t.string() }) }, (c) =\u003e {\n    // c.body is validated + typed — invalid input is rejected before this runs.\n    return { id: crypto.randomUUID(), name: c.body.name }\n  })\n  .listen(3000)\n\nexport type App = typeof app\n```\n\n## The typed client — the anti-drift seam\n\n```ts\n// client.ts — fully typed from the server, zero codegen\nimport { client } from \"@nifrajs/client\"\nimport type { App } from \"./server\"\n\nconst api = client\u003cApp\u003e(\"http://localhost:3000\")\n\nconst res = await api.users({ id: \"42\" }).get()\nif (res.ok) res.data.id   // typed from the route's return — tsc fails if the route changes\nelse res.error            // errors are returned, never thrown\n```\n\nThe client **never throws** — every call returns `{ ok, status, data, error }`, so the happy path and the failure path are both in the types.\n\n## Agent tooling\n\nnifra ships a purpose-built toolchain so coding agents stay correct as the codebase evolves.\n\n**AGENTS.md** — generated per scaffold, teaches the agent nifra's non-obvious rules:\n- validate every input at the boundary with `t` or any Standard Schema\n- always call this app's own API through `client\u003ctypeof app\u003e` — never hand-roll `fetch`\n- never top-level-import server-only code into a route module\n\n**Connect the MCP server** so the agent reads your live routes, verifies endpoints, and gates drift from inside its tool loop. Run once from your project root:\n\n```sh\n# Claude Code\nclaude mcp add nifra -- bunx nifra mcp\n\n# Cursor / Claude Desktop — add to .mcp.json (or claude_desktop_config.json):\n# { \"mcpServers\": { \"nifra\": { \"command\": \"bunx\", \"args\": [\"nifra\", \"mcp\"] } } }\n```\n\nOnce connected, the agent has twelve tools — no setup per prompt:\n\n| Tool | What it does |\n|---|---|\n| `nifra_context` | This project's live routes + schemas + the exact typed-client **call signature** per route (Markdown). |\n| `nifra_routes` | The same routes as **structured JSON** (`{ method, path, call, body?, query?, response? }`) — for programmatic use. |\n| `nifra_openapi` | OpenAPI 3.1 generated from backend route schemas, as JSON or YAML. |\n| `nifra_check` | Typecheck + drift lint, returned as **structured JSON** with safe fix suggestions. |\n| `nifra_doctor` | Flags packages imported in source but missing from `package.json` (resolve at runtime, break `tsc`). |\n| `nifra_run` | Calls a route **in-process** (via `@nifrajs/runner`) — the agent self-verifies an endpoint without booting a server. |\n| `nifra_render` | Server-renders a page to HTML — verify SSR output. |\n| `nifra_ws` | Opens a real Bun WebSocket against the current app, sends test frames, and returns structured evidence. |\n| `nifra_test` | Runs bounded `bun test` and returns structured stdout, stderr, timing, and summary. |\n| `nifra_scaffold` | URL pattern → the correct `routes/` file for the chosen UI framework. |\n| `nifra_docs` / `nifra_example` | Search the docs / fetch a **version-checked** snippet that compiles as-is (no hallucinated APIs). |\n\nNo MCP? The same data is available as plain commands — paste into any prompt, or run in CI:\n\n```sh\nnifra context          # routes + schemas (+ per-route call signatures) as Markdown\nnifra check            # typecheck + typed-client drift lint; --json for agents, --lints-only to skip tsc\nnifra doctor           # packages imported but not declared in package.json (--json for agents)\n```\n\n## Install\n\n```sh\nbun add @nifrajs/core            # the server + router + contracts\nbun add @nifrajs/client          # the typed client (browser-safe)\nbun add @nifrajs/schema          # the `t` schema builder + OpenAPI (optional)\nbun add @nifrajs/middleware      # CORS, security headers, rate limiting (optional)\n```\n\nnifra is **ESM-only** and **Bun-native** (it uses `Bun.serve`). It runs on Bun; the client is environment-agnostic.\n\n## Validate input with `t` (and get OpenAPI for free)\n\n`@nifrajs/schema`'s `t` is a TypeBox-backed builder: it validates at the request boundary *and* — because a TypeBox schema **is** a JSON Schema — generates OpenAPI with no extra work. Bring your own [Standard Schema][standard-schema] (zod, valibot, arktype) too; they validate identically.\n\n```ts\nimport { server } from \"@nifrajs/core\"\nimport { t, toOpenAPI } from \"@nifrajs/schema\"\n\nconst app = server().post(\"/users\", { body: t.object({ name: t.string() }) }, (c) =\u003e ({\n  id: \"u1\",\n  name: c.body.name, // typed as string, validated at runtime\n}))\n\nconst openapi = toOpenAPI(app) // OpenAPI 3.1 document\n```\n\nInvalid bodies are rejected with a structured `400` before your handler runs.\n\n## Graduate to a contract — handlers unchanged\n\nWhen you want a decoupled, versionable API surface, lift the same routes into a contract. Handlers written inline lift over **unchanged**.\n\n```ts\nimport { defineContract, implement } from \"@nifrajs/core\"\nimport { t } from \"@nifrajs/schema\"\n\nconst contract = defineContract({\n  getUser:    { method: \"GET\",  path: \"/users/:id\", response: t.object({ id: t.string(), name: t.string() }) },\n  createUser: { method: \"POST\", path: \"/users\",     body: t.object({ name: t.string() }), response: t.object({ id: t.string(), name: t.string() }) },\n})\n\nconst app = implement(contract, {\n  getUser:    (c) =\u003e ({ id: c.params.id, name: \"ada\" }),\n  createUser: (c) =\u003e ({ id: \"new\", name: c.body.name }),\n})\n```\n\nThe client can now be built from the **contract** alone (`client(contract, url)`) — no dependency on the server's source. This is the shape agents reference: `nifra context` emits the live contract; `nifra check` enforces it.\n\n## Harden it\n\n```ts\nimport { server } from \"@nifrajs/core\"\nimport { cors, securityHeaders, rateLimit, MemoryStore } from \"@nifrajs/middleware\"\n\nconst app = server()\n  .use(securityHeaders())\n  .use(cors({ origin: [\"https://app.example.com\"], credentials: true }))\n  .use(rateLimit({\n    store: new MemoryStore(),\n    max: 100,\n    windowMs: 60_000,\n    key: (req) =\u003e req.headers.get(\"x-user-id\") ?? \"anonymous\",\n  }))\n  .get(\"/\", () =\u003e ({ ok: true }))\n\n// Graceful shutdown, request timeout, body-size cap, redacting logger are built in:\nserver({ requestTimeoutMs: 5_000, gracefulSignals: true })\n```\n\n## Runs on the edge, too\n\nBun is the first-class runtime (`app.listen()`), but the whole lifecycle is `app.fetch(Request): Promise\u003cResponse\u003e` with zero Bun APIs — so the same `app` deploys to **Cloudflare Workers** (`export default app`), **Deno** (`Deno.serve(app.fetch)`), or **Node** (via the [`@nifrajs/node`](packages/node) adapter). See [Deployment](site/routes/docs/deployment.tsx) and [Edge \u0026 bindings](site/routes/docs/edge.tsx).\n\n## Principles (enforced, not aspirational)\n\n- **Reject invalid input at three boundaries** — compile-time (types), boot-time (config throws loudly), request-time (Standard Schema → structured `400`). \"Genuine fallback\" is a documented whitelist; everything else rejects.\n- **Tests everywhere, six kinds** — unit, type-level (`*.test-d.ts`), property/fuzz, mode-conformance, benchmark-regression, security-guardrail.\n- **Speed is a measured goal** — tracked with the `oha` HTTP matrix (`bun run bench:loadtest`) across Bun, Node, and Deno against raw runtime handlers plus representative API framework baselines.\n- **Production-grade by default** — graceful shutdown, redacting logs, idempotent guards, integer-money discipline; nothing is \"we'll fix it later\".\n\n## Packages\n\n| Package | What it is |\n|---|---|\n| [`@nifrajs/core`](packages/core) | Router, fully-inferred server, contracts, lifecycle middleware, hardening |\n| [`@nifrajs/client`](packages/client) | End-to-end-typed, never-throwing client (Eden-style proxy) |\n| [`@nifrajs/schema`](packages/schema) | TypeBox-backed `t` builder + `toOpenAPI` |\n| [`@nifrajs/middleware`](packages/middleware) | CORS, security headers, rate limiting |\n| [`@nifrajs/node`](packages/node) | Run a nifra app on Node's `http` server (opt-in) |\n| [`@nifrajs/cli`](packages/cli) | `nifra check`, `nifra context`, `nifra mcp` — the agent toolchain |\n\n## Examples\n\nRunnable, type-checked apps live in [`examples/`](examples):\n\n```sh\nbun run examples/inline-server.ts\nbun run examples/contract-client.ts\nbun run examples/schema-openapi.ts\nbun run examples/hardened.ts\nbun run examples/edge.ts        # app.fetch as a universal handler\n```\n\n## Develop\n\n```sh\nbun install\nbun run check          # lint + typecheck (incl. type-level tests) + tests w/ coverage\nbun run build          # emit dist/ (js + d.ts) for all packages\nbun run check:publish  # build + publint + arethetypeswrong\nbun run bench:loadtest # oha HTTP matrix across Bun/Node/Deno\n```\n\nMIT licensed.\n\n[standard-schema]: https://standardschema.dev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnifrajs%2Fnifra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnifrajs%2Fnifra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnifrajs%2Fnifra/lists"}