Small setup. Clear ownership.

HTTP and WebSockets,without the ceremony.

Redweb is a small Node.js foundation for web apps, APIs, realtime features, multiplayer services, and server-rendered HTML. Start with one useful piece; compose them when the application grows.

Node.jsTypeScript + TSXExpress + wsOpt-in controls
One Node listener

Web on one side. Realtime on the other.

1 port
1

Express application

2

Static files and JSON APIs

3

WebSocket upgrades

4

Decorator-first rendered pages

5

One explicit shutdown path

Define your pages, sockets, and services together. Call app.run() when you’re ready to open the listener.

HTTP

Express routes and static files

WS

JSON and raw binary handlers

TSX

Server-rendered components

1 port

One explicit lifecycle

The useful parts, already connected

A clean starting point that stays understandable.

Redweb removes repetitive wiring while leaving pages, Express, Node servers, WebSocket routes, and handler classes visible and ordinary.

HTTP and HTTPS

Express, already arranged

Serve static files, JSON APIs, and middleware without repeating the same server setup.

WebSockets

Messages find their handler

Give each socket path a route and each JSON message a type. Redweb owns upgrades, clients, binary frames, and lifecycle.

Live HTML

The server owns the state

Render TSX, inject application services, stream file actions, and project shared resources to connected pages.

Composition

Define once. Run together.

Register pages, socket routes, and application services with defineApp. One run() call owns startup and shutdown on a shared listener.

HTML without a client framework

Readable TSX, from first render to realtime.

Write ordinary TypeScript and TSX. Decorators expose only the state and actions you choose. Redweb renders directly to HTML, scopes server actions, and uses one bounded socket when the page is live.

Open the realtime template walkthrough →

This example is independent of the tic-tac-toe tutorial and includes its own setup and tests.

Complete setup, files and tests →

Browse application source, compiler configuration and assets

The generated build and test helpers come with redweb init.

app.tsx
import { action, defineApp, page, state } from 'redweb';

@page('/', { shared: true })
class CounterPage {
    @state() count = 0;

    @action()
    increment() { this.count += 1; }

    render() {
        return <button rw-click="increment">Count {this.count}</button>;
    }
}

const app = defineApp({ pages: [CounterPage] });
app.run();
When realtime becomes multiplayer

Grow into stricter controls, route by route.

The simple path stays simple. When a game route needs more, opt into bounded rooms, sessions, admission, backpressure, protocol evolution, distribution, and draining.

Rooms

Bounded, route-local membership with deterministic disconnect cleanup.

Sessions

Application-issued resumable ownership with expiry and atomic takeover.

Fixed ticks

Drift-aware, non-overlapping simulation steps with bounded catch-up.

Protocol

Version negotiation, stable envelopes and codes, generated types, and codec hooks.

Distribution

A small broker seam with bounded fan-out and honest best-effort semantics.

Observability

Low-cardinality metrics for capacity, failures, readiness, rooms, and connections.

One small setup

Define your app. Start HTTP and sockets together.

Define pages, socket routes and application services together. Nothing listens until run(); one owner starts and stops the shared HTTP/WebSocket listener.

A page at / and a socket route at /chat share one listener. This independent built-in template has its own setup, source files, and tests.

Open the HTTP + WebSocket template walkthrough →

Read the application API guide →

Complete setup, files and tests →

Browse application source, compiler configuration and assets

The generated build and test helpers come with redweb init.

http-ws.tsx
import { BaseHandler, defineApp, page, SocketRoute, type RedWebSocket } from 'redweb';

@page('/', { live: false })
class HomePage {
    render() { return <h1>Hello from HTTP!</h1>; }
}

class Hello extends BaseHandler {
    constructor() { super('hello'); }
    onMessage(socket: RedWebSocket) {
        socket.sendJson({ type: 'hello', message: 'Hello from WebSockets!' });
    }
}

class ChatRoute extends SocketRoute {
    constructor() { super({ path: '/chat', handlers: [Hello] }); }
}

const app = defineApp({ pages: [HomePage], sockets: [ChatRoute] });
await app.run();
Start with the web. Add realtime. Grow deliberately.

A small base for things that need to move.