{"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/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/runtime/server/endpoint.ts#L46-L80","documentation":"An API route endpoint (a `.ts`/`.js` file under `src/pages/` exporting `GET`/`POST`/etc.) ran its handler, but the resolved value is not a `Response` instance (it was falsy, or `response instanceof Response` is false). Astro endpoints are contract-bound to return exactly a `Response`. This is the `EndpointDidNotReturnAResponse` AstroError.","triggerScenarios":"The handler returns a plain object, array, string, number, `null`, or `undefined`; an async handler has a code path with no `return`; the handler returns a `Promise` that resolves to a non-Response; the handler returns a fetch result wrapper or custom class that isn't a `Response`.","commonSituations":"Returning JSON data directly (`return { ok: true }`) instead of wrapping it; forgetting `return` in an early-exit branch of an async handler; returning `Astro.redirect()`-like values from a helper that isn't a Response; migrating a page to an endpoint and forgetting the Response wrapping.","solutions":["Ensure every code path in the endpoint handler returns a `Response` (check early returns and the final statement).","Wrap data payloads: `return new Response(JSON.stringify(data), { headers: { 'content-type': 'application/json' } })` or `return Response.json(data)`.","For empty/204-style replies use `return new Response(null, { status: 204 })`.","Add a TypeScript return type of `Promise<Response>` so the compiler flags non-Response returns."],"exampleFix":"// before\nexport const GET = async ({ url }) => {\n  const data = await db.query();\n  return data; // plain object -> throws\n};\n\n// after\nexport const GET = async ({ url }): Promise<Response> => {\n  const data = await db.query();\n  return Response.json(data);\n};","handlingStrategy":"type-guard","validationCode":"// Ensure every endpoint handler returns a Response\nfunction assertResponse(value: unknown): Response {\n  if (!(value instanceof Response)) {\n    throw new Error('Endpoint handler must return a Response');\n  }\n  return value;\n}","typeGuard":"const isResponse = (v: unknown): v is Response => v instanceof Response;\n\n// Usage in a handler:\nexport const GET = async (ctx): Promise<Response> => {\n  const res = await handler(ctx);\n  return isResponse(res) ? res : Response.json(res);\n};","tryCatchPattern":"// Wrap external calls so a thrown/non-Response result never escapes\nexport const GET = async (ctx): Promise<Response> => {\n  try {\n    const data = await loadData(ctx);\n    return Response.json(data);\n  } catch (e) {\n    return Response.json({ error: String(e) }, { status: 500 });\n  }\n};","preventionTips":["Type every endpoint handler as `Promise<Response>` so the compiler rejects non-Response returns.","Always wrap data with `Response.json(...)` or `new Response(...)`.","Audit early-return branches to confirm each returns a Response."],"tags":["endpoint","api-routes","response","type-contract"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}