Build authenticated tic-tac-toe from the ground up.
Initialize once with Redweb, then add game rules, typed commands, connected players, server TSX, authentication, and one application entry point. Every chapter extends the same project.
These are complete generated starters, separate from the tic-tac-toe course. Each walkthrough includes its exact files, setup, tests, and operational limits.
One complete example connects a file upload, an explicit provider, and resource updates across two browser tabs. It comes from the published guide, not a second site implementation.
Define pages, socket routes and application services together. Nothing listens until run(); one owner starts and stops the shared HTTP/WebSocket listener.
Options
pages: decorated page classes (default [])
sockets: SocketRoute classes (default [])
services: application-wide classes with onInit(app, signal) and onShutdown() (default [])
port: number (default 8181); use 0 for an ephemeral test port
bind: string (default 0.0.0.0)
httpServices: existing Express service descriptors; route-specific SocketService classes remain on socket routes
startupTimeoutMs / shutdownTimeoutMs: total lifecycle budgets (default 5000 each)
signals: install owned process signal handling when run begins (default true); embedded applications/tests can set false
Existing live-page/HTTP options, including templateRoot, authentication, origins, server (Express app), development and ssl
Static export remains exportStatic(); defineApp does not accept a static export flag
providers: explicit application-owned objects available to @inject fields on pages
Create a deferred definition. Registration arrays are copied; imports and definitions open no port or process handlers.
run(): Promise<Application>
Initialize services in order and bind one HTTP/WS listener. Repeated calls share startup while running. A stopped application cannot restart.
shutdown(): Promise<void>
Cancel pending startup and perform bounded, idempotent cleanup. Close HTTP/socket resources before releasing application services in reverse order. Preserve cleanup errors.
options
Copied definition, available for defining independent test applications with port: 0 and signals: false. Captured objects and supplied Express apps are not deep-cloned.
app / server / http / sockets / services
Runtime Express application, native listener, HTTP adapter, optional WebSocket server and service instances. HTTP members are null before startup; await run before use.
revoke(principal): Promise<number>
Revoke matching live-page sessions; zero before live pages have been initialized.
inspect(): DevelopmentSnapshot | null
Return opt-in live-page inspection metadata, or null when unavailable/disabled.
Lower-level Express HTTP listener with parsers, CORS, static assets and services. Prefer defineApp for new applications; use HttpServer when you explicitly need its immediately listening constructor or custom listener ownership.
Options
port: number (default 80)
bind: string (default 0.0.0.0)
publicPaths: string[] (default ["./public"])
services: array of { serviceName, method, function }
listen: boolean (default true); set false to build app without binding a port
listenCallback: function invoked after listen
encoding: "json" | "urlencoded" (default json)
corsOptions: passed to cors when explicitly configured (default false, including undefined/null; no wildcard CORS). CORS read access does not permit cross-origin unsafe methods.
publicOrigin: exact external HTTP(S) origin when a trusted TLS proxy forwards to a private listener; allows same-origin browser mutations from that public origin without trusting arbitrary forwarded headers
Merges defaults, wires body parsing and opt-in CORS, contains static serving inside each public root, registers REST services, and starts listening unless listen is false. Unsafe requests with a foreign Origin or Sec-Fetch-Site other than same-origin/none are rejected; cookie-bearing unsafe requests also require a same-origin signal.
app (Express instance)
Use the returned `app` to add middleware or routes exactly like a normal Express server.
Public Express app builder used by HttpServer and HttpsServer. Use it for advanced composition when you want Redweb middleware, static files, and services without any listener behavior.
Options
All `HttpServer` app-building options
server: existing Express application to configure
listen is ignored because BaseHttpServer never binds a port
TLS-enabled variant of `HttpServer`. Accepts `ssl.key` and `ssl.cert` file paths, wraps them in an https server, and bootstraps the same middleware pipeline.
HTTP-upgrade WebSocket server on top of `ws`. Builds and listens on its own HTTP server by default; if you pass a Node `server`, it attaches upgrade handling and leaves `.listen()` to you unless `listen: true` is explicit.
Options
port: number (default 3000)
listen: boolean (default true for owned servers); supplied servers do not listen unless explicitly true
server: existing http.Server to attach to without double-listening (optional)
routes: array of SocketRoute subclasses (defaults to a single DefaultRoute at "/")
JavaScript
import { HttpServer, SocketServer } from'redweb'import { ChatRoute } from'./ChatRoute.js'// Advanced: attach to a listener whose lifecycle your application owns.const http = new HttpServer({ listen: false, publicPaths: [] })
const sockets = new SocketServer({ server: http.server, routes: [ChatRoute] })
http.server.listen(3030)
// At application shutdown, detach sockets before closing their shared listener.asyncfunction shutdown() {
await sockets.shutdown()
await http.shutdown()
}
Methods
constructor(options)
Creates or reuses an HTTP server, instantiates supplied routes or a DefaultRoute, attaches upgrade handling, and starts listening only when Redweb owns the server or listen is explicitly true.
addRoute(RouteClass)
Instantiate and register another `SocketRoute` at runtime.
handleUpgrade(req, socket, head)
Internal: normalises the request path, picks a matching route (or "/"), and forwards the upgrade to that route's server.
shutdown()
Closes all registered routes and the underlying HTTP server.
Loads TLS files or reuses a supplied HTTPS server, registers the provided routes, attaches upgrade handling, and starts listening only when Redweb owns the server or listen is explicitly true.
addRoute(RouteClass)
Same runtime route attachment as `SocketServer`.
shutdown()
Stops routes and services. Closes the HTTPS listener only when owned or closeServerOnShutdown is explicitly true; supplied servers remain open by default.
Defines a WebSocket endpoint and owns its handlers, services, clients, and opt-in multiplayer policies. Routes can add bounded admission, transport limits, ordered work, heartbeat, rooms, resumable sessions, distribution, draining, metrics, and protocol negotiation without changing legacy routes.
Options
path: WebSocket path (required)
handlers: array of handler classes (required)
services: array of SocketService subclasses (optional)
allowDuplicateConnections: allow multiple clients from the same IP
websocketOptions: options passed to ws WebSocketServer, such as maxPayload or perMessageDeflate; closeTimeout defaults to 5000 milliseconds (integer 1..2147483647) and bounds an already-started closing handshake, not healthy idle connections
admission: authenticate, validate origins, and optionally place a client before upgrade
maxPendingUpgrades: finite concurrent admission/negotiation work (default 64)
limits: connection, message-rate, pending-message, and outbound-buffer ceilings
orderedMessages: serialize each connection through a bounded queue
heartbeat: one route-level half-open connection monitor
rooms and sessions: bounded grouping and expiring application-issued ownership
distribution: optional bounded broker adapter; no broker is bundled or required
drainHandlers: track handler work and expose a cooperative shutdown signal
protocol: version negotiation, stable envelopes/error codes, and optional binary codecs
connections: optional connectedClients group for verified player facades and private page projection; enables rooms and handler draining by default
Validates input, instantiates handlers/services, sets up a `ws` server for the path with any websocketOptions, and registers connection listeners.
addHandler(HandlerClass)
Adds another handler class unless a handler with the same name already exists.
handleConnection(socket, req)
Stores the client (deduping by IP unless allowed), decorates the socket with `sendJson`/`broadcast`, and wires close/error/message listeners.
handleMessage(socket, data)
Dispatches an already-parsed message object by type. Only literal false declines success; a deliberate rejection should send a correlated protocol error. Unexpected handler exceptions retain sanitized failure and connection-close behavior.
handleBinaryMessage(socket, buffer)
Handles binary frames separately from JSON text frames and delegates to a handler selected by acceptsBinary(socket, buffer).
handleClose(socket, ip)
Removes the client from the registry and triggers an optional `connectionCloseCallback`.
shutdown()
Marks the route draining, stops services, bounds handler/adapter cleanup, closes clients, and releases every route-owned resource.
beginDrain()
Flips readiness and rejects new upgrades before shutdown work begins.
isReady()
Reports whether the route is accepting upgrades and any required distribution adapter is healthy.
publish(type, payload)
Publishes through the optional bounded distribution adapter and resolves to a success boolean.
handleError(socket, error, ip)
Logs socket errors; override for custom reporting.
Route-scoped simulation clock that compensates for drift, prevents overlapping async ticks, bounds catch-up work, and reports dropped retained lag instead of replaying forever.
Bounded route-local connection groups. Use asynchronous enterRoom for guarded membership, joinRoom for unguarded rooms, and roomBroadcast for delivery; disconnect cleanup removes all memberships and reclaims empty rooms.
Idempotently joins an unguarded bounded room and returns whether membership is active. Throws when rooms.authorize is configured; use await socket.enterRoom(roomId) instead.
await socket.enterRoom(roomId)
Applies configured room authorization before committing bounded membership and resolves to whether membership is active. Also works for unguarded rooms.
registry.roomsFor(socket)
Returns a snapshot of the socket's current room IDs. Room membership is delivery state, not a durable game seat.
socket.leaveRoom(roomId)
Idempotently leaves one room and reclaims it when empty.
socket.roomBroadcast(roomId, data, options?)
Serializes once and sends to selected connected members.
Bounded, expiring ownership records for application-issued opaque session IDs. Redweb handles takeover and expiry; the application owns credential issuance and payload validation.
Low-level envelope helper from redweb/client. It wraps a socket you own; it does not connect, reconnect or correlate pending requests. For those features use redweb-client; server TSX pages generate their client integration automatically.
Abstract message handler. Provide a name in the constructor; clients send `{ type: name, ... }` to target JSON messages, while binary frames can be accepted and handled as raw Buffer payloads.
Shared lifecycle and route-composition base for SocketServer and SecureSocketServer. Prefer defineApp for application startup; use concrete server classes or this base for infrastructure integrations.
JavaScript
import { BaseSocketServer } from'redweb'// SocketServer and SecureSocketServer inherit:// addRoute(), handleUpgrade(), beginDrain(), and shutdown().
Methods
addRoute(RouteClass)
Creates and registers one route class while enforcing unique paths.
beginDrain()
Stops accepting new upgrades across every registered route.
shutdown()
Drains routes and closes owned listeners without closing caller-owned servers.
Lower-level server-rendered page host. Prefer defineApp({ pages, sockets }) and app.run() to compose a new application; LiveHtmlServer remains available for direct page-host lifecycle integration.
Options
pages: non-empty array of classes decorated with page()
templateRoot: optional root for colocated HTML and CSS assets
sessionTtlMs and maxSessions: bound pending and reconnectable sessions
Pages are plain decorated classes; extending LivePage is optional. Prefer defineApp({ pages: [...] }) and app.run() for startup. start() remains a supported lower-level convenience.
Registers a unique route plus template, CSS, sharing, metadata, caching, and live/static behavior.
component()
Marks a class as a reusable state/action/lifecycle namespace.
component(render)
Creates a concise synchronous function component for stateless reusable HTML.
state(options?)
Publishes reassigned values; writable state may also receive bounded browser input.
action({ input? })
Explicitly exposes one method to rw-click or rw-submit. An optional Standard Schema input validates and transforms one submitted argument before invocation; ActionInput<typeof schema> describes its output. Invalid input stays recoverable, while validator bugs remain server failures. Undecorated methods stay unreachable.
rw-status="action"
Optional component-scoped placement for built-in action feedback. Without a slot, buttons/forms get an automatic status message. Pending duplicates from one control are suppressed; late responses preserve changed drafts and replacement forms. Disconnected actions are never queued or replayed.
view(stateName)
Renders each item of one decorated array for an rw-each collection.
inject(provider)
Assign an explicitly registered application provider to a page field; no hidden container or constructor injection.
liveResource() / resource(source, select)
Publish a value for a key and project it to matching active pages or components.
upload({ maxBytes?, accept? })
Expose a bounded streaming file action for rw-upload or rw-paste; applications own storage and validation.
Dependency-free server-side TSX that renders directly to HtmlFragment values. It provides readable components, fragments, arrays, automatic escaping, safe attributes, and existing html-fragment interoperability without React, a virtual DOM, or hydration.
Options
TypeScript: jsx = react-jsx and jsxImportSource = redweb
Production runtime: redweb/jsx-runtime; development runtime: redweb/jsx-dev-runtime
External CSS and rw-* server directives replace inline styles and browser event functions
TSX
// Start with npx redweb init; keep this source in a .tsx file.import { defineApp, page } from'redweb'import type { Child } from'redweb/jsx-runtime'function Card({ title, children }: { title: string; children?: Child }) {
return <article><h2>{title}</h2>{children}</article>
}
@page('/', { live: false })
class HomePage {
render() { return <Card title="Redweb">Readable server TSX</Card> }
}
const app = defineApp({ pages: [HomePage] })
app.run()
Methods
Intrinsic elements
Serialize standard, SVG, custom, data-*, aria-*, and rw-* attributes with HTML-correct boolean handling.
Fragments and arrays
Compose nested fragments and readonly child arrays without wrapper markup or comma coercion.
Function components
Synchronous functions receive typed props and children and must return an HtmlFragment or fragment array.
Escaping and URLs
Text and attributes escape automatically; URL attributes retain Redweb’s safe-protocol validation.
Interoperability
Existing html fragments nest in TSX and TSX fragments nest in html for incremental migration.
Renders non-live decorated pages to deterministic directory indexes and content-addressed CSS. It is intended for docs, marketing pages, and static hosting.
Options
outDir: required output directory; existing unrelated files are preserved
templateRoot: optional explicit root for templates and CSS
Lower-level rendering utility. Most applications should use plain @page classes with defineApp, or defineSite for static sites; this surface supports tooling and advanced integrations.
One shared Standard Schema contract validates wire payloads and infers client/server types. Route URLs choose the service; individual handler factories dispatch by message type.
ts
import { defineSocketContract } from'redweb/contract';
import { z } from'zod';
const position = { x: z.number().int().min(-100).max(100), y: z.number().int().min(-100).max(100) };
// Share this module with a browser or Node client. It imports no server application code.exportconst match = defineSocketContract('1', {
join: z.object({ name: z.string().trim().min(1).max(40) }).strict(),
move: z.object(position).strict(),
resume: z.object({ session: z.string().uuid() }).strict(),
state: z.object({ session: z.string().uuid(), name: z.string(), ...position }).strict(),
});
Methods
defineSocketContract(version, schemas, options?)
Creates an immutable contract and negotiated protocol policy from Standard Schema validators. Redweb does not require a particular validator at runtime.
handler(type, callback)
Creates a BaseHandler subclass. Validation completes before the callback receives its typed payload. The returned class can bind rw-submit or rw-click on a socket-bound page; Handler.with(payload) snapshots typed input.
client(socket)
Wraps an existing WebSocket with typed, validated send and parse methods. It does not open or reconnect the connection.
send(socket, type, payload, metadata?)
Validates server output before sending through the normal transport and protocol policy.
Route-owned connected players with checked identity, bounded room membership, deduplicated presence and private server-page updates. Reuses RoomRegistry; no second socket map or browser dispatcher is required.
Options
identity: rechecks the admitted principal against current credentials
page: lazy reference to the attached page class
project: side-effect-free per-player page state; online identities are deduplicated
reject / errorState: classify deliberately public errors and map them to page fields
raw: optional update/reject adapters for non-HTML clients
authorizationTimeoutMs / projectionTimeoutMs: bounded asynchronous work
ts
import { connectedClients } from'redweb'// Supply your authenticated account lookup, game service, page and contract.const players = connectedClients({
identity: context => account(context.request),
page: () => GamePage,
project: (player, room, online) => ({
game: games.resume(room, player.identity).snapshot(player.identity),
online,
}),
})
const match = players.bind(contract)
const Join = match.handler('join', (player, { room }) =>
player.join(room, () => games.join(room, player.identity)))
// Register connections: players and protocol: match.protocol on MatchRoute.// Attach GamePage with @page('/', { socket: MatchRoute }).// Its TSX form uses rw-submit={Join}.
Methods
players.bind(contract)
Creates typed handlers whose callback receives a ConnectedClient; successful commands refresh its rooms.
players.get(socket)
Returns the checked route-owned facade for page connection lifecycle hooks.
players.refresh(room)
Projects external domain changes to that room’s currently authorized connections.
player.identity / page / rooms / room
Admitted identity, private page, membership snapshot, and exactly-one-room convenience accessor.
player.join(room, commit?) / leave(room)
Checks capacity and authorization before a synchronous domain commit; failure removes newly added membership, not arbitrary domain side effects.