httpPlugin

import { httpPlugin } from '@routecraft/routecraft'

Serves routes over HTTP. Backs the http() source; routes declare .from(http({ path, method })) and the plugin mounts them on a named server.

http is a first-class core config key, so the common path is defineConfig({ http: {...} }) rather than plugins: [httpPlugin(...)]. The factory is exported for programmatic composition.

import { defineConfig, jwt } from '@routecraft/routecraft'

export const craftConfig = defineConfig({
  servers: { public: { port: 8080, host: '0.0.0.0' } },
  http: {
    server: 'public',
    auth: jwt({ secret: process.env.JWT_SECRET!, issuer: '...', audience: '...' }),
  },
})

Options

OptionTypeDefaultRequiredDescription
serverstringdefaultNoSingle-mount sugar: named server for the lone default mount. Mutually exclusive with mounts.
authHttpAuth | falseinheritedNoSingle-mount sugar: the wall for a lone default mount at /. Omit to inherit server auth, false to remove the wall (the inherited validator stays reachable for .authorize()). Mutually exclusive with mounts.
mountsRecord<string, { path, server?, auth? }>one default mount at /NoNamed path-scoped surfaces, each a complete self-description: path, server, auth. See Mounts and authentication and Mounts and servers.
maxBodySizenumber10485760 (10 MB)NoMaximum request body in bytes. Larger requests get 413.
events{ perRequest?: boolean }{ perRequest: true }NoToggle the plugin:http:request:completed event.
builtins{ health?, ready?, openapi?: { enabled?: boolean; requireAuth?: boolean } }see belowNoPer-endpoint config for /health, /ready, /openapi.json. Each takes the same { enabled, requireAuth } shape. See Configuring built-ins on the adapter reference for defaults and the per-endpoint behaviour table.

Per-route authorization uses the existing .authorize({ roles, scopes }) builder. The mount, not the route, decides authentication: routes on a walled mount require a valid credential, routes on a mount with no wall (auth: false) never see one, and such a route that declares .authorize() forces verification through the inherited validator for itself alone. See Mounts and authentication for the full matrix. Built-in endpoints /health, /ready, and /openapi.json serve from the default mount at / unless a user route claims the same path, and describe only routes on that mount's server when mounts span several listeners.

Custom mounts

Custom plugins can join the same listener during apply():

import { requireWebIngress, type CraftPlugin } from '@routecraft/routecraft'

const healthPlugin: CraftPlugin = {
  name: 'health',
  apply(ctx) {
    const ingress = requireWebIngress(ctx, 'public')
    const unmount = ingress.mountHttp({
      id: 'custom-health',
      claims: () => [{ kind: 'exact', path: '/internal/health', methods: ['GET'] }],
      handler: () => Response.json({ ok: true }),
    })
    ctx.registerTeardown(unmount)
  },
}

Claims are thunks evaluated once during startup validation. This lets plugins register dynamic routes during route startup while still detecting cross-mount conflicts before the listener binds.

A mount whose responses stream or stay quiet indefinitely (SSE, MCP-style channels) declares longLived: true; the ingress then exempts its requests from the listener's idle timeout while every other connection keeps the bounded default.

Lifecycle

  • apply(ctx) validates options, publishes the route registry, and mounts it on the selected named server. It does not bind a socket.
  • During context start, all mount claims on all named servers are validated before any listener binds. The server then emits server:listening { server, port, host }.
  • On context stop, named servers drain active requests, close idle connections, and emit server:closed { server }.
  • A bind failure (EADDRINUSE / EADDRNOTAVAIL) surfaces as RC5019.

Events

See HTTP plugin events for the full list. The plugin also re-uses the framework's auth:success / auth:rejected events with source: "http".