@reono/node-server
v1.0.9
Published
A Node.js server adapter for **Reono** that enables you to run your JSX-defined API routes on Node.js HTTP servers. This package provides a simple interface to convert Reono applications into Node.js HTTP handlers.
Readme
@reono/node-server
A Node.js server adapter for Reono that enables you to run your JSX-defined API routes on Node.js HTTP servers. This package provides a simple interface to convert Reono applications into Node.js HTTP handlers.
Overview
The @reono/node-server package:
- Node.js Integration: Converts Node.js
IncomingMessageto Web APIRequestobjects - Response Handling: Transforms Web API
Responseobjects back to Node.js HTTP responses - Simple API: Provides a clean, minimal interface for serving Reono applications
- Error Handling: Built-in error handling for unhandled exceptions
- Streaming Support: Handles request/response streaming efficiently
Installation
npm install @reono/node-server reono
# or
pnpm add @reono/node-server reono
# or
yarn add @reono/node-server reonoBasic Usage
import { createApp } from "@reono/node-server";
const App = () => (
<router path="api">
<get path="hello" handler={(c) => c.json({ message: "Hello, World!" })} />
<get path="users/:id" handler={(c) => c.json({ id: c.params.id })} />
</router>
);
const app = createApp();
// Register your Reono JSX app
app.serve(<App />);
// Start the HTTP server
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});API Reference
createApp()
Creates a new Reono application instance for Node.js.
const app = createApp();Returns: An application instance with the following methods:
app.serve(element: JSXElement)
Registers a Reono JSX element tree as the application handler.
app.serve(<App />);Parameters:
element: JSXElement- The root JSX element of your Reono application
Note: This method must be called before listen().
app.listen(port: number, callback?: () => void)
Starts the HTTP server on the specified port.
app.listen(3000, () => {
console.log("Server started!");
});Parameters:
port: number- The port number to listen oncallback?: () => void- Optional callback executed when the server starts
Throws: Error if serve() hasn't been called first.
app.close(callback?: (err?: Error) => void)
Stops the HTTP server.
app.close((err) => {
if (err) console.error("Error closing server:", err);
else console.log("Server closed");
});Parameters:
callback?: (err?: Error) => void- Optional callback executed when the server closes
Complete Example
// server.tsx
import { createApp } from "@reono/node-server";
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
});
// Mock data store
let users = [
{ id: 1, name: "Alice", email: "[email protected]" },
{ id: 2, name: "Bob", email: "[email protected]" },
];
const logger = (c, next) => {
console.log(`${new Date().toISOString()} ${c.req.method} ${c.req.url}`);
return next();
};
const App = () => (
<use handler={logger}>
<router path="api/v1">
<router path="users">
{/* GET /api/v1/users */}
<get path="" handler={(c) => c.json(users)} />
{/* POST /api/v1/users */}
<post
path=""
validate={{ body: userSchema }}
handler={(c) => {
const newUser = { id: Date.now(), ...c.body };
users.push(newUser);
return c.json(newUser, 201);
}}
/>
{/* GET /api/v1/users/:id */}
<get
path=":id"
validate={{ params: z.object({ id: z.coerce.number() }) }}
handler={(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);
}}
/>
{/* PUT /api/v1/users/:id */}
<put
path=":id"
validate={{
params: z.object({ id: z.coerce.number() }),
body: userSchema,
}}
handler={(c) => {
const index = users.findIndex((u) => u.id === c.params.id);
if (index === -1) {
return new Response("User not found", { status: 404 });
}
users[index] = { id: c.params.id, ...c.body };
return c.json(users[index]);
}}
/>
{/* DELETE /api/v1/users/:id */}
<delete
path=":id"
validate={{ params: z.object({ id: z.coerce.number() }) }}
handler={(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("", { status: 204 });
}}
/>
</router>
</router>
</use>
);
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}`);
console.log(`📚 API available at http://localhost:${PORT}/api/v1/users`);
});
// Graceful shutdown
process.on("SIGINT", () => {
console.log("\n🛑 Shutting down server...");
app.close(() => {
console.log("✅ Server closed");
process.exit(0);
});
});Environment Setup
TypeScript Configuration
Make sure your tsconfig.json is configured for Reono:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"jsxImportSource": "reono",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}Package.json Scripts
{
"scripts": {
"dev": "tsx watch src/server.tsx",
"start": "node dist/server.js",
"build": "tsc && node dist/server.js"
},
"dependencies": {
"@reono/node-server": "^1.0.0",
"reono": "^1.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}Development vs Production
Development
Use tsx for development with hot reloading:
npx tsx watch src/server.tsxProduction
Compile TypeScript and run the compiled JavaScript:
tsc
node dist/server.jsError Handling
The node-server package includes built-in error handling:
- Unhandled Errors: Automatically caught and converted to 500 responses
- Request Processing: Errors during request/response conversion are handled gracefully
- Validation Errors: Reono's validation errors are properly formatted as 400 responses
For custom error handling, implement it in your route handlers or middleware:
const errorHandler = async (c, next) => {
try {
return await next();
} catch (error) {
console.error("Route error:", error);
return c.json({ error: "Something went wrong" }, 500);
}
};
const App = () => <use handler={errorHandler}>{/* Your routes */}</use>;Request/Response Conversion
The package handles conversion between Node.js and Web API standards:
Request Conversion
- Converts Node.js
IncomingMessageto Web APIRequest - Handles headers, method, URL, and body streaming
- Supports both HTTP and HTTPS
- Automatically detects protocol from socket encryption
Response Conversion
- Converts Web API
Responseto Node.js HTTP response - Transfers status, headers, and body
- Handles streaming and buffering appropriately
- Sets appropriate
Content-Lengthheaders
Performance Considerations
- Streaming: Request bodies are streamed using Node.js streams converted to Web streams
- Memory: Response bodies are currently buffered for simplicity (streaming support planned)
- Headers: Efficient header conversion between Node.js and Web API formats
- Error Handling: Minimal performance impact from error boundaries
Compatibility
- Node.js: Requires Node.js 18+ (for Web API support)
- TypeScript: Full TypeScript support with comprehensive type definitions
- ESM/CJS: Supports both ES modules and CommonJS
- Reono: Compatible with all Reono core features
Deployment
Docker
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/server.js"]Environment Variables
PORT=3000
NODE_ENV=productionLicense
ISC
Contributing
This package is part of the experimental Reono ecosystem. Contributions and feedback are welcome!
