Host Routing
Tenant per subdomain with one options object for the API and the HTML pages.
What it shows
Section titled “What it shows”createHostRoutingFeature(options): the Host header is the only source of the anonymous tenant; a forgedX-Tenantheader answers400 tenant_mismatch.createHostDispatch(options): HTML file and CSP per host, a 404 page for unknown shops, and/de/...redirecting to the unprefixed path.- The same
hostDispatchgoes intorunProdApp({ hostDispatch }); the dev server takes it throughtoDevHostDispatchfrom@cosmicdrift/kumiko-dev-server.
Source
Section titled “Source”Feature entry point: src/feature.ts.
Needs a running Postgres and TEST_DATABASE_URL set (e.g. postgres://postgres:postgres@127.0.0.1:5432/postgres, see demo/.env.example).
cd samples/recipes/host-routingbun testSource code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Host routing: one options object drives both halves. The feature makes the// Host header the only source of the anonymous tenant; the dispatch picks the// HTML file per host for runProdApp's `hostDispatch` (and, through// toDevHostDispatch, for the dev server).
import { createHostDispatch, createHostRoutingFeature, type HostRoutingOptions,} from "@cosmicdrift/kumiko-bundled-features/host-routing";import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
export const routingOptions: HostRoutingOptions = { tenantBy: "host", baseDomain: "shop.test", apex: { file: "index.html", localeFiles: { en: "en/index.html" } }, tenant: { file: "shop.html", csp: "default-src 'self'; frame-ancestors 'none'" }, unknownTenant: { file: "unknown-shop.html" }, locales: { default: "de", prefixed: ["en"] },};
export const hostRoutingFeature = createHostRoutingFeature(routingOptions);export const hostDispatch = createHostDispatch(routingOptions);
// A public query that reports which tenant the request landed on.export const storefrontFeature = defineFeature("storefront", (r) => { r.queryHandler({ name: "whoami", schema: z.object({}), access: { roles: ["anonymous"] }, rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 }, handler: async (query) => ({ tenantId: query.user.tenantId }), });});📄 On GitHub: samples/recipes/host-routing/src/feature.ts