@reono/root
v0.0.1
Published
Reono monorepo root package
Downloads
5
Readme
Reono
Build HTTP APIs with JSX. Define routes, middleware, and validation using familiar JSX syntax, powered by a high‑performance runtime and pluggable server adapters.
Status: Maturing. The core runtime, trie router, validation, and Node.js adapter are stable and well‑tested. Real‑world examples (Scenario‑1 and a Multi‑Tenant SaaS API Gateway in Scenario‑2) are included. Expect iterative improvements as we head toward v2.
Why Reono?
- JSX‑first API design: Define routers, routes, and middleware declaratively
- Type‑safe: First‑class TypeScript with rich inference via schema validation
- Fast: Optimized trie‑based routing with O(1) average case matching
- Standards‑based: Built on Web APIs (Request, Response, Headers)
- Framework‑agnostic core, pluggable server adapters (Node HTTP available)
- Composable middleware with simple, predictable control‑flow
Monorepo at a glance
packages/core— Core JSX runtime, router, utilities, and testspackages/node-server— Node HTTP adapter (@reono/node-server)apps/scenario-1— Minimal example showing routing, middleware, and validationapps/scenario-2— Multi‑Tenant SaaS API Gateway (authZ/authN, tiered rate limiting, dynamic CORS, analytics & billing)apps/api— Additional sample/tests exercising core behaviorpackages/ui,packages/eslint-config,packages/typescript-config— Internal tooling and DX
Quick start
Install
npm install reono @reono/node-server
# or
pnpm add reono @reono/node-server
# or
yarn add reono @reono/node-serverBasic example
import { createApp } from "@reono/node-server";
import { z } from "zod";
// Define your API with JSX
const App = () => (
<router path="api/v1">
<get path="hello" handler={(c) => c.json({ message: "Hello, World!" })} />
<router path="users">
<get path="" handler={(c) => c.json(getAllUsers())} />
<post
path=""
validate={{
body: z.object({ name: z.string(), email: z.string().email() }),
}}
handler={(c) => c.json(createUser(c.body))}
/>
<get
path=":id"
validate={{ params: z.object({ id: z.coerce.number() }) }}
handler={(c) => c.json(getUser(c.params.id))}
/>
</router>
</router>
);
// Create and start server
const app = createApp();
app.serve(<App />);
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});TypeScript configuration
Configure your tsconfig.json for JSX:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "reono"
}
}Real‑world examples
- Scenario‑1: A focused starter demonstrating routing, validation, and middleware. See
apps/scenario-1. - Scenario‑2: A comprehensive Multi‑Tenant SaaS API Gateway. See
apps/scenario-2for code, middleware, routers, and tests.
SaaS API Gateway (Scenario‑2) highlights
- Multi‑tenant authentication, authorization, and request context
- Tiered rate limiting (Free, Premium, Enterprise) with per‑tenant isolation
- Dynamic CORS based on tenant configuration
- Feature routers: tenants, users, analytics, billing, and content
- Exhaustive integration + performance tests validating behavior and SLAs
Refer to the app’s README for local run instructions, example API keys, and test commands.
Core concepts
JSX elements
Reono provides intrinsic JSX elements for defining your API structure:
<router>
Groups routes under a common path prefix. Supports nesting for complex API structures.
<router path="api/v1">
<router path="users">{/* Routes nested under /api/v1/users */}</router>
</router>HTTP method elements
Define route handlers for specific HTTP methods with full type safety.
<get path="users" handler={(c) => c.json(users)} />
<post path="users" validate={{ body: userSchema }} handler={createUser} />
<put path="users/:id" handler={updateUser} />
<delete path="users/:id" handler={deleteUser} />
<patch path="users/:id" handler={patchUser} /><use> — middleware
Apply middleware to routes with clean composition. Middleware runs in declaration order.
<use handler={authMiddleware}>
<use handler={loggingMiddleware}>
<get path="protected" handler={protectedHandler} />
</use>
</use>Path patterns
{/* Static */}
<get path="users" />
{/* Params (captured in c.params) */}
<get path="users/:id" />
<get path="users/:userId/posts/:postId" />
{/* Wildcards (matches remaining path) */}
<get path="files/*" />Request context
type ApiContext = {
params: Record<string, any>;
body: any;
req: Request;
url: URL; // Parsed request URL
state: Map<string, any>; // Per-request state bag
res?: Response;
json: (data: unknown, init?: ResponseInit) => Response;
};Validation & type safety
Use any validation library with a parse method (e.g. Zod) for runtime validation and compile‑time inference:
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0).optional(),
});
<post
path="users"
validate={{
body: userSchema,
params: z.object({ id: z.coerce.number() }),
}}
handler={(c) => c.json(createUser(c.body), 201)}
/>;Middleware system
Create reusable middleware with the Koa‑style (context, next) pattern:
import { type MiddlewareHandler } from "reono";
const logger: MiddlewareHandler = async (c, next) => {
const start = Date.now();
const result = await next();
console.log(`← ${c.req.method} ${c.req.url} (${Date.now() - start}ms)`);
return result;
};
const auth: MiddlewareHandler = async (c, next) => {
const token = c.req.headers.get("authorization");
if (!token) return new Response("Unauthorized", { status: 401 });
return next();
};Complete example
import { createApp } from "@reono/node-server";
import { type MiddlewareHandler, CORS } from "reono";
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.enum(["user", "admin"]).default("user"),
});
const userUpdateSchema = userSchema.partial();
let users = [
{ id: 1, name: "Alice", email: "[email protected]", role: "admin" },
{ id: 2, name: "Bob", email: "[email protected]", role: "user" },
];
const logger: MiddlewareHandler = async (c, next) => {
const start = Date.now();
const result = await next();
console.log(`← ${c.req.method} ${c.req.url} (${Date.now() - start}ms)`);
return result;
};
const getAllUsers = (c) => c.json(users);
const getUser = (c) => {
const user = users.find((u) => u.id === c.params.id);
if (!user) return new Response("User not found", { status: 404 });
return c.json(user);
};
const createUser = (c) => {
const newUser = { id: Date.now(), ...c.body };
users.push(newUser);
return c.json(newUser, 201);
};
const updateUser = (c) => {
const index = users.findIndex((u) => u.id === c.params.id);
if (index === -1) return new Response("User not found", { status: 404 });
users[index] = { ...users[index], ...c.body };
return c.json(users[index]);
};
const deleteUser = (c) => {
const index = users.findIndex((u) => u.id === c.params.id);
if (index === -1) return new Response("User not found", { status: 404 });
users.splice(index, 1);
return new Response(null, { status: 204 });
};
const App = () => (
<CORS origins={["*"]} headers={["Content-Type", "Authorization"]}>
<use handler={logger}>
<router path="api/v1">
<get
path="health"
handler={(c) => c.json({ status: "ok", timestamp: Date.now() })}
/>
<router path="users">
<get path="" handler={getAllUsers} />
<get
path=":id"
validate={{ params: z.object({ id: z.coerce.number() }) }}
handler={getUser}
/>
<post path="" validate={{ body: userSchema }} handler={createUser} />
<put
path=":id"
validate={{
params: z.object({ id: z.coerce.number() }),
body: userUpdateSchema,
}}
handler={updateUser}
/>
<delete
path=":id"
validate={{ params: z.object({ id: z.coerce.number() }) }}
handler={deleteUser}
/>
</router>
</router>
</use>
</CORS>
);
const app = createApp();
app.serve(<App />);
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running on http://localhost:${PORT}`);
});Intrinsic elements and props
<router path>: groups child routes under a path prefix.pathcan be string or array. Supports nested routers.<use handler>: middleware. Runs outer‑to‑inner and unwinds inner‑to‑outer. Multiple nestedusestack.- HTTP routes:
<get|post|put|delete|patch path handler validate?>pathsupports:- Static:
users - Params:
:id(captured inc.params, optionally coerced viavalidate.params) - Wildcard:
*(consumes the remainder)
- Static:
validate(optional):{ body?, query?, params?, headers? }where each is a schema‑like object withparse(input)(e.g. Zod). On success, values are replaced inc.body,c.params, etc. On failure, the runtime responds 400 with a JSON error payload.
Runtime semantics
- Methods:
GET,POST,PUT,DELETE,PATCH,OPTIONS,HEAD. Only the five route tags are supported in JSX;OPTIONS/HEADreturn 405 unless explicitly routed. - Matching & normalization:
- Leading/trailing/multiple slashes are normalized.
- Static segments take precedence over params at the same depth (
/users/mebeats/users/:id). *wildcard matches the remainder.
- 404 vs 405:
- Unknown path → 404 Not Found.
- Known path without a handler for the method → 405 Method Not Allowed (plain text).
- Middleware composition:
- Koa‑style
(ctx, next); order is declaration order. - Calling
next()multiple times is guarded (500). - Middleware may short‑circuit by returning a
Response.
- Koa‑style
- Body parsing (by
Content-Type):application/json→ parsed JSON (invalid JSON → 400).text/*→ string viareq.text().application/x-www-form-urlencoded→ plain object fromFormData.
- Context and responses:
cincludes{ params, body, req, url, state, res?, json(data, init?) }.c.json()setscontent-type: application/json; charset=utf-8and optional status.- Handlers may return a
Responsedirectly; it is passed through unchanged. - If no handler or middleware writes a response, a default
200with JSONnullis returned.
Utilities (sugar elements)
Convenience components built on top of <use> for common patterns. Import from reono:
import { CORS, RateLimit, Guard, Static, FileUpload, Transform } from "reono";CORS
Preflight + headers with a single wrapper (OPTIONS routes are injected automatically for matched paths):
<CORS
origins={["https://app.example.com", "https://admin.example.com"]}
methods={["GET", "POST", "PUT", "DELETE"]}
headers={["Content-Type", "Authorization"]}
credentials
maxAge={86400}
>
<router path="api">{/** your routes */}</router>
</CORS>RateLimit
Apply rate limits per IP or custom key (e.g., API key or tenant ID). Headers are added to responses automatically:
<RateLimit
requests={100}
window={60_000}
keyGen={(c) =>
c.req.headers.get("x-api-key") ??
c.req.headers.get("x-forwarded-for") ??
"anonymous"
}
>
<router path="api">{/** rate‑limited routes */}</router>
</RateLimit>Guard
Conditionally allow access. Useful for auth checks and feature flags:
<Guard
condition={(c) => c.state.get("user")?.role === "admin"}
fallback={(c) => c.json({ error: "Admin required" }, 403)}
>
<router path="admin">{/** admin routes */}</router>
</Guard>Transform
Post‑process responses (add headers, wrap payloads, etc.):
<Transform
transform={(out) =>
out instanceof Response
? out
: { data: out, requestId: crypto.randomUUID() }
}
>
<router path="api">{/** all responses wrapped */}</router>
</Transform>Static
Serve static assets at a mount point; can be wrapped with auth middleware:
<Static path="/assets" directory="./public" />
<Static path="/uploads" directory="./uploads" middleware={[authMiddleware]} />FileUpload
Validate uploads (size and type) and access parsed files from c.state:
<FileUpload
maxSize={10 * 1024 * 1024}
allowedTypes={["image/jpeg", "image/png"]}
>
<post
path="upload"
handler={(c) => c.json({ uploaded: c.state.get("uploadedFileCount") })}
/>
</FileUpload>See tested examples in packages/core/__tests__/utilities/ and Scenario‑2 for multi‑tenant patterns using these utilities together.
Adapter: Node HTTP (@reono/node-server)
createApp()returns{ serve(element), listen(port, cb?), close() }.serve(element)builds the trie and stores a fetch‑compatible handler.listen(port)wires Node’shttp.createServerto translateIncomingMessage→ FetchRequestand write back the FetchResponse.
Testing
The repository includes extensive tests:
- Core/unit tests in
packages/core/__tests__ - Example‑level integration and performance tests in
apps/scenario-1andapps/scenario-2 - Additional sample tests in
apps/api
See each app’s README for scripts and commands to run locally. All scenarios are wired to validate 404/405 behavior, normalization, validation, middleware semantics, and performance characteristics.
Roadmap
- Developer experience: richer response helpers (text, HTML, redirects, streaming), query/headers/cookies, better error messages
- Ecosystem: more utility components, static/caching improvements
- Adapters: Bun, Deno, Cloudflare Workers, Vercel Edge
- OpenAPI: schema generation and docs tooling
- Performance: streaming, caching, compression
License
ISC
—
Reono — Build HTTP APIs with the power and familiarity of JSX.
