# Node http

Source: https://permdock.com/docs/adapters/node

permdock/node wraps the Fetch kernel for raw node:http handlers (IncomingMessage and ServerResponse) and for any framework not covered by a dedicated adapter.

## Purpose [#purpose]

Some services run on `node:http` directly, on a framework without a PermDock adapter (Koa, Polka, h3), or on a custom server. `permdock/node` converts `IncomingMessage` / `ServerResponse` to the Fetch `Request` / `Response` pair the [server kernel](/docs/adapters/server-kernel) expects, and exposes the kernel's `permdock`, `protect` and `problem` in a callback shape. It is also the conversion layer the [Express](/docs/adapters/express) and [Nest](/docs/adapters/nest) adapters reuse. Runtimes that already expose Fetch handlers (Bun, Deno, Workers, `srvx`) should use `permdock/server` directly.

## API [#api]

```ts
import { createServer } from "node:http";
import { createPermDock } from "permdock/node";
import { policy } from "./policy";
import { permissions } from "./permissions";

const { permdock, protect, send } = createPermDock(policy, {
  subject: (req) => userFromCookie(req.headers.cookie),
});

createServer(async (req, res) => {
  const instance = await permdock(req);

  if (req.method === "DELETE" && req.url?.startsWith("/posts/")) {
    const guard = await protect(permissions.post.delete, () =>
      loadPost(idFrom(req.url)),
    )(req);
    if (!guard.ok) return send(res, guard.response);
    await deletePost(guard.data);
    res.statusCode = 204;
    return res.end();
  }

  res.statusCode = 200;
  res.end(
    JSON.stringify(instance.filter(permissions.post.read, await listPosts())),
  );
}).listen(3000);
```

| Export | Role |
| --- | --- |
| `permdock(req)` | Converts the request once (headers, method, URL; body is streamed lazily), resolves the subject, builds the instance, memoises it in a `WeakMap` keyed by `req`. |
| `protect(permission, loadData?, options?)` | Returns `(req) => Promise<Guard>`; same result shape as the kernel (`ok`, `permdock`, `decision`, `data` or `response`). The `tenant` option (`(req) => ...`) is resolved for each call. |
| `send(res, response)` | Writes a Fetch `Response` to a `ServerResponse` (status, headers, body). Used for Problem Details and for the decision endpoint. |
| `permdockHandler()` | `(req, res) => Promise<void>` implementing the AuthZEN-shaped decision endpoint for mounting under any path. |
| `toRequest(req)` / `fromResponse(res, response)` | Exported converters so other adapters and custom frameworks can reuse them. |

## Request lifecycle [#request-lifecycle]

`permdock/node` follows the [shared adapter contract](/docs/adapters) through the [server kernel](/docs/adapters/server-kernel). What differs on raw `node:http`:

* `permdock(req)` memoises on `req`, so later calls with the same request return the same instance.
* A PermDock error thrown by `assert` has no framework hook to catch it: wrap the handler and `send` the result of `problemFromError(error)` from `permdock/server` when it returns a `Response`.
* The conversion is lazy. Headers, method and URL are copied when `permdock(req)` is first called; an unread body is exposed as a pull stream (`highWaterMark: 0`) on the Fetch `Request` and only read when a loader or the decision endpoint consumes it, so a body parser that runs later still sees every byte. A parsed `req.body` (Express, Nest) is reused instead of re-reading the stream. A missing `Host` header falls back to `localhost` and does not throw.
* `permdockHandler()` validates decision-endpoint bodies against the AuthZEN `evaluations` schema.

Denials are the kernel's Problem Details responses, written through `send`.

## Example app [#example-app]

No dedicated example. Covered by unit tests using `node:http` on an ephemeral port and by the Express and Nest examples, which reuse the same converters.

`tests/integration/src/http/node.test.ts` runs the [`testHttpAdapter`](/docs/adapters/testing) scenarios on `node:http` with a `URLPattern` router, a `formData()` upload behind `protect` and `problemFromError` for `assert`.

## Related standards [#related-standards]

* [Problem Details](/docs/standards/problem-details).
