Express wired up: /api prefix, /config endpoint, bundled static frontend

Commit `acbb6cc7` ties the fork's backend together. Every router gets mounted under `/api/*`, two roots (`/install` and `/config`) stay at the top level, and a static frontend block lets a Next.js static export be served from the same process. A second commit (`6697186c`) adds the full `.env.example` and supporting chore files.

multi-tenantinfrastructure

Maintained by Allen Morgan · verified on MikeWatch

The /api prefix is a breaking change relative to upstream - all client URLs need updating. The rationale is that the same Express process now serves the static frontend bundle from public/ when PUBLIC_DIR exists at startup, so API routes have to live under a distinct prefix to avoid colliding with frontend paths. When PUBLIC_DIR is absent, the static handler is skipped and the frontend dev server at :3000 talks to the backend over CORS as before.

The static-serving block handles Next.js output: "export" quirks. findShell() resolves dynamic-segment routes like /projects/<id> back to the _-placeholder HTML file that Next emits for parameterized routes. It tries the literal path first, then substitutes the last segment with _, then the second-to-last, and so on. RSC .txt requests for unmapped routes return 404 so Next falls back to a hard navigation rather than getting stuck.

GET /config returns { authProvider, entra: { tenantId, clientId } } from server env variables, unauthenticated. The intent is one Docker image that ships to any tenant - the frontend calls this before rendering the login screen so it knows which auth flow to offer.

trust proxy is set to true because Container Apps terminates TLS at the ingress layer and forwards plain HTTP to the container. Without this, req.protocol reports http even when the browser is on https://, which breaks OAuth redirect URI construction.

The .env.example in 6697186c documents the full env surface across all providers.

So what The `/api` prefix shift is a non-trivial migration if you're importing this selectively - clients need to be updated in sync. The `/config` endpoint is a clean pattern to borrow if you need one image to work across multiple tenant configurations. The `findShell()` static serving logic is specific to Next.js `output: "export"` and is only useful with that setup.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

2 commits from Altien/mikeOssAzure, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
acbb6cc7 feat(server): runtime /config + /api prefix + bundled static frontend Allen Morgan 2026-05-08 ↗ GitHub
commit body
Wires every router added in earlier commits into the Express app and
adds the runtime-config endpoint that the browser bundle reads at
startup.

  * routes/config.ts mounted at /config - returns
    { authProvider, entra: { tenantId, clientId } } from server env.
    Unauthenticated; lets the same Docker image ship to any tenant.

  * /api prefix shift - every existing API router moves under /api/* so
    the backend can also serve a static frontend bundle from the same
    process (the SPA shell at / would otherwise collide).
    /install and /config keep their current paths because they have
    independent meaning at the root.

  * Static frontend bundle.  When PUBLIC_DIR (../public relative to
    dist/index.js) exists, the backend serves the Next.js static export
    out of the same Express process.  findShell() resolves dynamic-
    segment routes like /projects/<id> back to the right shell file
    that Next.js export emits with `_` placeholders.  RSC `.txt`
    requests for unmapped routes 404 cleanly so Next falls back to a
    hard navigation.  When PUBLIC_DIR is absent (local backend dev),
    the static handler is skipped and the frontend dev server at :3000
    talks to this backend over CORS as before.

  * trust-proxy + cookie-parser + urlencoded body parser added to
    support OAuth state cookies, the configurator's signed sessions,
    and the bootstrap-token paste form respectively.
6697186c chore(backend): env example, dockerignore, regenerate lockfile Allen Morgan 2026-05-08 ↗ GitHub
commit body
  * backend/.env.example                - full set of env vars including
                                          AUTH_PROVIDER block (local /
                                          supabase / entra), Azure
                                          storage block, OpenAI/AOAI,
                                          download-signing secret,
                                          observability connection
                                          string.  Sanitized - concrete
                                          identifiers replaced with
                                          placeholders.

  * backend/.dockerignore               - standard Node ignore set so
                                          docker build doesn't blow up
                                          the image with node_modules.

  * backend/package-lock.json           - regenerated to match the
                                          dependency set added across
                                          earlier commits (azure SDKs,
                                          pg / node-pg-migrate, openai,
                                          cookie-parser).

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-198.md from inside the repo you want the changes in.

⬇ Download capture-thread-198.md