Lesson 30 — Apps: publishing a fn as a public site
Goal: by the end of this lesson you can take a fn that
produces a web response and put it live on the public internet at
https://<label>.graphden.app — and point your own custom domain
at it. You'll understand how a graphden app differs from the
editor, and why your app code runs in a sandbox.
Concepts introduced: an app (a fn served on the web), the
per-fn Apps popover (the ▣ row action), a named app (the
:app-route behind it), the apps-domain (graphden.app) vs the
editor domain, the globally-unique label, custom domains, and
the FaaS sandbox your app runs in.
This lesson assumes you can compose a fn that returns a web
response — see Lesson 10 (Composing pages from
components), whose :html-page /
:router fns are exactly what an app serves.
What an app is
An app is nothing new — it's a fn you already know how to build,
one whose return value is an HTTP response. The same
:html-page you rendered in Lesson 10, or a :router that
dispatches on the request path, IS an app the moment you give it a
public address. graphden runs it for you on each request; you
never run a web server yourself.
Concretely, an app's handler fn is called with the incoming
request as a free argument ({:request …}) and returns a Ring
response (a status/headers/body map — an :html-page produces one
for you). If you want more than one page, serve a :router that
picks a handler by path, exactly like the editor itself does.
Apps in the editor
On a cloud deployment (the tenancy addon is active) publishing starts from the fn, the same way declaring a service does: the ⋯ menu on a selected fn's root row has an Apps entry (▣). Creating and removing routes requires manage-apps access; an organization owner holds it implicitly. Its popover lists the hosts this fn already serves — each a live link, with a × to remove it — and a form to add a new one.
On a single-tenant self-hosted instance there is no ▣ entry — apps are a cloud concept, and a self-hoster simply points their own reverse proxy at the fn they want to serve.
Apps also show up in the Explorer itself: a fn that serves an app carries a ▣ marker on its tree row, and the apps chip under the sidebar search (Lesson 20) narrows the tree to just those fns — that chip is the org-wide overview of everything published. The chip shows a count and appears only when the deployment has app routing at all.
The interactive lesson needs an account browser session and an isolated apps
domain as well as manage-apps access. It extends html-ok-response, binds
:body to <h1>Hello from my app</h1> and records the exact newly created app
UUID. Open ⋯ → HTTP → Preview HTML handler → Open HTML preview to read
that page from the lesson's captured scratch branch, as in lesson 14.
Finish offers to keep the app and branch or remove the exact app before
its handler and branch. An unavailable or changed route keeps the cleanup
journal for retry.
Ordinary app requests resolve the organization's main graph. To publish the reviewed handler there, use the existing branch merge controls from lesson 23, then open the ordinary app host. This later publication is deliberate: a merge into real main retains source history, while the lesson's preview and cleanup stay reversible. Creating an app label alone does not publish scratch-branch versions into main.
Creating an app
In the fn's ▣ popover, the + Add app form asks for one thing:
a subdomain label — the <label> in <label>.graphden.app.
It must be a DNS-safe subdomain (lower-case letters, digits and
hyphens), it can't be a reserved platform label (www, api,
app, …), and it is globally unique: the apps-domain is one
flat namespace, so shop is claimed by whoever takes it first
(like a project name on a deployment host). Pick something
specific to you.
The fn it serves is the one you opened the popover on — nothing to type or copy. Submit, and the host appears in the list. That's the whole deployment: no build step, no container, no restart — the app is live the instant the row is written. To serve the same host from a different fn, remove it here and add it on the other fn.
Your app's address
The app is now served at:
https://<label>.graphden.app
Note the domain: apps live on graphden.app, deliberately a
different domain from the editor's graphden.dev. That
separation is a security boundary — your app runs your org's code
and is its public face, so it must not share an origin (and thus
cookies / your editor token) with the editor. It's the same reason
GitHub serves user content from githubusercontent.com, not
github.com.
Custom domains
A <label>.graphden.app address is the free default. For a real
site you'll want your own domain — shop.acme.com. Three steps, on
two sides:
- Operator side — the hostname is registered for your org by
the platform operator (
POST /api/domains; a:domainrow is tenant-forbidden, so there is no self-serve form). Ask for it; the row starts unverified. - Your side, at your DNS provider — add a
TXTrecord on that hostname readinggraphden-verify=<your-org>. - Your side, against graphden — call
POST /api/my-app/verify-domainwithhostname=shop.acme.com(an authenticated request; no panel yet). Graphden looks the record up, refuses a hostname registered to another org, and flips the row to verified. From then on the hostname serves the same app.
An unverified domain never routes — you can't hijack a name you don't control.
The sandbox (why your app can't do everything)
Your app runs inside graphden's sandbox (this is the "FaaS" — functions-as-a-service — model). On each request the handler executes effect-gated: it may read and compose graph data and reach the integrations your plan allows, but it cannot touch the server's files, environment, or spawn processes — the same effect gate you met in Lesson 16 (Effects and secrets), now guarding the public entry point. It's also time-bounded, so a runaway handler can't wedge the platform.
The payoff: you deploy a fn, not a server, and the platform keeps it safe, isolated per-org, and always-current with your graph.
Removing an app
The × next to a host in the fn's ▣ popover deletes it (with a confirm). The subdomain and any custom domain stop serving immediately; the fn it pointed at is untouched — you only removed the routing, not your code.
Recap
- An app is a fn that returns a web response, given a public address.
- The fn's ▣ Apps action maps a globally-unique label →
<label>.graphden.app→ the fn you opened it on; custom domains point at the same app once DNS-verified. The apps filter is the org-wide overview. - Apps live on
graphden.app, isolated from the editor'sgraphden.devorigin, and run in the effect-gated FaaS sandbox. - Deploying, re-pointing, and removing an app are all single actions with no build or restart.
Next: lesson 31 — Packages. The flip side of the domain model — where your editor lives when you belong to more than one org — is lesson 33.