Skip to content

Host adapters

Your team already runs a web framework, with its middleware, health checks, and deployment story. You do not want a second server for Arc. A host adapter mounts the Arc application into the framework you have and leaves every route Arc does not own to that framework.

  • An ES module package ("type": "module"). Applications using the adapters and Core need Node.js 22 or later; building the workspace needs 22.19 or later, and Node.js 24 LTS is recommended.
  • A built Arc application. Keep it in its own module so every host imports the same instance:
arc.ts
import { ArcApplication } from '@cratis/arc.core';
import { Tasks } from './Features/Tasks/Tasks.js';
import { metadata } from './Features/generatedMetadata.js';
const builder = ArcApplication.createBuilder();
builder.useGeneratedMetadata(metadata);
builder.services.addSingleton(Tasks);
await builder.discover(new URL('./Features/', import.meta.url));
export const arc = await builder.build();

This is the Tasks sample’s bootstrap without app.run(), because the framework owns the listener. metadata is the module that arc-proxygenerator --metadata writes; see Generate artifact metadata. Register your own services the way the sample registers Tasks. build() checks the artifacts and options and throws when something is wrong, so a mistake shows up when the process starts rather than on the first request.

PackageExportsFramework peer range
@cratis/arc.expresscratisArc(arc) middleware and .injectWebSocket(listener)express ^5.0.0
@cratis/arc.fastifyapp.register(cratisArc, { arc }) (WebSockets on by default)fastify ^5.0.0
@cratis/arc.honoapp.use(cratisArc(arc)); Node: serveCratisArchono ^4.0.0; optional @hono/node-server ^1.19.11 for Node hosting
@cratis/arc.core/hostingattachNodeWebSockets and adapter hosting primitivesNone

Each adapter accepts a built ArcApplication or low-level ArcServer. The @cratis/arc.core/hosting subpath also exposes integration-only command helpers. Applications must not call these helpers; inlineCommitClientResponse, for example, lets a trusted integration mark a command whose handler commits inline.

  • Express: one middleware, mounted before body parsers.
  • Fastify: an encapsulated plugin with its own raw-body parser.
  • Hono: middleware on a Fetch API framework, with raw-path checks on Node.
  • Fetch API runtimes: explicitly registered artifacts over app.fetch for a checked Next.js Node.js production-server route handler and bounded Bun/Deno checks. The package is an unpublished source preview; read the verification boundary before deployment.

Express needs a listener attach step for WebSockets; Fastify’s plugin and Hono’s Node helper attach them in the setup call, described in WebSockets. To use a principal your framework already verified, see Native principal.

The adapters add no Arc behavior of their own. Each one:

  • dispatches only requests whose path exactly matches an Arc route, on a fixed internal origin, so a crafted Host header cannot select a different operation (Express and Fastify compare the raw path; Hono checks the raw request-target only on @hono/node-server);
  • hands Arc the unparsed body and lets Arc enforce hosting.maxBodyBytes;
  • passes a cancellation signal that becomes context.signal; pass it to anything that accepts one, such as fetch or a database driver;
  • serves the same routes: commands, validation routes, queries, observable queries, and the /.cratis and /openapi.json endpoints listed in the HTTP contract reference.
AreaExpressFastifyHono
RegistrationOne middleware, before body parsersEncapsulated plugin; routes exist once the app is readyOne middleware
Request bodiesRaw request streamRaw buffer from a scoped catch-all parserThe Fetch API request body
Body sizeArc’s hosting.maxBodyBytesFastify’s bodyLimit first, then hosting.maxBodyBytesArc’s hosting.maxBodyBytes
Unknown pathFalls through to your routes; Express’s own 404 carries no Arc correlation headerFastify’s 404Falls through to your routes
context.signal abortsWhen the client disconnects before the response finishesWhen the client disconnects before the response finishesWhen the signal of the request Hono received aborts; depends on the server running Hono
Application typeExpressFastifyInstanceHono<E> for any Env type

The framework owns its listener. Mounting an application does not make the adapter own shutdown: call shutdownArcHost(arc.server, () => framework.close()) from @cratis/arc.core/hosting to start participant shutdown before closing a listener with live observables. Express also provides middleware.shutdown(listener). If your host supplied Arc’s registry, pass it explicitly as the third argument to shutdownArcHost (or the second argument to Express shutdown); it is never disposed implicitly. Without participants the helper closes the framework first, then disposes Arc, as before. framework.close() or middleware.close(listener) alone remains listener-only.