A multi-tenant portal platform composed of independent services unified by a single theme. API-first, HTMX-first, schema-validated.
BetterPortal is a protocol first, a Node SDK second. The wire protocol (HTTP + HTMX + JSON) is specified in spec/ so services and themes can be implemented in any language. The reference Node services use BSB (Better Service Base). Standalone .NET and Python SDKs are in development and remain incomplete and unpublished. See route authoring for the three runtime workflows.
BetterPortal lets you compose a tenant-aware web portal out of small, independent services that each own their own data, config, and views. A shell service renders the host page; HTMX swaps content from each business service directly in the browser. There is no client-side framework, no proxy layer, no shared monolith.
+-------------+
| Browser |
+--+--+---+---+
shell + nav | | | HTMX fragments / SSE
+---------+ | | (CORS, direct)
v | v
+--------------+ | +-------------+
| Theme(s) | | | Service A |
| port 3100 | | | port 3200 |
| bootstrap1 | | +-------------+
+--------------+ | +-------------+
+-->| Service B |
| port 3300 |
+-------------+
Theme serves the HTML shell + nav/style/brand/fragment-list endpoints.
Each service is its own origin; the browser calls them directly via
HTMX. The theme NEVER proxies content from a service.
- Microservices without microservice pain. Each service is its own deployable, but the portal feels like one application to the user.
- No SPA framework. Server-rendered HTML. HTMX swaps fragments in. State lives in the URL, server, and DOM. That's all.
- No iframes. Composition is fragment swaps, not nested browsing contexts.
- Schema-validated everywhere. Inputs and outputs go through anyvali. Nothing untyped crosses a boundary.
- File-based routing. Drop a folder under
bp-routes/- it becomes a route, validated and themed. - Per-tenant config. Encrypted secrets, scoped sync, admin UI.
PHP port work starts with the experimental native protocol primitives. They cover negotiation, SSE framing and BP header directives; PHP service hosting remains pending.
| Layer | Where | What |
|---|---|---|
| Protocol | spec/ |
Normative HTTP + HTMX + JSON contracts. Any language can implement. |
| Node SDK | framework/nodejs/, plugins/nodejs/betterportal-bsb/ |
Reference TypeScript implementation of the protocol, built on BSB. |
| Reference services | services/nodejs/auth-*/, themes/nodejs/, services/nodejs/admin/config-manager/ |
Concrete services built with the Node SDK. |
| Examples | services/nodejs/examples/hello-view/ |
A minimal Node service demonstrating views, fragments, SSE. |
When the SDK and the spec disagree, the spec wins - file a bug.
BetterPortal/
+-- spec/ # NORMATIVE protocol specification (language-neutral)
+-- bp-config.yaml # platform config (tenants/apps/routes/services/menu/fragments)
+-- framework/nodejs/ # Node SDK: contracts, runtime, codegen, h3 adapter
+-- framework/go/ # experimental Go protocol primitives (hosting pending)
+-- plugins/nodejs/betterportal-bsb/ # BSB <-> Node SDK integration (BPService base class)
+-- themes/nodejs/
| +-- runtime/ # shared backend-built HTMX shell runtime
| +-- bootstrap1/ # reference theme: Bootstrap 5 + HTMX shell
| +-- embedded/ # reference theme: lightweight embedded renderer
+-- services/nodejs/
| +-- auth-default/ # optional JWT auth platform service
| +-- auth-authress-io/ # optional auth platform service (Authress)
| +-- auth-workos/ # optional auth platform service (WorkOS)
| +-- registry/ # BP contract / plugin registry
| +-- docs-site/ # rendered docs service
| +-- examples/hello-view/ # example business service (clock SSE, profile fragment, showcase)
| +-- admin/config-manager/ # admin UI: tenants, services, routes, menu, fragments, preview
+-- automations/ # n8n / node-red / zapier integration packages
+-- llms.txt # detailed developer guide (humans + LLMs)
+-- docs/ # user guides, route authoring, operations and reference
npm install
# build (workspace-aware)
npm run -ws build
# in three terminals (or background, your call):
cd themes/nodejs/bootstrap1 && npm start # http://localhost:3100
cd services/nodejs/admin/config-manager && npm start # http://localhost:3300
cd services/nodejs/examples/hello-view && npm start # http://localhost:3200
# wait for /.well-known/bp/manifest on each, then open http://localhost:3100The default bp-config.yaml ships with the betterportal tenant + betterportal-web app pre-configured. The sidebar menu exposes the showcase routes (hello-view) and admin tools (config-manager).
Go port work starts with the experimental native protocol primitives. They cover negotiation, SSE framing and BP header directives; Go service hosting remains pending.
- Scaffold a directory under
services/nodejs/<category>/<name>/(mirrorservices/nodejs/examples/hello-view/). - Declare the plugin in
src/plugins/<plugin-name>/index.tsextendingBPServicefrom@betterportal/plugin-bsb. - Drop routes under
bp-routes/<routeDir>/. Put metadata inindex.ts, method handlers inGET.ts/POST.ts, and HTML in_renderer.<renderer>/GET.tsx. - Run
npx bp-codegen(regenerates.bp-generated/registry.ts). npm run build && npm start- visit/.well-known/bp/manifest.- Register in
bp-config.yamlunder a tenant'sservices[](or as a platform service) and bind a route inapps[].routes[].
The full developer guide lives in llms.txt - written for LLM agents but equally useful for humans new to the codebase. It covers file conventions, HTMX patterns, design constraints, the auth model, SSE, and codegen in detail.
| Concept | What it is |
|---|---|
| Tenant | Isolation boundary; owns services + branding. |
| App | A site under a tenant; has a shell, routes, menu, fragments. |
| Service | BSB plugin exposing typed views + manifest, registered under a tenant. |
| Platform service | Shared cross-tenant service (e.g., auth); tenants opt in. |
| Route | path -> service + view + targetPath binding in an app. |
| View | bp-routes/<dir>/index.ts metadata plus method handlers. HTML renderers live alongside in _renderer.<renderer>/METHOD.tsx. |
| Fragment | HTML island at a named location (nav, footer). File: _<location>.<id>.GET.tsx. Optional SSE renderer: _<location>.<id>.sse.tsx. |
| Menu | Per-app tree (groups + links) driving the sidebar nav. |
- No iframes.
- No SPA framework, no client-side router, no client-side state library, no client-side data-fetching library.
- Server never emits absolute URLs - client rewrites root-relative
/footo the correct service origin viadata-bp-service. - HTML-as-API. Services emit HTML fragments; the browser inserts them.
- Cookies are theme-origin only. Auth tokens travel via
hx-headers, never cross-origin cookies. - Codegen is mandatory. Hand-written registries are not supported.
See llms.txt section 1b for the full list.
| Package | Purpose |
|---|---|
@betterportal/framework |
Contracts, runtime, codegen CLI (bp-codegen), h3 adapter, schema helpers. |
@betterportal/plugin-bsb |
BPService base class; wires h3, CORS, observability into BSB. |
@betterportal/theme-runtime |
Shared backend-assembled HTMX shell, BP headers, preload, URL rewriting, generic route chrome lifecycle, SSE, and typed TSX shell adapter contract. |
@betterportal/theme-bootstrap1 |
Default theme: Bootstrap 5 + HTMX shell, theme designer, nav/brand/style/fragment refresh endpoints. |
@betterportal/theme-embedded |
Lightweight embedded renderer for headless / external embeds. |
@betterportal/auth-default |
Optional JWT auth platform service. |
@betterportal/auth-authress-io |
Optional auth platform service backed by Authress. |
@betterportal/auth-workos |
Optional auth platform service backed by WorkOS. |
@betterportal/config-manager |
Admin UI for tenants, services, routes, menu, fragments, preview. |
@betterportal/registry |
BP contract / plugin registry service. |
@betterportal/docs-site |
Rendered documentation service. |
@betterportal/hello-view |
Example business service. |
All packages live in this repo as a single npm workspace. Versioning is unified.
Pre-release. APIs are stabilizing. Expect breaking changes between minor versions until 1.0.
PRs welcome. Before opening one:
- Read
llms.txtend-to-end - especially section 1b Design constraints. - Run
npm run build,npm run lint, andnpm testfrom the repository root. CI requires all three to pass. - New routes require
npx bp-codegenbefore commit. - Don't break the no-iframes / no-SPA / no-absolute-URLs rules.
The shared ESLint configuration checks authored JavaScript, TypeScript, TSX, and tests; generated code, build outputs, and dependencies are excluded. npm run lint:fix applies available safe automatic fixes. Warnings and unused suppression directives fail CI.
Use TSDoc for new or changed public APIs and non-obvious lifecycle, concurrency, and security behavior. Explain guarantees, failure behavior, and ownership; add @param, @returns, and @throws where they clarify the contract. Avoid comments that just repeat a name or type. The TSDoc syntax rule validates existing documentation syntax; documentation coverage remains a review requirement.
Keep any necessary ESLint suppression on the narrowest scope and include a -- reason. Existing explicit any at schema and HTMX boundaries remains allowed; lint does not replace the TypeScript build or runtime tests. No formatting preset or type-aware lint rules are enabled.
Dual-licensed:
- GNU AGPL-3.0-only - for open-source use. If you run a modified BetterPortal as a network service, you must offer the modified source to your users.
- Commercial license - for use without AGPL obligations. Contact BetterCorp.
See LICENSE for the full AGPL text and package.json for SPDX identifiers.