{"record":{"id":"452818059a0f309b","repo":"withastro/astro","slug":"endpointdidnotreturnaresponse","errorCode":"EndpointDidNotReturnAResponse","errorMessage":"An endpoint must return either a `Response`, or a `Promise` that resolves with a `Response`.","messagePattern":"An endpoint must return either a `Response`, or a `Promise` that resolves with a `Response`\\.","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/runtime/server/endpoint.ts","lineNumber":64,"sourceCode":"\t\t\t\t\t: ''),\n\t\t);\n\t\t// No handler matching the verb found, so this should be a\n\t\t// 404. Should be handled by 404.astro route if possible.\n\t\treturn new Response(null, { status: 404 });\n\t}\n\tif (typeof handler !== 'function') {\n\t\tlogger.error(\n\t\t\t'router',\n\t\t\t`The route \"${\n\t\t\t\turl.pathname\n\t\t\t}\" exports a value for the method \"${method}\", but it is of the type ${typeof handler} instead of a function.`,\n\t\t);\n\t\treturn new Response(null, { status: 500 });\n\t}\n\n\tlet response = await handler.call(mod, context);\n\tif (!response || response instanceof Response === false) {\n\t\tthrow new AstroError(EndpointDidNotReturnAResponse);\n\t}\n\n\t// Endpoints explicitly returning 404 or 500 response status should\n\t// NOT be subject to rerouting to 404.astro or 500.astro.\n\tif (state && REROUTABLE_STATUS_CODES.includes(response.status)) {\n\t\tstate.skipErrorReroute = true;\n\t}\n\n\tif (method === 'HEAD') {\n\t\t// make sure HEAD responses doesnt have body\n\t\treturn new Response(null, response);\n\t}\n\n\treturn response;\n}\n","sourceCodeStart":46,"sourceCodeEnd":80,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/runtime/server/endpoint.ts#L46-L80","documentation":"Astro API routes (endpoints) must return a Response instance from every handler. After awaiting the handler, the endpoint runner checks response instanceof Response; returning undefined, a plain object, a string, or any other value throws EndpointDidNotReturnAResponse. (A handler that exists but is not a function at all is a different, logged 500 path just above.)","triggerScenarios":"export const GET = () => someData returning raw JSON data; an early 'return;' with no value; returning JSON.stringify(data) (a string, not a Response); a helper that returns parsed data instead of a fetch Response; an async branch that forgets to wrap the result.","commonSituations":"Migrating Express-style res.json() handlers to Astro endpoints; forgetting the Response wrapper in one branch of a conditional; returning the result of a data-layer function directly; returning a fetch() result object from an intermediate helper that already consumed it.","solutions":["Wrap the payload: return Response.json(data) or new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })","Make every code path return a Response, including early exits: return new Response(null, { status: 404 })","Annotate handlers with TypeScript return types (Response | Promise<Response>) so the compiler catches bad returns","If you need a passthrough, return fetch(...) directly since that already resolves to a Response"],"exampleFix":"// before\nexport const GET = async () => {\n  const data = await getPosts();\n  return data;\n};\n\n// after\nexport const GET = async () => {\n  const data = await getPosts();\n  return Response.json(data);\n};","handlingStrategy":"type-guard","validationCode":"export const GET = async () => {\n  const data = await getPosts();\n  const body = JSON.stringify(data);\n  return new Response(body, { headers: { 'Content-Type': 'application/json' } });\n};","typeGuard":"function isResponse(value: unknown): value is Response {\n  return value instanceof Response;\n}","tryCatchPattern":"Wrap handler bodies in try/catch and always end with a Response, e.g. catch (e) { return new Response('Server error', { status: 500 }); } — the runtime throws EndpointDidNotReturnAResponse only when a non-Response escapes, so guaranteeing the return type in every branch (including catch) fully avoids it.","preventionTips":["Annotate every handler: export const GET: APIRoute = async ({}) => { ... } (APIRoute forces a Response return)","Use Response.json(data) instead of returning raw objects","Lint for 'return;' with no argument inside endpoint files"],"tags":["astro","endpoint","api-route","response","return-type"],"backgroundTag":"invalid-handler-return-type","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}