Http-ws: complete application
Documentation for Redweb 0.13.3. Install that exact version when following these examples.
HTTP and WebSockets on one listener
One Node server answers ordinary HTTP requests and upgrades /chat connections to WebSockets. HTTP paths select Express services; a socket URL selects a route and each message's type selects a handler. No socket decorators or secondary message.action dispatcher are needed.
After starting the application, request http://127.0.0.1:8181/health to receive {"ok":true}. Connect a WebSocket to ws://127.0.0.1:8181/chat and send {"type":"hello"} to receive {"type":"hello","message":"Hello from the server!"}. This is a raw JSON socket example, not a chatroom UI or the versioned socket-contract protocol. Use the chat or socket starter for those applications.
The HTTP builder does not bind a port. The socket service explicitly takes responsibility for listening and closing the supplied server with listen: true and closeServerOnShutdown: true. Call the returned application's shutdown(); it processes route failures and still closes its HTTP/TCP peers. Do not separately close the HTTP builder. Importing this module creates no listener; the standalone entrypoint uses the same bounded runApp helper as the other starters.
/health reports liveness, not readiness or completed application work. Loopback binding is intentional. Before exposing the service, choose your deployment bind address, configure HTTPS/WSS, trusted origins, authentication, authorization, payload/connection limits, and any persistence you need. Shared listeners do not automatically provide these policies. Shutdown may force connections closed; it does not guarantee message delivery or durable work.
npm test runs real HTTP and WebSocket requests on ephemeral ports, checks strict socket routing and multiple clients, verifies idempotent cleanup with an incomplete HTTP peer, and proves a failing route cleanup still closes the listener. It also runs the shared process-lifecycle suite. No mocks are used. The package verification gate repeats the tests against the compiled application with src/ unavailable.
Setup and acceptance
npx --yes redweb@0.13.3 init my-http-ws --template http-ws
cd my-http-ws
npm install --save-exact redweb@0.13.3
npm test
npm run devRequirements: Node.js 18 or newer and npm for the realtime, chat, site, socket and http-ws templates; the dashboard template requires Node.js 22.13+ for native SQLite. Use a currently supported Node.js release in production.
For an unreleased checkout or tarball, first run npm install --save-exact TARBALL, replacing TARBALL with the absolute path to the same tested Redweb tarball used to generate this app (quote paths containing spaces). This installs the matching package and its published client dependency. Do not substitute an older registry release or latest. Published Redweb releases can use the installation command below directly.
npm install
npm test
npm run devHTTP starters open at http://localhost:8181; the authenticated dashboard uses http://127.0.0.1:8181/login and requires account provisioning described below. Set the PORT environment variable to change the listener.
npm test builds and runs real HTTP/WebSocket integration tests on an ephemeral loopback port. No mocks or external service are needed.
npm run test:coverage runs the same tests with application coverage mapped back to TypeScript. Reports are written to the ignored coverage/ directory; this is separate from Redweb library coverage. TypeScript-generated decorator accessors can appear in function counts even when the framework does not call them. The report exposes remaining gaps; it does not certify complete application coverage. Source maps are generated during the build for diagnostics and coverage, but no coverage collector is loaded by npm start.
Development and production
Edit src/app.tsx. npm run dev watches TypeScript, TSX, CSS, HTML, and the root TypeScript configuration,
then rebuilds and restarts the server. A type error stops startup until you fix it. On direct localhost access,
HTML pages refresh automatically when a new server revision is ready. If edits were detected, a keyboard-accessible
notice keeps the old document until you choose Reload and discard drafts. This is a conservative edit guard,
not autosave or browser hot-module replacement: restarts reset in-memory state and old socket sessions.
The generated development command sets REDWEB_DEV_REFRESH=1; development: { refresh: false } overrides it.
The refresh feature is refused under NODE_ENV=production, applies only to served HTML (not raw sockets or static exports),
and creates no local/session-storage copy of form contents. Use direct localhost, 127.x.x.x, or [::1] access;
custom hostnames, tunnels and proxy-forwarded origins are not supported by this development helper.
npm run build checks types and copies CSS/HTML beside the compiled classes in dist/.
Run npm start to serve the compiled app. For deployment, build first, ship dist/, package.json, and the lockfile,
then install runtime dependencies with npm ci --omit=dev. The application does not require TypeScript or src/ at runtime.
The standalone entrypoint calls the shared runApp(createApp) helper. Importing either module starts no listener and installs no process handlers. On SIGINT/SIGTERM, a listener error, or native listener closure, the helper calls application shutdown once. Repeated signals do not bypass cleanup. The five-second outer deadline covers the whole application, including database/worker cleanup after HTTP closes; customize it with the helper's second argument if necessary. Cleanup must resolve only after resources are released. A failed cleanup sets a failure exit status and retains a deadline for any surviving handles; the helper never resets an existing failure status. If cleanup does not finish in time, the entrypoint terminates the process with a failure status. This cannot preempt synchronous code blocking Node's event loop and does not make in-memory state durable. Factory functions remain responsible for releasing partially constructed resources before throwing.
The shipped lifecycle tests exercise actual processes, HTTP/TCP/WebSocket peers and timers. Linux uses actual OS signals; Windows tests explicitly emit signal events inside the process because killing a Windows child does not exercise graceful POSIX signal delivery. This is not a claim that Windows console/service managers forward the same signals. Deploy with a supervisor that forwards the supported termination signal and allows longer than the configured cleanup deadline.
For public deployment, configure HTTPS/WSS at your Node server or reverse proxy, authentication, trusted origins,
and application-specific rate limits. These starters are demonstrations, not a hosted identity or database service.
Never commit secrets; .env is ignored but is not loaded automatically.
npx --no-install redweb doctor --json reports configuration problems without changing your files.
Exact generated files
These files come from the initializer itself. The tests below run real listeners; they are not illustrative pseudocode. The generated manifest uses the package metadata version; the installation step above pins the matching artifact or release.
package.json
{
"name": "redweb-app",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "tsc && node scripts/copy-assets.cjs",
"start": "node dist/app.js",
"dev": "nodemon",
"test": "npm run build && node --test test/app.test.cjs test/run-app.test.cjs",
"test:coverage": "npm run build && c8 --all --src=dist --include=dist/** --reporter=text --reporter=json node --test test/app.test.cjs test/run-app.test.cjs"
},
"dependencies": {
"redweb": "^0.13.3"
},
"devDependencies": {
"typescript": "^5.9.3",
"nodemon": "^3.1.11",
"ws": "^8.21.3",
"c8": "^10.1.3"
},
"nodemonConfig": {
"env": {
"REDWEB_DEV_REFRESH": "1"
},
"watch": [
"src",
"tsconfig.json"
],
"ext": "ts,tsx,css,html,json",
"exec": "npm run build && npm start || exit 1",
"delay": 200
}
}tsconfig.json
{
"extends": "redweb/tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"sourceMap": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}src/app.tsx
import { BaseHandler, HttpServer, METHODS, SocketRoute, SocketServer, type RedWebSocket, type SocketServerOptions } from 'redweb';
import { runApp } from './run-app';
export class Hello extends BaseHandler {
constructor() { super('hello'); }
onMessage(socket: RedWebSocket) {
socket.sendJson({ type: 'hello', message: 'Hello from the server!' });
}
}
export class ChatRoute extends SocketRoute {
constructor() {
super({ path: '/chat', handlers: [Hello], allowDuplicateConnections: true });
}
}
export function createApp(options: Pick<SocketServerOptions, 'port' | 'bind' | 'logger'> = {}) {
const http = new HttpServer({
listen: false,
publicPaths: [],
services: [{ serviceName: '/health', method: METHODS.GET, function: (_req, res) => res.json({ ok: true }) }],
});
return new SocketServer({
port: options.port ?? Number(process.env.PORT ?? 8181),
bind: options.bind ?? '127.0.0.1',
logger: options.logger,
server: http.server,
routes: [ChatRoute],
listen: true,
closeServerOnShutdown: true, // One owner closes routes and the shared HTTP listener.
});
}
if (require.main === module) runApp(createApp);src/run-app.ts
import type { Server } from 'node:http';
interface Application { server: Server; shutdown(): Promise<void>; }
/** Entry-point policy only: importing a recipe never installs process handlers. */
export function runApp<T extends Application>(createApp: () => T, shutdownTimeoutMs = 5000): T | undefined {
if (!Number.isInteger(shutdownTimeoutMs) || shutdownTimeoutMs < 1 || shutdownTimeoutMs > 2147483647) {
throw new RangeError('Application shutdown timeout must be a positive timer-safe integer.');
}
const fail = (message: string) => {
console.error(message);
if (Number(process.exitCode ?? 0) === 0) process.exitCode = 1;
};
let app: T;
try { app = createApp(); }
catch { fail('Application startup failed.'); return undefined; }
let closing: Promise<void> | undefined;
const stop = () => {
if (!closing) {
let failed = false;
const deadline = setTimeout(() => {
fail('Application cleanup exceeded its deadline; terminating the process.');
process.exit();
}, shutdownTimeoutMs);
closing = Promise.resolve().then(() => app.shutdown()).catch(() => {
failed = true;
fail('Application cleanup failed.');
}).finally(() => {
// Failed cleanup may leave live handles. Permit natural exit if none
// remain, but still force a bounded exit when resources were leaked.
if (failed) { deadline.unref(); return; }
clearTimeout(deadline);
process.off('SIGINT', stop);
process.off('SIGTERM', stop);
app.server.off('error', onError);
app.server.off('close', stop);
});
}
return closing;
};
const onError = () => { fail('Application listener failed.'); void stop(); };
// Persistent handlers keep repeated signals from bypassing active cleanup.
process.on('SIGINT', stop);
process.on('SIGTERM', stop);
app.server.on('error', onError);
// Native close can precede database/worker cleanup: it starts, never ends, shutdown.
app.server.once('close', stop);
return app;
}src/app.css
:root { color-scheme: dark; font-family: system-ui, sans-serif; background: #08090d; color: #fff; }
body { margin: 0; }
.home { width: min(42rem, calc(100% - 2rem)); margin: 18vh auto 0; }
h1 { font-size: clamp(2rem, 6vw, 4rem); line-height: 1.1; }
p { color: #bfc1ca; line-height: 1.6; }
button { padding: .8rem 1.2rem; background: #ff5064; color: #08090d; border: 0; border-radius: .5rem; cursor: pointer; font: inherit; }
button:focus-visible, a:focus-visible { outline: 3px solid #fff; outline-offset: 4px; }
nav { padding: 1rem; } a { color: #ff8795; }README.md
# Your Redweb application
Requirements: Node.js 18 or newer and npm for the realtime, chat, site, socket and http-ws templates; the dashboard template requires Node.js 22.13+ for native SQLite. Use a currently supported Node.js release in production.
For an unreleased checkout or tarball, first run `npm install --save-exact TARBALL`, replacing `TARBALL` with the absolute path to the same tested Redweb tarball used to generate this app (quote paths containing spaces). This installs the matching package and its published client dependency. Do not substitute an older registry release or `latest`. Published Redweb releases can use the installation command below directly.
```sh
npm install
npm test
npm run dev
```
HTTP starters open at http://localhost:8181; the authenticated dashboard uses http://127.0.0.1:8181/login and requires account provisioning described below. Set the `PORT` environment variable to change the listener.
`npm test` builds and runs real HTTP/WebSocket integration tests on an ephemeral loopback port. No mocks or external service are needed.
`npm run test:coverage` runs the same tests with application coverage mapped back to TypeScript. Reports are written to the ignored `coverage/` directory; this is separate from Redweb library coverage. TypeScript-generated decorator accessors can appear in function counts even when the framework does not call them. The report exposes remaining gaps; it does not certify complete application coverage. Source maps are generated during the build for diagnostics and coverage, but no coverage collector is loaded by `npm start`.
## Development and production
Edit `src/app.tsx`. `npm run dev` watches TypeScript, TSX, CSS, HTML, and the root TypeScript configuration,
then rebuilds and restarts the server. A type error stops startup until you fix it. On direct localhost access,
HTML pages refresh automatically when a new server revision is ready. If edits were detected, a keyboard-accessible
notice keeps the old document until you choose **Reload and discard drafts**. This is a conservative edit guard,
not autosave or browser hot-module replacement: restarts reset in-memory state and old socket sessions.
The generated development command sets `REDWEB_DEV_REFRESH=1`; `development: { refresh: false }` overrides it.
The refresh feature is refused under `NODE_ENV=production`, applies only to served HTML (not raw sockets or static exports),
and creates no local/session-storage copy of form contents. Use direct `localhost`, `127.x.x.x`, or `[::1]` access;
custom hostnames, tunnels and proxy-forwarded origins are not supported by this development helper.
`npm run build` checks types and copies CSS/HTML beside the compiled classes in `dist/`.
Run `npm start` to serve the compiled app. For deployment, build first, ship `dist/`, `package.json`, and the lockfile,
then install runtime dependencies with `npm ci --omit=dev`. The application does not require TypeScript or `src/` at runtime.
The standalone entrypoint calls the shared `runApp(createApp)` helper. Importing either module starts no listener and installs no process handlers. On SIGINT/SIGTERM, a listener error, or native listener closure, the helper calls application shutdown once. Repeated signals do not bypass cleanup. The five-second outer deadline covers the whole application, including database/worker cleanup after HTTP closes; customize it with the helper's second argument if necessary. Cleanup must resolve only after resources are released. A failed cleanup sets a failure exit status and retains a deadline for any surviving handles; the helper never resets an existing failure status. If cleanup does not finish in time, the entrypoint terminates the process with a failure status. This cannot preempt synchronous code blocking Node's event loop and does not make in-memory state durable. Factory functions remain responsible for releasing partially constructed resources before throwing.
The shipped lifecycle tests exercise actual processes, HTTP/TCP/WebSocket peers and timers. Linux uses actual OS signals; Windows tests explicitly emit signal events inside the process because killing a Windows child does not exercise graceful POSIX signal delivery. This is not a claim that Windows console/service managers forward the same signals. Deploy with a supervisor that forwards the supported termination signal and allows longer than the configured cleanup deadline.
For public deployment, configure HTTPS/WSS at your Node server or reverse proxy, authentication, trusted origins,
and application-specific rate limits. These starters are demonstrations, not a hosted identity or database service.
Never commit secrets; `.env` is ignored but is not loaded automatically.
`npx --no-install redweb doctor --json` reports configuration problems without changing your files.
## HTTP and WebSockets on one listener
One Node server answers ordinary HTTP requests and upgrades `/chat` connections to WebSockets. HTTP paths select Express services; a socket URL selects a route and each message's `type` selects a handler. No socket decorators or secondary `message.action` dispatcher are needed.
After starting the application, request `http://127.0.0.1:8181/health` to receive `{"ok":true}`. Connect a WebSocket to `ws://127.0.0.1:8181/chat` and send `{"type":"hello"}` to receive `{"type":"hello","message":"Hello from the server!"}`. This is a raw JSON socket example, not a chatroom UI or the versioned socket-contract protocol. Use the chat or socket starter for those applications.
The HTTP builder does not bind a port. The socket service explicitly takes responsibility for listening and closing the supplied server with `listen: true` and `closeServerOnShutdown: true`. Call the returned application's `shutdown()`; it processes route failures and still closes its HTTP/TCP peers. Do not separately close the HTTP builder. Importing this module creates no listener; the standalone entrypoint uses the same bounded `runApp` helper as the other starters.
`/health` reports liveness, not readiness or completed application work. Loopback binding is intentional. Before exposing the service, choose your deployment bind address, configure HTTPS/WSS, trusted origins, authentication, authorization, payload/connection limits, and any persistence you need. Shared listeners do not automatically provide these policies. Shutdown may force connections closed; it does not guarantee message delivery or durable work.
`npm test` runs real HTTP and WebSocket requests on ephemeral ports, checks strict socket routing and multiple clients, verifies idempotent cleanup with an incomplete HTTP peer, and proves a failing route cleanup still closes the listener. It also runs the shared process-lifecycle suite. No mocks are used. The package verification gate repeats the tests against the compiled application with `src/` unavailable..gitignore
node_modules/
dist/
coverage/
.env
data/
*.sqlite
*.sqlite-wal
*.sqlite-shm