denoland/deno · error · TypeError

Return value from serve handler must be a response or a prom

Error message

Return value from serve handler must be a response or a promise resolving to a response

What it means

Deno.serve requires the handler (or the promise it returns) to resolve to an instance of the built-in Response. Before sending, serve validates the value's prototype chain; anything else (string, plain object, undefined) throws this TypeError so the failure surfaces in the handler's context instead of crashing the native layer. The throw is routed to onError, which by default produces a 500 response.

Source

Thrown at ext/http/00_serve.ts:724

        response = await callback();
      } else {
        innerRequest = new InnerRequest(req, context);
        const request = fromInnerRequest(innerRequest, "immutable");
        innerRequest.request = request;

        if (span) {
          updateSpanFromRequest(span, request);
        }

        response = await callback(
          request,
          new ServeHandlerInfo(innerRequest),
        );
      }

      // Throwing Error if the handler return value is not a Response class
      if (!ObjectPrototypeIsPrototypeOf(ResponsePrototype, response)) {
        throw new TypeError(
          "Return value from serve handler must be a response or a promise resolving to a response",
        );
      }

      // The Response prototype check above passes for Response-like objects
      // (e.g. a subclass that skipped super(), or a Response from a different
      // realm/polyfill). Those don't carry the internal slot we read from
      // below, so reject them with a clear error instead of crashing later.
      inner = getInnerResponse(response);
      if (inner === undefined) {
        throw new TypeError(
          "Return value from serve handler must be a Response constructed via the Response constructor in this realm",
        );
      }

      if (inner.type === "error") {
        throw new TypeError(
          "Return value from serve handler must not be an error response (like Response.error())",

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Wrap non-Response values: return new Response('ok') or return Response.json(data)
  2. Audit every branch of the handler, including catch blocks, so each path returns a Response
  3. In middleware chains, always return the downstream handler's result
  4. Type the handler as (req: Request) => Response | Promise<Response> so mismatches surface at compile time

Example fix

// before
Deno.serve((req) => {
  if (req.url.endsWith("/health")) return "ok"; // plain string
  return appHandler(req);
});

// after
Deno.serve((req) => {
  if (req.url.endsWith("/health")) return new Response("ok");
  return appHandler(req);
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate before returning from any handler path
if (!(response instanceof Response)) {
  response = new Response(String(response));
}
return response;

Type guard

function isResponse(v) {
  return v instanceof Response;
}

Try / catch

Deno.serve({
  onError: (err) =>
    err instanceof TypeError &&
      err.message.includes("must be a response or a promise")
      ? new Response("Handler must return a Response", { status: 500 })
      : new Response("Internal Server Error", { status: 500 }),
  handler,
});

Prevention

When it happens

Trigger: Returning 'ok', a parsed JSON object (await res.json()), or a URL from the handler; a code path that falls off the end without a return statement; a middleware branch that forgets 'return next(request)'.

Common situations: Porting Express-style handlers that used res.send(...); early-return guard clauses with a missing return; returning the result of a fetch().json() call instead of the fetch Response itself.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/b41ecb8ad95ce0ec. Report an issue: GitHub.