# 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/docs/adapters/node.md)                       | `serve(handler)` on `NodeHttpServer`, or Express | yes                             |
| [Bun](https://v12.trpc.io/docs/adapters/bun.md)                         | `Bun.serve({ fetch: handler.fetch })`            | with `serve(handler)`           |
| [Deno](https://v12.trpc.io/docs/adapters/deno.md)                       | `Deno.serve`                                     | `handler.websocket`             |
| [Workers, Next.js](https://v12.trpc.io/docs/adapters/fetch.md)          | `handler.fetch`                                  | in a Durable Object             |
| [AWS Lambda](https://v12.trpc.io/docs/adapters/aws-lambda.md)           | `toLambdaHandler(handler)`                       | no                              |
| [Durable Objects](https://v12.trpc.io/docs/adapters/durable-objects.md) | `experimental_createHibernatingWebSockets`       | hibernating                     |
| [Message ports](https://v12.trpc.io/docs/adapters/message-ports.md)     | `handler.messagePort(port)`                      | n/a                             |
| [Firebase](https://v12.trpc.io/docs/adapters/firebase.md)               | `call()` in `onCall`                             | no                              |
| [Raw sockets](https://v12.trpc.io/docs/adapters/sockets.md)             | `serveSocket(handler)`                           | the WebSocket protocol over TCP |

## Middleware and CORS

Request-level concerns are Effect `HttpMiddleware` in the handler's `middleware` option, outermost first. They wrap `handler.fetch` and, under `serve()`/`toHttpEffect`, WebSocket upgrades too. Call-level concerns are root `t.use()` [middleware](https://v12.trpc.io/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.
