Bring your own backend
Decide whether you need a backend, declare its contract, and control inherited or overridden environment origins.
A backend is your always-running HTTP application behind Farther Shore's gateway. You need one when a route must run your code: query your database, call a private provider, process a job, or calculate dynamic usage. You do not need one for platform-owned operations such as subscriptions, API keys, hosted portal data, or request counting at the gateway.
Three separate responsibilities
| Responsibility | Owner | How it changes |
|---|---|---|
| Logical backend and route binding | Repository | fs.backend() and route refs in business/ |
| Concrete origin for an environment | Platform operating state | farthershore backend create or backend bind |
| Application process and database | Your infrastructure | Deploy with your normal host and migration workflow |
fs.backend() is a contract declaration. It says which logical backend a route
uses, which transport it expects, whether verification is required, and which
meters it may report. An origin URL is environment-owned operating state and
is intentionally rejected in the Business SDK.
import * as fs from "@farthershore/business";
const requests = fs.requests();
const api = fs.backend("api", {
name: "Application API",
transport: { mode: "direct" },
verification: { required: true },
default: true,
});
const jobs = fs.route("/v1/jobs", {
get: { backend: api, requireMember: true },
post: { backend: api, requireMember: true },
});
fs.plan("starter", {
kind: fs.plan.kind.free,
grants: [jobs],
limits: [requests.perMinute(60)],
});
export default fs.business();
Verification defaults to required, so the explicit line above is useful for
readers but not necessary. A single declared backend is the implicit default.
With multiple backends, bind every route explicitly or mark exactly one backend
as default: true.
Build and push the repository change before binding traffic:
pnpm build
git push
farthershore apply-timeline list my-business
Create or bind the origin
Backends use a stable logical slug across environments. Previews inherit production backends by default; a preview row with a concrete target overrides only that slug in that environment. Concrete overrides and their runtime credentials remain per-environment.
This fallback applies only to the backend's concrete target. The preview's
plans, prices, routes, permissions, meters, limits, and policies always come
from the Business SDK compiled on its own env/* branch; none of that contract
state is inherited from production.
Published routes also address backends by this stable slug, rather than by an environment-specific database ID. The gateway resolves the slug against the effective backend set for the request environment, so binding an override takes effect without changing the Business program or duplicating routes.
Create a direct production backend and bind the origin in one operation:
farthershore backend create my-business \
--name "Application API" \
--slug api \
--transport direct \
--origin-url https://api.example.com \
--idempotency-key <persisted-backend-create-attempt-key> \
--default
Do nothing when the preview should use the production service. To override it, create the corresponding preview row before binding it:
farthershore backend create my-business \
--name "Application API" \
--slug api \
--env staging \
--idempotency-key <persisted-backend-create-attempt-key> \
--transport direct
farthershore backend bind my-business api \
--env staging \
--origin-url https://api-staging.example.com
backend create is useful when the row does not exist. backend bind updates a
direct backend's origin for one environment. A manifest-created preview row with
no target remains a placeholder and does not hide the usable production row.
Resolution is preview override, then production, by exact logical slug. It
never consults another preview. An environment-only backend with no usable
target fails closed with origin_unavailable (HTTP 503).
The same typed 503 origin_unavailable is what callers see whenever the origin
cannot be reached — while it is redeploying, stopped, or failing to resolve —
and 504 origin_timeout when it accepts the connection but never sends response
headers within the route's budget. Your hosting provider's own error page is
never relayed, so an outage is never mistaken for one of your application's
404s. Your backend's own JSON responses pass through untouched. See
response codes.
For tunnel transport, the platform provisions the origin hostname, so do not
pass --origin-url:
farthershore backend create my-business \
--name "Private API" \
--slug api \
--transport tunnel \
--runner embedded \
--idempotency-key <persisted-backend-create-attempt-key> \
--default
See Transport modes before choosing a tunnel.
Give the process a runtime token
The backend SDK bootstraps from FS_RUNTIME_TOKEN. For one deployment serving
the business across multiple environments, the simplest default is a
business-scoped token: omit both --env and --backend.
farthershore backend tokens create my-business --format json --idempotency-key <persisted-backend-tokens-create-attempt-key>
Store the one-time secret in your host's secret manager, restart or redeploy the service, then verify the binding:
farthershore backend list my-business --format json
farthershore backend tokens list my-business --format json
Use an environment-scoped or backend-scoped token only when the deployment boundary needs that narrower scope. The complete lifecycle is in Runtime tokens.
What the gateway does
For both direct and tunnel transport, the gateway:
- resolves the current environment and its compiled route;
- selects a concrete environment override or the inherited production backend;
- enforces the plan, limits, subject, and route permission;
- signs the exact method, path, query, raw body hash, route, backend, and verified principal context;
- forwards the request to the resolved origin.
Your process verifies that signature with @farthershore/backend before reading
identity or handling the request. Transport changes how packets reach the
process; it does not change the trust model.