Files
skills/docs/api-stack/api/api-plugin-site/src/routes/public-routes.md
2026-05-29 08:20:56 +01:00

2.3 KiB

routes/public-routes.ts

Purpose

Builds the public (no-auth) Hono route tree that the plugin mounts under the configured routePrefix. The two endpoints exposed here are the entrypoints used by edge / SSR consumers that haven't yet authenticated — domain resolution drives multi-tenant routing decisions at request ingress, and the public-info endpoint surfaces a santised view of the site (theme + feature flags) so client bundles can render branded shells without leaking push config or secrets.

Routes are wired through @hono/zod-openapi so the OpenAPI document the host app exposes is automatically populated from the same Zod schemas the service layer validates against.

Features

  • GET /sites/resolve/{domain} — resolves a domain to a site + organization envelope.
  • GET /sites/{id}/public — returns the SitePublicInfo projection.
  • Both routes leverage the canonical SiteSchema / SitePublicInfoSchema for OpenAPI typing.
  • Failures bubble up as SiteError so the host-app error middleware can map them to RFC 7807 / structured envelopes consistently with other plugins.

Usage: Basic

import { createPublicSiteRoutes } from "@suaveplan/api-plugin-site/routes/public-routes";

const publicRouter = createPublicSiteRoutes({ siteService, domainResolver });
app.route("/api", publicRouter);

Usage: Advanced

// Mount under a custom prefix and layer additional middleware.
const publicRouter = createPublicSiteRoutes({ siteService, domainResolver });
app.use("/v1", rateLimitMiddleware);
app.route("/v1", publicRouter);

API Reference

  • interface PublicSiteRoutesDeps — siteService, domainResolver.
  • createPublicSiteRoutes(deps: PublicSiteRoutesDeps): OpenAPIHono.

Implementation Notes

  • The resolve route always returns 200 with success: false on miss — this mirrors the upstream contract so an existing consumer's redirect logic does not have to swap between 200 and 404.
  • The public-info route DOES throw SITE.SITE_NOT_FOUND on miss, because the route is the integration boundary between "lookup" and "render" — a 404 here is the conventional signal to the upstream caller.
  • The conditional spread (...(site.slug !== undefined && {...})) keeps the JSON payload tight by omitting absent optionals rather than emitting null.