[data-reveal]{opacity:1!important;transform:none!important}
Routing and the backend proxy
Frontend

Routing and the backend proxy

The order in which the frontend server picks the owner of a path, how modules declare backend prefixes, and what the proxy adds and relays.

The frontend server answers each path with one owner, whoever asks and whatever the method. It looks for the owner in this order.

Who answers a path

The renderer

Its icon endpoint (/api/_dms/icons), Vite's URLs, the built assets, and any file it has: a development source, a built asset, a module's public/ file.

In production, a path with a segment starting with a dot is never a built asset. Vite's build metadata under dist/client/.vite/ stays private, including its compressed copies. A leading /.well-known/ directory is the exception: its public files remain served.

The module routes

The routes of the modules' dms.server.ts, for every method. A method the module does not declare on its path answers 405, and a HEAD reaches the GET route. See Server routes.

The backend proxies

The prefixes the modules declare. The renderer proxies no path on its own: the DMS declares /api/** and /dms/** in its dms.server.ts.

The pages

Everything else is a page. A method other than GET and HEAD there is a 404. A backend route outside every declared prefix, such as a page's <page>/pagelayout, is never reached from the browser.

The server lists the result when it starts:

Routing: renderer files, then 21 module routes, then the backend proxies below, then the pages
  /api/**    -> backend (session), declared by antelopejs__dms-frontend-vue
  /dms/**    -> backend (session), declared by antelopejs__dms-frontend-vue
  /ai/**     -> backend (session), declared by @antelopejs/dms-ai-frontend-vue
  /i18n/**   -> backend (none), declared by @antelopejs/dms-lang-frontend-vue
  /media/**  -> backend (session), declared by @antelopejs/dms-media-frontend-vue

In development, every answer names its owner in an x-dms-route-owner header: renderer, module:<id>, backend:<pattern>, page or not-found.

Declaring a backend prefix

In dms.server.ts:

dms.server.ts
server.registerBackendProxy("/api/**"); // auth: "session", the default
server.registerBackendProxy("/media/**", { auth: "none" });
  • auth: "session" adds the session's access token to the request (Authorization: Bearer), server-side: the token never reaches the browser, which only holds the sealed, HttpOnly session cookie. A request with a state-changing method must carry the frontend's own Origin, except on a public endpoint.
  • auth: "none" never sends the session, so it needs no Origin: for media, or a webhook that authenticates its caller itself.

A pattern is /x, /x/* (one segment below) or /x/** (the path and everything below), its first segment literal, outside the renderer's own paths. Of two patterns covering a path, the more specific wins. For one pattern, the highest-priority frontend module's declaration wins, then the manifest's, with a warning.

The manifest's backendProxies reach the frontend only when it is fetched with the bootstrap credential. A build made without DMS_BOOTSTRAP_SECRET serves only the prefixes the modules' dms.server.ts declare.

What the proxy sends

Every proxied request relays:

  • If-Match, so a backend route can refuse a write made from a stale copy (412 or 409);
  • the client's Origin and Referer as received, so a public route can check where a beacon comes from;
  • X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host, set by the proxy itself so a route can limit each client. The client's own X-Forwarded-For is extended with the socket's address only behind DMS_TRUSTED_PROXY_HOPS trusted proxies, and replaced by it otherwise.

The proxy matches the request's normalized path, the one it sends the backend, so .., %2e%2e or \ cannot reach another route through a declared prefix.

Response compression

The proxy asks the backend for an uncompressed answer. It compresses eligible text (JSON, text, XML and SVG) of at least 1 KiB, or of unknown length, using Brotli or gzip according to the client's Accept-Encoding qualities. Streaming compression delivers each part as the backend writes it. Eligible answers include Vary: Accept-Encoding even when the client receives identity bytes.

The proxy does not compress event streams, ranges, answers marked no-transform, or opaque encoded bodies. If the backend ignores the identity request, native fetch decodes supported encodings; the proxy removes the stale encoding and length before relaying or recompressing that body. A decoded encoded range returns 502 rather than forwarding byte offsets that no longer describe its body. HEAD and bodyless responses retain their representation metadata.