N
Nuxt4mo ago
Kérunix

Send back full external API response from Nitro handler.

I'm sending requests to a Laravel backend from a Nuxt frontend. Both apps (Laravel and Nuxt) are implementing the Precognition protocol for form validation, but the data model that the backend expects is different from the one the frontend uses. The issue I'm facing is that precognition requests, when failing, include errors and headers that the frontend library (we're using nuxt-precognition) needs to correctly display the form errors. Is there a way to force Nitro to return the exact same headers and response body when the Laravel Precognition middleware returns a 422 ? I tried the following code which got me close but the Nitro docs specifically state that it won't work, so I'm not surprised. Just wondering if there's another way to make it work ?
export default defineEventHandler(async (event) => {
// Nitro path matches the Laravel API path so we just forward it
const path = event.path
const response = await $fetch.raw(`http://my-new-api.dev/${path}`, {
// Proxy method and headers
method: event.method,
headers: event.headers,
// Prevent from throwing error so I can access the raw response and handle it
ignoreResponseError: true,
})
// Try and make our response match the one from Laravel precognition.
// This is where it fails, `statusCode` is not set correctly (again, expected since documented)
// And Response.json(response.body) does not set the body correctly (which I don't understand)
event.node.res.statusCode = response.status
response.headers.forEach((header, name) => event.node.res.setHeader(name, header))
event.node.res.statusMessage = response.statusText
return Response.json(response.body)
})
export default defineEventHandler(async (event) => {
// Nitro path matches the Laravel API path so we just forward it
const path = event.path
const response = await $fetch.raw(`http://my-new-api.dev/${path}`, {
// Proxy method and headers
method: event.method,
headers: event.headers,
// Prevent from throwing error so I can access the raw response and handle it
ignoreResponseError: true,
})
// Try and make our response match the one from Laravel precognition.
// This is where it fails, `statusCode` is not set correctly (again, expected since documented)
// And Response.json(response.body) does not set the body correctly (which I don't understand)
event.node.res.statusCode = response.status
response.headers.forEach((header, name) => event.node.res.setHeader(name, header))
event.node.res.statusMessage = response.statusText
return Response.json(response.body)
})
From Nitro docs:
When sending a Response, any headers that set using event.res.headers before, will be merged as default headers. event.res.{status/statusText} will be ignored.
6 Replies
kapa.ai
kapa.ai4mo ago
Hi @Kérunix. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You're correct that directly setting statusCode, statusMessage, and headers on event.node.res (or event.res) is not fully supported in Nitro, as documented. Nitro merges headers and ignores status/statusText set this way when returning a Response object, which makes it difficult to proxy an external API response exactly as-is, especially for non-2xx responses like 422 from Laravel Precognition. The knowledge sources do not provide an officially supported way to fully override the response (status, headers, and body) in a Nitro handler. The documentation explicitly states that when sending a Response, any headers set using event.res.headers before will be merged as default headers, and event.res.{status/statusText} will be ignored. This matches the issue you're experiencing: status codes and some headers are not preserved as you expect when returning a Response object from your handler [Forwarding Context & Headers]. There is no documented workaround in the official Nuxt or Nitro docs to force Nitro to return the exact same headers and status code as the proxied backend response. The current utilities and event handler patterns are designed for typical API use cases, not for transparent proxying of all response details. Summary:
- Nitro does not currently support fully transparent proxying of external API responses (status, headers, and body) in a handler. - The limitation is documented, and your experience matches the expected behavior. - There is no officially documented workaround in the knowledge sources provided. If you need this feature, you may want to follow or contribute to related discussions or issues in the Nitro or Nuxt repositories, as this is a known limitation for advanced proxy use cases [Forwarding Context & Headers]. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@189762782642569217> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kérunix
KérunixOP4mo ago
@kapa.ai noted, what would be your way to achieve the required behavior in a Nuxt context then ?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Given the current state of Nitro and Nuxt, there is no officially documented way to fully proxy an external API response—including status code, headers, and body—so that it matches the backend response exactly. The Nitro documentation explicitly states that when returning a Response object, status and statusText set on the event are ignored, and headers are merged rather than replaced. This means you cannot guarantee a 1:1 passthrough of all response details from Laravel to the Nuxt frontend using standard Nitro handlers [Forwarding Context & Headers]. What you can do: - You can forward the request and response body using $fetch.raw and return the data, but you will not be able to fully control the status code and headers in the response. - For some use cases, you might be able to use the setResponseStatus utility to set the status code, but this only works in the Nuxt context and not when returning a Response object directly [setResponseStatus]. - If you need to forward specific headers or context, you can use event.$fetch in server routes, but again, not all headers are forwarded, and some are intentionally excluded for security reasons [Forwarding Context & Headers]. Limitation:
There is no officially supported or documented way to achieve a perfect proxy of all response details (status, headers, body) in a Nuxt/Nitro handler as of now. If this is a critical requirement, you may need to consider using a dedicated proxy server (like Nginx or a custom Node.js proxy) outside of Nuxt, or follow ongoing discussions and issues in the Nuxt/Nitro repositories for future improvements. If you have control over the frontend library, another workaround is to adapt it to handle the response format that Nitro can provide, but this may not always be feasible. In summary:
The knowledge sources do not provide a way to achieve the required behavior natively in Nuxt/Nitro at this time. This is a known limitation, and you may need to look for external solutions or monitor the Nuxt/Nitro ecosystem for updates. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@189762782642569217> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kérunix
KérunixOP4mo ago
I managed to do it like this
const path = event.path
const body = await readBody(event)
const response = await $fetch.raw(`http://my-new-api${path}`, {
method: event.method,
headers: event.headers,
body,
ignoreResponseError: true,
})

appendResponseHeader(event, 'precognition', 'true')
appendResponseHeader(event, 'vary', 'Precognition')
setResponseStatus(event, response.status)

return response._data
const path = event.path
const body = await readBody(event)
const response = await $fetch.raw(`http://my-new-api${path}`, {
method: event.method,
headers: event.headers,
body,
ignoreResponseError: true,
})

appendResponseHeader(event, 'precognition', 'true')
appendResponseHeader(event, 'vary', 'Precognition')
setResponseStatus(event, response.status)

return response._data
and its working fine

Did you find this page helpful?