# Adapters

> One handler serves HTTP, SSE and WebSockets on every runtime; pick the adapter for yours.

`createFetchHandler()` returns a web-standard `fetch(request)`. Anything that speaks `Request` and `Response` can serve it directly. On Effect's `HttpServer`, `@trpc12/trpc/http` adds WebSockets, client-disconnect aborts, and Effect's platform layers.

```ts
const handler = createFetchHandler({ router, createContext, layer });
// handler.fetch(request): Promise<Response>
// handler.websocket(socket, { request })
// handler.dispose(): releases the layer
```

Procedures are served under `endpoint`, `/trpc` by default.

| Runtime                        | Serve with                                       | WebSockets            |
| ------------------------------ | ------------------------------------------------ | --------------------- |
| [Node](https://v12.trpc.io/v12/docs/adapters/node.md)              | `serve(handler)` on `NodeHttpServer`, or Express | yes                   |
| [Bun](https://v12.trpc.io/v12/docs/adapters/bun.md)                | `Bun.serve({ fetch: handler.fetch })`            | with `serve(handler)` |
| [Deno](https://v12.trpc.io/v12/docs/adapters/deno.md)              | `Deno.serve` on `handler.fetch`                  | `websocket.upgrade`   |
| [Workers, Next.js](https://v12.trpc.io/v12/docs/adapters/fetch.md) | `handler.fetch`                                  | in a Durable Object   |
| [AWS Lambda](https://v12.trpc.io/v12/docs/adapters/aws-lambda.md)  | `toLambdaHandler(handler)`                       | no                    |

The [`examples/runtime-matrix`](https://github.com/trpc/trpc/blob/v12/next/examples/runtime-matrix/README.md) app serves one router on each of these and runs the same calls against every one: batching, errors, files, streaming, SSE, CORS and WebSockets.

## Middleware and CORS

Request-level concerns are Effect `HttpMiddleware` in the handler's `middleware` option, outermost first. They wrap `handler.fetch` and WebSocket upgrades too: under `serve()`/`toHttpEffect`, and through `handler.fetch` with `websocket.upgrade` (Deno). A socket you accept yourself and hand to `handler.websocket` skips them. Call-level concerns are root `t.use()` [middleware](https://v12.trpc.io/v12/docs/server/middleware.md).

```ts
import { HttpMiddleware } from 'effect/http';
import { cors } from '@trpc12/trpc/http';

const handler = createFetchHandler({
  router,
  middleware: [
    cors({ allowedOrigins: ['https://app.example.com'], credentials: true }),
    HttpMiddleware.logger,
  ],
});
```

`cors()` is Effect's `HttpMiddleware.cors` plus an origin check on WebSocket upgrades (403 for other origins). Browsers send cookies with cross-site upgrades and CORS doesn't cover them, so use it when you authenticate with cookies.

## Compression

@trpc12/trpc doesn't compress. Your CDN or proxy (Cloudflare, Vercel, nginx) already negotiates `Accept-Encoding`, and does it better. If you add a compressing `HttpMiddleware`, skip `application/jsonl`, `application/vnd.trpc.frames` and `text/event-stream` responses: a gzip stream buffers until its window fills, which stalls subscriptions and streamed results.
