---
title: Host adapters
editUrl: https://github.com/Cratis/Arc.TypeScript/edit/main/Documentation/hosts/index.md
description: Mount Arc in Express, Fastify, Hono, or a Fetch API host; know which runtime features each host supports.
---


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.

:::caution[Source preview]
The adapters are not published to npm. Pack the adapter you need from a built clone of this repository and install the tarball in your project, or put your application under `Samples/` in the clone and reference the adapter with the `workspace:^` protocol. [Create an application](/arc/backend/typescript/getting-started/create-an-application/) shows both paths, and [Packages](/arc/backend/typescript/reference/packages/) lists the package names.
:::

## Before you start

- 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:

```typescript title="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](https://github.com/Cratis/Arc.TypeScript/blob/main/Samples/Tasks/main.ts) without `app.run()`, because the framework owns the listener. `metadata` is the module that `arc-proxygenerator --metadata` writes; see [Generate artifact metadata](/arc/backend/typescript/proxy-generation/generated-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.

| Package | Exports | Framework peer range |
| --- | --- | --- |
| `@cratis/arc.express` | `cratisArc(arc)` middleware and `.injectWebSocket(listener)` | `express` `^5.0.0` |
| `@cratis/arc.fastify` | `app.register(cratisArc, { arc })` (WebSockets on by default) | `fastify` `^5.0.0` |
| `@cratis/arc.hono` | `app.use(cratisArc(arc))`; Node: `serveCratisArc` | `hono` `^4.0.0`; optional `@hono/node-server` `^1.19.11` for Node hosting |
| `@cratis/arc.core/hosting` | `attachNodeWebSockets` and adapter hosting primitives | None |

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.

## Pick your framework

- [Express](/arc/backend/typescript/hosts/express/): one middleware, mounted before body parsers.
- [Fastify](/arc/backend/typescript/hosts/fastify/): an encapsulated plugin with its own raw-body parser.
- [Hono](/arc/backend/typescript/hosts/hono/): middleware on a Fetch API framework, with raw-path checks on Node.
- [Fetch API runtimes](/arc/backend/typescript/hosts/fetch-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](/arc/backend/typescript/hosts/websockets/). To use a principal your framework already verified, see [Native principal](/arc/backend/typescript/hosts/native-principal/).

## What every adapter shares

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](/arc/backend/typescript/reference/http-contract/).

## Where adapters differ

| Area | Express | Fastify | Hono |
| --- | --- | --- | --- |
| Registration | One middleware, before body parsers | Encapsulated plugin; routes exist once the app is ready | One middleware |
| Request bodies | Raw request stream | Raw buffer from a scoped catch-all parser | The Fetch API request body |
| Body size | Arc's `hosting.maxBodyBytes` | Fastify's `bodyLimit` first, then `hosting.maxBodyBytes` | Arc's `hosting.maxBodyBytes` |
| Unknown path | Falls through to your routes; Express's own 404 carries no Arc correlation header | Fastify's 404 | Falls through to your routes |
| `context.signal` aborts | When the client disconnects before the response finishes | When the client disconnects before the response finishes | When the signal of the request Hono received aborts; depends on the server running Hono |
| Application type | `Express` | `FastifyInstance` | `Hono<E>` for any `Env` type |

## Ownership and shutdown

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.

## Related

- [Hosting overview](/arc/backend/typescript/overview/)
- [Arc.Core and the standalone host](/arc/backend/typescript/core/) for hosting without a framework
- [Calling commands from code](/arc/backend/typescript/commands/calling-commands-from-code/)
