From 0b722d505c2bb84e5bc1ad90ce33e440ffea8013 Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Wed, 15 Jul 2026 11:00:33 -0400 Subject: [PATCH 1/5] docs: add Multiple Applications on One Cluster guide Add a guide covering how to run multiple applications on a single Harper cluster: how components coexist, how to isolate them via routes, data, and roles, and how to deploy across every node. Co-Authored-By: Claude Opus 4.8 (1M context) --- learn/developers/multiple-applications.mdx | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 learn/developers/multiple-applications.mdx diff --git a/learn/developers/multiple-applications.mdx b/learn/developers/multiple-applications.mdx new file mode 100644 index 00000000..d05147d9 --- /dev/null +++ b/learn/developers/multiple-applications.mdx @@ -0,0 +1,118 @@ +--- +title: Multiple Applications on One Cluster +--- + +# Multiple Applications on One Cluster + +A single Harper cluster can host any number of applications side by side. Because the database, cache, application logic, messaging, and search run within one distributed runtime, an application is not a group of containers wired together—it is a [component](./), a self-contained module that Harper loads and serves. You add an application to a cluster by registering another component, not by provisioning a new cluster. + +This guide covers how to run multiple applications on one cluster: how they coexist, how to isolate them where required, and how to deploy them across every node. + +## Applications are components + +The unit you deploy in Harper is a component. In the [Operations API](../operations-api/components) and CLI, "component" refers to an application—a collection of schemas, resources, routes, and static assets that Harper loads and serves. + +Two consequences follow: + +- **A cluster is a shared resource.** To run a new application, register another component on an existing cluster rather than creating a new cluster. +- **Co-located applications share the runtime.** Because they run within the same converged process, one application's resources can access another application's tables through an in-process call—no cross-service HTTP and no separate connection pool. + +Co-located applications must be kept separate along three boundaries: **routes, data, and roles.** The sections below describe how to define each boundary. + +## Structuring each application + +Every Harper application is configured with a `config.yaml` file in the root of its directory. This file specifies which built-in extensions the application uses—`rest`, the `graphqlSchema` loader, custom JavaScript resources, Fastify routes, static file serving, and others: + +```yaml +# listings-api/config.yaml +rest: true +graphqlSchema: + files: '*.graphql' +jsResource: + files: 'resources.js' +roles: + files: 'roles.yaml' +fastifyRoutes: + files: 'routes/*.js' + urlPath: '/listings' +static: + files: 'web/**' + urlPath: '/listings' +``` + +A `config.yaml` **completely replaces** the default configuration; it is not merged with Harper's defaults. Each application therefore defines its own surface area explicitly. For the full list of options, see the [Component Configuration](../../reference/components/configuration) and [Built-In Extensions](../../reference/components/built-in-extensions) reference pages. + +### Namespacing routes + +Applications on the same instance respond on the same HTTP port (`9926` by default), so route collisions must be designed around. + +For [Fastify routes](./define-routes) and static assets, set a unique `urlPath` prefix per application—for example `/listings`, `/inventory`, or `/admin`. Custom routes and web content are then kept separate. + +REST endpoint paths are derived from the resource and table names an application exports, not from `urlPath`. Give each application uniquely named resources or, preferably, its own database. Two applications that export a `User` resource into the same database will conflict; two applications with tables in separate databases will not. + +## Isolating data + +Isolate co-located applications through the database. Assign each application its own database so its tables belong to it. Those tables are queryable by that application's resources and remain hidden from other applications unless a query deliberately reaches across. + +When one application needs data from another—for example, an `admin-dashboard` application reading from the `listings-api` application—it queries the table directly, in-process, rather than opening a network connection to another service. Data boundaries are defined per application. + +## Configuring authentication per application + +Each application can include its own [`roles.yaml`](./defining-roles) file, defining the roles and permissions that control access to its resources. Co-located applications do not share a permission model: a public-facing listings API and an internal admin tool can enforce different access rules on the same cluster. Define each application's roles around its own data; the runtime enforces them independently. + +## Registering multiple applications + +There are two ways to add applications to an instance. + +### Declaratively, through the instance config + +Harper reads applications from the `harperdb-config.yaml` file in its root path (usually `~/hdb`). Each entry points to a package—a GitHub repository, an npm package, a tarball, a local path, or a URL: + +```yaml +# ~/hdb/harperdb-config.yaml +listings-api: + package: my-org/listings-api#v1.4.0 +inventory-service: + package: my-org/inventory-service#v2.1.0 +admin-dashboard: + package: my-org/admin-dashboard#v0.9.0 +``` + +Reference a Git repository with a semver tag to lock each application to a specific, reproducible version. Harper translates these entries into a `package.json` file, runs an install to resolve them, and loads each as a component. The entry name is arbitrary and does not need to match a package dependency name. + +### Imperatively, through the CLI + +To deploy from an application directory, run `harperdb deploy` inside the project. This packages the current directory and sends it to the active instance: + +```bash +cd listings-api +harperdb deploy +``` + +For local iteration, `harperdb dev .` runs the application and watches for file changes, restarting worker threads on edit. See the [Applications reference](../../reference/components/applications) for the complete set of deployment options. + +## Deploying across the cluster + +The preceding sections deploy an application to a single instance. To deploy it across every node in the cluster, include the `replicated=true` parameter. + +When deploying in a clustered environment, set `replicated=true` to spread the deployment to all nodes: + +```bash +harperdb deploy_component \ + project=listings-api \ + package=https://github.com/my-org/listings-api#v1.4.0 \ + target=https://cluster-node-1.example.com:9925 \ + replicated=true +``` + +`target` points to a node's operations endpoint. `replicated=true` sends the deployment to the rest of the cluster, extending the "deploy everywhere" behavior of Harper's [replication](../replication/) to the application itself. + +> Restart afterward to apply the changes—for example, `harperdb restart target=https://cluster-node-1.example.com:9925 replicated=true`. + +On Harper Fabric, a single `harperdb deploy` from the project directory deploys the application across regions, with replication, routing, and failover managed by the platform. + +## When to co-locate + +Co-locate applications on one cluster when they share a data domain, benefit from in-process access to each other's tables, or are small enough that separate clusters would sit mostly idle. This covers common cases such as an API, its admin interface, a background worker, and a public site. + +Use separate clusters when applications require independent scaling, have tenancy or compliance requirements that mandate separation, or have significantly different availability needs. Choose based on the workload rather than defaulting to one cluster per service. From fa6a9921014353f6ead502f7ab1cfc1c4efb0449 Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Wed, 15 Jul 2026 11:04:36 -0400 Subject: [PATCH 2/5] docs: add Multiple Applications guide to Learn sidebar Co-Authored-By: Claude Opus 4.8 (1M context) --- sidebarsLearn.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sidebarsLearn.ts b/sidebarsLearn.ts index 23b906d7..ea1334d9 100644 --- a/sidebarsLearn.ts +++ b/sidebarsLearn.ts @@ -41,6 +41,11 @@ const sidebarsLearn: SidebarsConfig = { id: 'developers/harper-applications-in-depth', label: 'Harper Applications in Depth', }, + { + type: 'doc', + id: 'developers/multiple-applications', + label: 'Multiple Applications on One Cluster', + }, { type: 'doc', id: 'developers/caching-with-harper', From 1ea78475dadc4341038cb6184d3aa7f4eeeffb2e Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Wed, 15 Jul 2026 11:19:13 -0400 Subject: [PATCH 3/5] docs: address PR review on multiple-applications guide - Rename config file references to harper-config.yaml (was harperdb-config.yaml) - Use the harper CLI consistently (was harperdb) - Convert the restart note to a :::note admonition - Fix broken doc links to resolve to /reference/v5/ targets Co-Authored-By: Claude Opus 4.8 (1M context) --- learn/developers/multiple-applications.mdx | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/learn/developers/multiple-applications.mdx b/learn/developers/multiple-applications.mdx index d05147d9..681e4ca9 100644 --- a/learn/developers/multiple-applications.mdx +++ b/learn/developers/multiple-applications.mdx @@ -4,13 +4,13 @@ title: Multiple Applications on One Cluster # Multiple Applications on One Cluster -A single Harper cluster can host any number of applications side by side. Because the database, cache, application logic, messaging, and search run within one distributed runtime, an application is not a group of containers wired together—it is a [component](./), a self-contained module that Harper loads and serves. You add an application to a cluster by registering another component, not by provisioning a new cluster. +A single Harper cluster can host any number of applications side by side. Because the database, cache, application logic, messaging, and search run within one distributed runtime, an application is not a group of containers wired together—it is a [component](/reference/v5/components/overview), a self-contained module that Harper loads and serves. You add an application to a cluster by registering another component, not by provisioning a new cluster. This guide covers how to run multiple applications on one cluster: how they coexist, how to isolate them where required, and how to deploy them across every node. ## Applications are components -The unit you deploy in Harper is a component. In the [Operations API](../operations-api/components) and CLI, "component" refers to an application—a collection of schemas, resources, routes, and static assets that Harper loads and serves. +The unit you deploy in Harper is a component. In the [Operations API](/reference/v5/operations-api/overview) and CLI, "component" refers to an application—a collection of schemas, resources, routes, and static assets that Harper loads and serves. Two consequences follow: @@ -40,13 +40,13 @@ static: urlPath: '/listings' ``` -A `config.yaml` **completely replaces** the default configuration; it is not merged with Harper's defaults. Each application therefore defines its own surface area explicitly. For the full list of options, see the [Component Configuration](../../reference/components/configuration) and [Built-In Extensions](../../reference/components/built-in-extensions) reference pages. +A `config.yaml` **completely replaces** the default configuration; it is not merged with Harper's defaults. Each application therefore defines its own surface area explicitly. For the full list of options, see the [Component Configuration](/reference/v5/components/overview#configuration) and [Built-In Extensions](/reference/v5/components/overview#built-in-extensions-reference) reference pages. ### Namespacing routes Applications on the same instance respond on the same HTTP port (`9926` by default), so route collisions must be designed around. -For [Fastify routes](./define-routes) and static assets, set a unique `urlPath` prefix per application—for example `/listings`, `/inventory`, or `/admin`. Custom routes and web content are then kept separate. +For [Fastify routes](/reference/v5/fastify-routes/overview) and static assets, set a unique `urlPath` prefix per application—for example `/listings`, `/inventory`, or `/admin`. Custom routes and web content are then kept separate. REST endpoint paths are derived from the resource and table names an application exports, not from `urlPath`. Give each application uniquely named resources or, preferably, its own database. Two applications that export a `User` resource into the same database will conflict; two applications with tables in separate databases will not. @@ -58,7 +58,7 @@ When one application needs data from another—for example, an `admin-dashboard` ## Configuring authentication per application -Each application can include its own [`roles.yaml`](./defining-roles) file, defining the roles and permissions that control access to its resources. Co-located applications do not share a permission model: a public-facing listings API and an internal admin tool can enforce different access rules on the same cluster. Define each application's roles around its own data; the runtime enforces them independently. +Each application can include its own [`roles.yaml`](/reference/v5/users-and-roles/configuration) file, defining the roles and permissions that control access to its resources. Co-located applications do not share a permission model: a public-facing listings API and an internal admin tool can enforce different access rules on the same cluster. Define each application's roles around its own data; the runtime enforces them independently. ## Registering multiple applications @@ -66,10 +66,10 @@ There are two ways to add applications to an instance. ### Declaratively, through the instance config -Harper reads applications from the `harperdb-config.yaml` file in its root path (usually `~/hdb`). Each entry points to a package—a GitHub repository, an npm package, a tarball, a local path, or a URL: +Harper reads applications from the `harper-config.yaml` file in its root path (usually `~/hdb`). Each entry points to a package—a GitHub repository, an npm package, a tarball, a local path, or a URL: ```yaml -# ~/hdb/harperdb-config.yaml +# ~/hdb/harper-config.yaml listings-api: package: my-org/listings-api#v1.4.0 inventory-service: @@ -82,14 +82,14 @@ Reference a Git repository with a semver tag to lock each application to a speci ### Imperatively, through the CLI -To deploy from an application directory, run `harperdb deploy` inside the project. This packages the current directory and sends it to the active instance: +To deploy from an application directory, run `harper deploy` inside the project. This packages the current directory and sends it to the active instance: ```bash cd listings-api -harperdb deploy +harper deploy ``` -For local iteration, `harperdb dev .` runs the application and watches for file changes, restarting worker threads on edit. See the [Applications reference](../../reference/components/applications) for the complete set of deployment options. +For local iteration, `harper dev .` runs the application and watches for file changes, restarting worker threads on edit. See the [Applications reference](/reference/v5/components/applications) for the complete set of deployment options. ## Deploying across the cluster @@ -98,18 +98,20 @@ The preceding sections deploy an application to a single instance. To deploy it When deploying in a clustered environment, set `replicated=true` to spread the deployment to all nodes: ```bash -harperdb deploy_component \ +harper deploy_component \ project=listings-api \ package=https://github.com/my-org/listings-api#v1.4.0 \ target=https://cluster-node-1.example.com:9925 \ replicated=true ``` -`target` points to a node's operations endpoint. `replicated=true` sends the deployment to the rest of the cluster, extending the "deploy everywhere" behavior of Harper's [replication](../replication/) to the application itself. +`target` points to a node's operations endpoint. `replicated=true` sends the deployment to the rest of the cluster, extending the "deploy everywhere" behavior of Harper's [replication](/reference/v5/replication/overview) to the application itself. -> Restart afterward to apply the changes—for example, `harperdb restart target=https://cluster-node-1.example.com:9925 replicated=true`. +:::note +Restart afterward to apply the changes—for example, `harper restart target=https://cluster-node-1.example.com:9925 replicated=true`. +::: -On Harper Fabric, a single `harperdb deploy` from the project directory deploys the application across regions, with replication, routing, and failover managed by the platform. +On Harper Fabric, a single `harper deploy` from the project directory deploys the application across regions, with replication, routing, and failover managed by the platform. ## When to co-locate From 3830c92a297e19c0beb3c0d57af566913c16d9ae Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Wed, 15 Jul 2026 14:27:02 -0400 Subject: [PATCH 4/5] docs: expand multiple-applications routing + isolation (PR review) Address maintainer review requesting fuller routing and isolation coverage: - Make routing the backbone: new "Routing requests to each application" section covering path-based routing (urlPath), the server.http() middleware chain, and host-based routing via request.host - Add "What co-located applications share" section documenting the module loader context isolation model (isolated module caches, shared process-wide data/APIs, shared process lifecycle) - Note SNI certificate configuration for serving multiple domains Co-Authored-By: Claude Opus 4.8 (1M context) --- learn/developers/multiple-applications.mdx | 58 +++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/learn/developers/multiple-applications.mdx b/learn/developers/multiple-applications.mdx index 681e4ca9..354511f0 100644 --- a/learn/developers/multiple-applications.mdx +++ b/learn/developers/multiple-applications.mdx @@ -6,7 +6,7 @@ title: Multiple Applications on One Cluster A single Harper cluster can host any number of applications side by side. Because the database, cache, application logic, messaging, and search run within one distributed runtime, an application is not a group of containers wired together—it is a [component](/reference/v5/components/overview), a self-contained module that Harper loads and serves. You add an application to a cluster by registering another component, not by provisioning a new cluster. -This guide covers how to run multiple applications on one cluster: how they coexist, how to isolate them where required, and how to deploy them across every node. +This guide covers how to run multiple applications on one cluster: how they coexist, how requests are routed to each one, how to isolate them where required, and how to deploy them across every node. ## Applications are components @@ -17,7 +17,17 @@ Two consequences follow: - **A cluster is a shared resource.** To run a new application, register another component on an existing cluster rather than creating a new cluster. - **Co-located applications share the runtime.** Because they run within the same converged process, one application's resources can access another application's tables through an in-process call—no cross-service HTTP and no separate connection pool. -Co-located applications must be kept separate along three boundaries: **routes, data, and roles.** The sections below describe how to define each boundary. +Harper isolates each application's code automatically (see [What co-located applications share](#what-co-located-applications-share)), but you control how they expose functionality and reach one another along three boundaries: **routing, data, and roles.** The sections below describe how to define each boundary. + +## What co-located applications share + +Harper runs as a single process. Every co-located application shares that process and its worker threads, so it is worth being precise about what is isolated between applications and what is not. + +- **Module contexts are isolated.** Harper loads each application's JavaScript in its own module context using Node.js's VM module loader, giving every application a distinct module cache. One application's modules, imports, and module-scoped state are not visible to another, so two applications can depend on different packages—or different versions of the same package—without colliding. +- **The data layer and Harper APIs are shared.** The objects you reach through the `harper` package or as globals—`tables`, `databases`, and the rest—are the same live, process-wide objects in every application. A record written by one application is immediately visible to every other, and any application can read another's tables in-process. This is what makes co-location efficient, and it is why [data isolation](#isolating-data) is a deliberate choice rather than an automatic one. +- **The process is shared.** Because every application runs in one process, operational actions apply to all of them: restarting the instance restarts every co-located application, and applications cannot change the process working directory. Plan restarts and deployments with the whole instance in mind. + +For the full model, see the [JavaScript Environment](/reference/v5/components/javascript-environment) reference. ## Structuring each application @@ -42,13 +52,49 @@ static: A `config.yaml` **completely replaces** the default configuration; it is not merged with Harper's defaults. Each application therefore defines its own surface area explicitly. For the full list of options, see the [Component Configuration](/reference/v5/components/overview#configuration) and [Built-In Extensions](/reference/v5/components/overview#built-in-extensions-reference) reference pages. -### Namespacing routes +## Routing requests to each application + +Applications on the same instance share a single HTTP listener and port (`9926` by default). Every request enters one layered middleware chain, and routing determines which application handles it. You can route on two dimensions: **URL path** and **host**. + +### Path-based routing + +Path prefixes are the primary way to keep applications separate. Each built-in extension that serves HTTP—`fastifyRoutes`, `static`, and others—accepts a `urlPath` that mounts its files under a URL prefix. Give each application a unique prefix such as `/listings`, `/inventory`, or `/admin`, as in the `config.yaml` above. + +When `urlPath` is `.` or begins with `./`, Harper prepends the component name automatically, so an application named `listings-api` mounts under `/listings-api` by default. Set an explicit prefix to control the path yourself. You can also override the mount path at deploy time with the `urlPath` parameter to `deploy_component` (for example, `urlPath=/api/v2`). -Applications on the same instance respond on the same HTTP port (`9926` by default), so route collisions must be designed around. +REST endpoint paths behave differently: they are derived from the resource and table names an application exports, not from `urlPath`. Give each application uniquely named resources or, preferably, its own database. Two applications that export a `User` resource into the same database will conflict; two applications with tables in separate databases will not. -For [Fastify routes](/reference/v5/fastify-routes/overview) and static assets, set a unique `urlPath` prefix per application—for example `/listings`, `/inventory`, or `/admin`. Custom routes and web content are then kept separate. +### The middleware chain + +Harper processes every HTTP request through a layered middleware chain. Components add handlers with the [`server.http()`](/reference/v5/http/api#serverhttplistener-options) API. Handlers run in order; each either returns a `Response` to handle the request or calls `next(request)` to pass it to the next handler: + +```js +server.http( + (request, next) => { + if (request.pathname.startsWith('/listings')) return handleListings(request); + return next(request); + }, + { runFirst: true } +); +``` + +`runFirst: true` inserts the handler at the front of the chain. Reach for the middleware chain when path prefixes and resource naming are not enough—for example, to dispatch on a custom header or rewrite a path before it reaches an application. See the [HTTP API](/reference/v5/http/api) reference for the full `Request` object. + +### Host-based routing + +To route by hostname instead of path—serving `listings.example.com` and `admin.example.com` from the same instance—branch on the `host` property of the request inside a middleware handler: + +```js +server.http( + (request, next) => { + if (request.host === 'admin.example.com') return handleAdmin(request); + return next(request); + }, + { runFirst: true } +); +``` -REST endpoint paths are derived from the resource and table names an application exports, not from `urlPath`. Give each application uniquely named resources or, preferably, its own database. Two applications that export a `User` resource into the same database will conflict; two applications with tables in separate databases will not. +When serving multiple domains over HTTPS, configure a certificate per domain with [SNI](/reference/v5/http/tls#multi-domain-certificates-sni): define `tls` as an array with a `host` entry for each domain. SNI selects the certificate; the middleware handler selects the application. ## Isolating data From 54935e9cb06c4a9786fd05331289ccc38f50f6eb Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Fri, 17 Jul 2026 11:13:04 -0400 Subject: [PATCH 5/5] docs: make middleware example match its prose (PR review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The middleware-chain example described dispatching on a custom header but showed a path-prefix check — a case path-based routing already covers. Switch the example to a custom-header check so it demonstrates what the surrounding text actually recommends the chain for. Co-Authored-By: Claude Opus 4.8 (1M context) --- learn/developers/multiple-applications.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/learn/developers/multiple-applications.mdx b/learn/developers/multiple-applications.mdx index 354511f0..ab74b3aa 100644 --- a/learn/developers/multiple-applications.mdx +++ b/learn/developers/multiple-applications.mdx @@ -71,14 +71,14 @@ Harper processes every HTTP request through a layered middleware chain. Componen ```js server.http( (request, next) => { - if (request.pathname.startsWith('/listings')) return handleListings(request); + if (request.headers.get('x-app-target') === 'listings') return handleListings(request); return next(request); }, { runFirst: true } ); ``` -`runFirst: true` inserts the handler at the front of the chain. Reach for the middleware chain when path prefixes and resource naming are not enough—for example, to dispatch on a custom header or rewrite a path before it reaches an application. See the [HTTP API](/reference/v5/http/api) reference for the full `Request` object. +`runFirst: true` inserts the handler at the front of the chain. Reach for the middleware chain when path prefixes and resource naming are not enough—for example, to dispatch on a custom header, as above, or to rewrite a path before it reaches an application. See the [HTTP API](/reference/v5/http/api) reference for the full `Request` object. ### Host-based routing