Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Code of Conduct

Be kind and respectful. This project follows a standard code of conduct: be inclusive, avoid personal attacks, and report violations to the maintainers.

This is a short version — for serious projects prefer the full Contributor Covenant.
28 changes: 28 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Contributing

Thanks for your interest in contributing! The following guidelines help keep the project stable and easy to maintain.

Workflow

1. Open an issue first for substantial or breaking changes to get feedback from maintainers.
2. Create a branch for each change (e.g., `fix/runtime-typing`, `feat/logger-adapter`).
3. Keep changes small and atomic — one logical change per PR.
4. Add or update tests for behavior you change.
5. If your change affects the public API, document it clearly (CHANGELOG) and discuss versioning.
6. Open a Pull Request with a clear description, rationale, and testing instructions.

Code style and expectations

- Use TypeScript with `strict` mode enabled. Prefer `unknown` over `any` and perform explicit narrowing/casts when necessary.
- Keep public exports stable: avoid renaming/removing exported functions without prior discussion.
- Write clear, concise commit messages (short summary + explanation why).

PR checklist

- [ ] Branch name follows pattern and describes intent.
- [ ] Tests added or updated where relevant.
- [ ] Linted / code formatted (if linting/formatting is configured).
- [ ] Changes described in PR body and linked to any related issue.
- [ ] If public API changed, document it and include a migration note.

If you're unsure about implementation details or the scope of a change, open an issue or ask in the PR comments — maintainers will help.
59 changes: 59 additions & 0 deletions Learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Learn ReactServe

This guide explains core concepts, runtime behavior, and practical tips for working on the codebase without introducing breaking changes.

Overview

- ReactServe is a small monorepo containing the core runtime (`packages/react-serve-js`), a project scaffolder (`packages/create-react-serve`), and several example apps in `examples/`.
- The core idea: authors write backend logic using a JSX-like tree (components such as `<App>`, `<Route>`, `<RouteGroup>`, `<Middleware>`, and `<Response>`). The runtime converts that tree into Express routes at startup.

Design and Principles

- Keep public API stable: avoid renaming or removing exported symbols (`serve`, `useRoute`, `useSetContext`, `useContext`, `Middleware` type, etc.).
- Prefer small, atomic changes so users can update incrementally.
- Favor safety in typing: prefer `unknown` (or specific interfaces) instead of `any`, then narrow/cast locally where necessary.
- Centralize cross-cutting concerns (logging, error handling) so behavior is consistent and easy to change.

Runtime model (high level)

- At startup, `serve(element)` walks the JSX tree and collects routes, middleware and global config.
- Route handlers are stored as functions (typically an async function returning a `Response` element or primitive). A `createExpressHandler` wrapper sets up a `routeContext`, runs middlewares in sequence, awaits the final output and normalizes it into an HTTP response.
- Middlewares are executed sequentially and can call `next()` to advance. The middleware can return a `Response` to short-circuit.

Key API summary

- `serve(element: ReactNode): Server` — boot the server from an `App` root element.
- `App({ children, port?, cors? })` — root component for configuration.
- `Route({ path, method, middleware?, children })` — defines an endpoint. `children` is typically an async handler returning a `Response` element or primitive.
- `Response({ json?, status?, text?, html?, headers?, redirect? })` — helper element for sending replies.
- `Middleware({ use })` — wrapper to include middleware functions inside `RouteGroup` or at top-level.
- Hooks available inside handlers/middleware:
- `useRoute()` — returns the runtime route context (request, response, params, query, body). (Note: the runtime uses a typed `RouteContext` internally; prefer to treat fields as `unknown` and narrow them.)
- `useSetContext(key, value)` / `useContext(key)` — per-request middleware context and global context.

Runtime types and safety

- The runtime manipulates dynamic JS shapes (JSX runtime objects). To avoid unsafe usage of `any`, the code uses `unknown` or `Record<string, unknown>` in public internals and narrows/casts locally only when necessary. This reduces accidental runtime errors while keeping the code compatible with consumer usage.

Logging

- A minimal `logger` utility lives in `packages/react-serve-js/src/logger.ts` and is used by the core runtime. It provides `info`, `warn`, `error`, and `debug` methods and can be disabled via the `REACT_SERVE_LOG` environment variable.
- Rationale: centralizing logging makes it easy to switch implementations (for example to `pino` or `winston`) and to control verbosity in production.

Error handling and best practices

- The runtime catches handler errors and returns a 500 JSON error when possible. Consider extracting a shared error-handler utility for improved observability and consistent error shapes.
- Validate external inputs (request body, params) inside handlers using a validator library (Zod/ajv/Joi) when building real endpoints.

Development workflow

- Install dependencies at the repo root: `npm install`.
- Build the core package: `npm run build --workspace=packages/react-serve-js`.
- Run the basic example in dev mode: `npm run dev --workspace=examples/basic`.
- To silence runtime logging for the session:
- PowerShell (session only): `$env:REACT_SERVE_LOG = 'false'` then run the dev script.

Testing suggestions

- Add unit tests for the route parser (`processElement`) to ensure routes and middleware are discovered correctly.
- Add tests for middleware ordering and short-circuit behavior.
15 changes: 7 additions & 8 deletions examples/basic/backend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type MiddlewareFunction,
//@ts-ignore
} from "../../packages/react-serve-js/src";
import logger from "../../packages/react-serve-js/src/logger";

const mockUsers = [
{ id: 1, name: "John Doe", email: "john@example.com" },
Expand Down Expand Up @@ -44,7 +45,7 @@ const authMiddleware: MiddlewareFunction = (req, next) => {

// Logging middleware example
const loggingMiddleware: MiddlewareFunction = (req, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
logger.info(`[${new Date().toISOString()}] ${req.method} ${req.path}`);

// Add request timestamp to context
useSetContext("requestTimestamp", Date.now());
Expand All @@ -54,7 +55,7 @@ const loggingMiddleware: MiddlewareFunction = (req, next) => {

// Route-specific middleware example
const slowRouteMiddleware: MiddlewareFunction = (req, next) => {
console.log(`⏱️ Slow route accessed: ${req.path}`);
logger.info(`⏱️ Slow route accessed: ${req.path}`);

// Simulate some processing time
useSetContext("processStartTime", Date.now());
Expand All @@ -64,9 +65,7 @@ const slowRouteMiddleware: MiddlewareFunction = (req, next) => {

// Another route-specific middleware
const adminLogMiddleware: MiddlewareFunction = (req, next) => {
console.log(
`🔐 Admin route accessed: ${req.path} at ${new Date().toISOString()}`
);
logger.info(`🔐 Admin route accessed: ${req.path} at ${new Date().toISOString()}`);

useSetContext("adminAccess", true);

Expand Down Expand Up @@ -94,7 +93,7 @@ export default function Backend() {
{/* Route with individual middleware */}
<Route path="/slow" method="GET" middleware={slowRouteMiddleware}>
{async () => {
const processStart = useContext("processStartTime");
const processStart = (useContext("processStartTime") as number) || 0;
const processingTime = Date.now() - processStart;

return (
Expand Down Expand Up @@ -208,12 +207,12 @@ export default function Backend() {

<Route path="/me" method="GET">
{async () => {
const user = useContext("user");
const user = useContext("user") as Record<string, any> | null;
const timestamp = useContext("requestTimestamp");
return (
<Response
json={{
...user,
...(user ?? {}),
requestedAt: timestamp,
}}
/>
Expand Down
20 changes: 20 additions & 0 deletions packages/react-serve-js/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const LOG_ENABLED = process.env.REACT_SERVE_LOG !== "false";

export const logger = {
info: (...args: unknown[]) => {
if (LOG_ENABLED) console.log(...args);
},
warn: (...args: unknown[]) => {
if (LOG_ENABLED) console.warn(...args);
},
error: (...args: unknown[]) => {
// Always show errors
console.error(...args);
},
debug: (...args: unknown[]) => {
if (process.env.NODE_ENV !== "production" && LOG_ENABLED)
console.debug(...args);
},
};

export default logger;
Loading