denoland/deno · error · TypeError

First argument to 'respondWith' must be a Response or a…

Error message

First argument to 'respondWith' must be a Response or a promise resolving to a Response

What it means

`Deno.HttpConn.respondWith()` (the low-level API obtained from `Deno.serveHttp(conn)`) accepts only a `Response` instance or a promise resolving to one. Before writing to the socket, Deno awaits the value and verifies it is on the `Response.prototype` chain; anything else — a string, plain object, `undefined`, a `Request` — throws this TypeError and the request fails.

Solutions

  1. Wrap the payload: `respondWith(new Response(body, { status, headers }))`
  2. Check async handlers actually `return` the Response
  3. If you return a promise, make sure it resolves to a real `Response`

Example fix

// before
await requestEvent.respondWith(JSON.stringify(data));

// after
await requestEvent.respondWith(
  new Response(JSON.stringify(data), { headers: { "content-type": "application/json" } }),
);
Defensive patterns

Strategy: type-guard

Validate before calling

const resp = await maybePromise;
if (!(resp instanceof Response)) {
  throw new TypeError("handler must return a Response");
}
await e.respondWith(resp);

Type guard

const isResponse = (v: unknown): v is Response => v instanceof Response;

Try / catch

try {
  await e.respondWith(handler(requestEvent.request));
} catch (err) {
  if (err instanceof TypeError && err.message.includes("respondWith")) {
    await e.respondWith(new Response("handler error", { status: 500 }));
  } else throw err;
}

Prevention

When it happens

Trigger: `requestEvent.respondWith("hello")`; `respondWith({ status: 200 })`; an async handler that forgets `return` before `new Response(...)` so the awaited promise resolves to `undefined`; passing a `Request` back by mistake.

Common situations: Migrating from service-worker fetch-event or Express-style `(req, res)` handlers; refactors that drop the return statement; responding with serialized JSON instead of a Response object.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/43b40eec402e4d12. Report an issue: GitHub.

Appendix: source

Thrown at ext/http/01_http.js:201

        const reqEvt = await httpConn.nextRequest();
        // Change with caution, current form avoids a v8 deopt
        return { value: reqEvt ?? undefined, done: reqEvt === null };
      },
    };
  }
}

function createRespondWith(
  httpConn,
  request,
  readStreamRid,
  writeStreamRid,
) {
  return async function respondWith(resp) {
    try {
      resp = await resp;
      if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) {
        throw new TypeError(
          "First argument to 'respondWith' must be a Response or a promise resolving to a Response",
        );
      }

      // The Response prototype check above passes for Response-like objects
      // that don't carry the internal slot (e.g. `Object.create(Response.prototype)`
      // or a polyfilled/foreign-realm Response). Reject those here instead of
      // crashing later on `innerResp.body`. Mirrors the Deno.serve guard
      // added in #34416.
      const innerResp = toInnerResponse(resp);
      if (innerResp === undefined) {
        throw new TypeError(
          "First argument to 'respondWith' must be a Response constructed via the Response constructor in this realm",
        );
      }

      // If response body length is known, it will be sent synchronously in a
      // single op, in other case a "response body" resource will be created and

View on GitHub (pinned to 9ad36f7a2c)