AN API FRAMEWORK FOR BUN, DISTRIBUTED AS SOURCE
Hyper is an HTTP framework for Bun. There is no @hyper/core in your package.json. The CLI copies the components you want into src/hyper/ as plain TypeScript — yours to read, edit, fork, or delete. hyper diff and hyper update keep the upgrade path intact.
Declare a route once. Hyper emits the runtime, an OpenAPI 3.1 document, a typed RPC client, and an MCP server from the same source.
> bun create hyper my-app
> bun run devTHE REGISTRY
The framework is the registry. Every part — router, plugins, test helpers, OpenAPI and MCP adapters — is a folder of source files. Install what you need; the rest never enters your repo. The registry stays an installer, not a runtime dependency.
Each install is recorded per-file in hyper.lock.json. Local edits don't block upgrades — hyper update merges only the deltas you accept, the rest of the file stays exactly as you wrote it.
> bun create hyper my-app --template api
> hyper add cors auth-jwt session
+ src/hyper/cors/index.ts
+ src/hyper/auth-jwt/index.ts
+ src/hyper/auth-jwt/jwt.ts
+ src/hyper/session/index.ts
~ .env.local (1 secret added: JWT_SECRET)
run: bun add zod jose
> hyper diff cors
ok src/hyper/cors/index.ts
drift src/hyper/cors/options.ts (3 local edits)ONE DEFINITION, MANY OUTPUTS
A route is one declaration: path, schemas, errors, handler. From it Hyper emits the runtime, an OpenAPI 3.1 document, a typed RPC client, and an MCP server. No parallel schema file, no decorator metadata, no second source of types.
Schemas use any Standard Schema library — Zod, Valibot, Arktype — directly. Errors declared on the route appear as response codes in OpenAPI, a discriminated union in the client, and assertions in the test runner.
import { Hyper, ok } from "@hyper/core"
import { z } from "zod"
export default new Hyper()
.get("/health", "OK")
.post(
"/users",
{
body: z.object({
name: z.string().min(1),
email: z.email(),
}),
throws: { 409: z.object({ taken: z.string() }) },
},
async ({ body }) => {
return ok({ id: crypto.randomUUID(), ...body })
},
)
.listen(3000)> hyper openapi > openapi.json # 3.1 spec
> hyper client ./client.gen.ts # typed RPC client
> hyper mcp # MCP server, no extra configPLUGINS AND SUB-APPS
One composition primitive: .use(). It takes a plugin, a sub-app with its own prefix, an imported route file, or a function that decorates the request. The router flattens at build time — nesting is organisational, free at runtime.
In the registry today:
- cors — CORS, with strict wildcard rejection when credentials are present
- auth-jwt — JWT auth, EdDSA / RS256 / HS256, 32-byte secret floor
- session — encrypted cookie sessions, CSRF double-submit
- log — structured logging, header / body redaction
- rate-limit — sliding window and token bucket, per-route override
- csp — Content-Security-Policy, report-only mode, nonces
- cache — RFC 9111-aware route caching with conditional revalidation
- idempotency — idempotency-key handling for write methods
- otel — OpenTelemetry traces and metrics
import { Hyper } from "@hyper/core"
import { corsPlugin } from "@hyper/cors"
import { logPlugin } from "@hyper/log"
import { authJwtPlugin } from "@hyper/auth-jwt"
import { rateLimitPlugin } from "@hyper/rate-limit"
import users from "./routes/users.ts"
import posts from "./routes/posts.ts"
export default new Hyper()
.use(logPlugin())
.use(corsPlugin({ origin: ["https://example.com"] }))
.use(rateLimitPlugin({ window: "1m", max: 60 }))
.use(authJwtPlugin())
.use(users) // honors its own prefix -> /users/*
.use("/v1", posts) // re-prefixed -> /v1/posts/*
.listen(3000)BUN-NATIVE
Built for Bun directly — HTTP server, cookie map, password and hash APIs, file primitives. A thin layer on a fast runtime, not an abstraction over runtimes you don't ship to.
Handlers pay only for what they read. Static responses bypass the handler path. Security defaults — HSTS, body limits, strict CORS, secret-length floors, CSRF double-submit — are audited by hyper security --check in CI.
> hyper security --check
ok HSTS enabled in production
ok body limit <= 1 MB
ok prototype-pollution guards active
ok CORS rejects wildcard with credentials
ok JWT secret >= 32 bytes
ok session secret >= 32 bytes
ok CSRF double-submit on state-changing methods
... 5 moreONE BINARY
One CLI for the whole loop: scaffold, dev (hot reload + type-check), test (.example() contracts + bun:test), bench, build, and the registry (add, diff, update, list).
Every command emits JSON with --json. Editor integrations, CI, and agents read the same surface you do.
hyper init [template] scaffold + auto-install core
hyper dev [entry] hot reload + tsgo --watch
hyper build [entry] bundle + route graph
hyper test .example() contracts + bun:test
hyper bench [entry] per-route latency p50/p99
hyper openapi [out] OpenAPI 3.1 spec
hyper client <out> typed RPC client
hyper mcp [entry] MCP server (--audit)
hyper routes [entry] print the route graph
hyper security --check audit the secure-by-default posture
# registry
hyper add <name>... install components into your repo
hyper diff <name> show drift vs the registry
hyper update [...names] bump installed components
hyper list [query] browse the catalogMCP AS A PEER OF HTTP
Any route can opt into MCP exposure. The same schema, validation, and errors run whether the call arrives over HTTP or MCP — no second layer of glue.
The registry runs an MCP endpoint too. From an editor that speaks MCP, installing a Hyper component is the same operation as calling a route.
import { Hyper, ok } from "@hyper/core"
import { z } from "zod"
export default new Hyper({ prefix: "/users" })
.post(
"/",
{
body: z.object({ name: z.string(), email: z.email() }),
mcp: {
title: "Create user",
description: "Provision a new tenant user. Sends a welcome email.",
},
},
async ({ body }) => ok({ id: crypto.randomUUID(), ...body }),
){
"mcpServers": {
"my-api": { "url": "http://localhost:3000/mcp" },
"hyper": { "url": "https://hyperjs.ai/mcp" }
}
}