Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 BUG: api route request body is empty with node adapter SSR #3619

Closed
1 task done
Scalamando opened this issue Jun 16, 2022 · 13 comments · Fixed by #4023
Closed
1 task done

🐛 BUG: api route request body is empty with node adapter SSR #3619

Scalamando opened this issue Jun 16, 2022 · 13 comments · Fixed by #4023
Assignees
Labels
- P4: important Violate documented behavior or significantly impacts performance (priority)

Comments

@Scalamando
Copy link

Scalamando commented Jun 16, 2022

What version of astro are you using?

1.0.0-beta.40, 1.0.0-beta.47

Are you using an SSR adapter? If so, which one?

Node (v1.2.0)

What package manager are you using?

npm, yarn classic

What operating system are you using?

WSL Debian / Windows

Describe the Bug

Received POST requests that should contain a json body (and a Content-Type: application/json header) have an empty body in SSR mode using the node adapter.

Example request:

const response = await fetch("/test", {
  method: "post",
  body: JSON.stringify({ 
    test: "Baby don't hurt me, no more"
  }),
  headers: { "Content-Type": "application/json" }
});

The api endpoint receives the following:

export const post : APIRoute = async ({ request }) => {
  console.log(request.body); // Logs: null
  const { test } = await request.json(); // SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

  ...
}

Run the following in the StackBlitz console:

  • npm run build
  • Ignore the premium warning
  • node server.mjs

Just to be sure I checked the response using the express.json() middleware and the body is available there

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-1qng5x?file=src/pages/test.ts

GitHub Repo with MRE

https://github.com/Scalamando/astro-ssr-node-issue-repro

Simply run yarn && yarn start-ssr

Participation

  • I am willing to submit a pull request for this issue.
@Scalamando Scalamando changed the title 🐛 BUG: JSON POST body is empty with node adapter SSR 🐛 BUG: JSON POST body is empty with node adapter SSR build Jun 16, 2022
@Scalamando Scalamando changed the title 🐛 BUG: JSON POST body is empty with node adapter SSR build 🐛 BUG: JSON POST body is empty with node adapter SSR in a production build Jun 16, 2022
@Scalamando Scalamando changed the title 🐛 BUG: JSON POST body is empty with node adapter SSR in a production build 🐛 BUG: JSON POST body is empty with node adapter SSR Jun 16, 2022
@Scalamando
Copy link
Author

Scalamando commented Jun 16, 2022

Just did some more testing and it looks like no type of body is received at all.

I did tests for

  • Multipart Form Data
  • Form URL Encoded
  • Plain Text

with Insomnia and none of them ever showed up in request.body.

I also tried a raw http server:

http.createServer(function(req, res) {
  ssrHandler(req, res, err => {
    if(err) {
      res.writeHead(500);
      res.end(err.toString());
    } else {
      res.writeHead(404);
      res.end();
    }
  });
}).listen(8080);

but that didn't change anything.

@Scalamando Scalamando changed the title 🐛 BUG: JSON POST body is empty with node adapter SSR 🐛 BUG: JSON/Form/anything POST/PUT body is empty with node adapter SSR Jun 16, 2022
@Scalamando
Copy link
Author

Scalamando commented Jun 18, 2022

After looking into it a bit more, I found the reason.

https://github.com/withastro/astro/blob/main/packages/astro/src/core/app/node.ts#L8

function createRequestFromNodeRequest(req: IncomingMessage): Request {
	let url = `http://${req.headers.host}${req.url}`;
	const entries = Object.entries(req.headers as Record<string, any>);
	let request = new Request(url, {
		method: req.method || 'GET',
		headers: new Headers(entries),
	});
	return request;
}

If the request is a node request (http.IncomingMessage), the body is omitted in the conversion to a fetch api request. After looking into this issue even more, the solution seems to be non-trivial and/or I lack the knowledge to tackle this problem.

@Scalamando Scalamando changed the title 🐛 BUG: JSON/Form/anything POST/PUT body is empty with node adapter SSR 🐛 BUG: body is empty with node adapter SSR Jun 18, 2022
@Scalamando Scalamando changed the title 🐛 BUG: body is empty with node adapter SSR 🐛 BUG: api route request body is empty with node adapter SSR Jun 19, 2022
@tony-sull tony-sull added - P4: important Violate documented behavior or significantly impacts performance (priority) s1-small labels Jun 20, 2022
@tony-sull
Copy link
Contributor

Thanks for all the investigation @Scalamando! Sorry you hit this bug, the lack of body in the final request is definitely an issue 😬

We're pushing hard to knock out any p4 and p5 issues that pop up before the 1.0 release so we should have this fixed soon!

@Scalamando
Copy link
Author

Scalamando commented Jun 20, 2022

Sounds good, no worries!

For anybody else that is facing this problem: Although suboptimal I solved this temporarily by sending the stringified json data in a query string and parsing it in the endpoint:

Client

await fetch(
    `/register?data=${encodeURIComponent(JSON.stringify(dataObj))}`,
    { method: "post" }
);

API Endpoint

export const post: APIRoute = async ({ request }) => {
    const { data } = Object.fromEntries(new URL(request.url).searchParams);
}

@matthewp
Copy link
Contributor

Sorry about this, I'll take this one on.

@314ga
Copy link

314ga commented Aug 1, 2023

I am having a similar issue, using node adapter SSR with a hybrid output target. I am using an example of Firebase authentication (Astro Firebase). It works perfectly with "npm run dev", hovewer when I run "npm run build" and "node ./dist/server/entry.mjs", I am able to access only plain URL. I tried the workaround provided by Scalamando but the query string disappears too. Any idea what am I doing wrong?

CLIENT:
const response = await fetch(/api/auth/login?token=${idToken}\, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: Bearer ${idToken},
},
});

SERVER:
export const get: APIRoute = async ({ request, cookies, redirect }) => {

if(!request.headers.get("authorization"))
{
return new Response(
JSON.stringify({
error: request.url,
author: request.headers.get("authorization"),
referer: request.headers.get("referer"),
cookie: request.headers.get("cookie")
}),
{ status: 401 }
);
}

RESPONSE: {"error":"http://localhost:3000/api/auth/login","author":null,"referer":null,"cookie":null}

@tayhimself
Copy link

tayhimself commented May 27, 2024

@314ga Did you find a fix for the issue? I'm also seeing this issue. Works with the dev server, but on prod there is no body in the form request. node adapter, server output and can't get the body.

I added a middleware and I can see both types of form requests in it, but nothing on the prod.
Headers look fine

accept: '*/*', connection: 'Keep-Alive', 'content-length': '107', 'content-type': 'multipart/form-data; boundary=X-INSOMNIA-BOUNDARY', host: 'xxx'
or

accept: '*/*', connection: 'Keep-Alive', 'content-length': '15', 'content-type': 'application/x-www-form-urlencoded', host: 'xxx'

export async function onRequest(context, next) {
    const req = context.request.clone()
    const headers = Object.fromEntries(req.headers)
    console.log(headers)
    if (req.method === "POST" && req.headers.get('content-type').includes("multipart/form-data")) {
        const formData = await req.formData()

        console.log(formData)
    }
    if (req.method === "POST" && req.headers.get('content-type').includes("application/x-www-form-urlencoded")) {
        const body = await req.text()
        const fields = new URLSearchParams(body)
        console.log("x-url",Object.fromEntries(fields))
    }
    return next()
}

@rimzzlabs
Copy link

rimzzlabs commented Jun 25, 2024

Closed the issue and never give the solution, oh what A great community, I just tried Astro for the first time, but then discovered the same problem just like this issue, everytime I hit the api, it always shows me dis error:
image

// SERVER

export async function POST({ request }) {
const body = await request.json();  // put this on top so it will throw error inside POST fun

try {
    return new Response(JSON.stringify(body), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(
      JSON.stringify({
        text: "Error,",
        message: "Unknown error",
      }),
      { status: 500, headers: { "Content-Type": "application/json" } },
    );
  }
}

// CLIENT

type Props =  Readonly<{
    reactionKey: string;
    alt: string;
    emoji: string;
}>

async function onClick(){
 await fetch(`/api/reaction`, {
      headers: { "Content-Type": "application/json" },
      method: "POST",
      body: JSON.stringify(props),
    });
}

@bholmesdev
Copy link
Contributor

Hey @tayhimself and @rimzzlabs, thanks for reporting! Do you mind seeing if you can reproduce in a minimal example using an astro.new template? That should help us find whether it's a general issue, or something more unique to your setups.

@rimzzlabs
Copy link

Hey @tayhimself and @rimzzlabs, thanks for reporting! Do you mind seeing if you can reproduce in a minimal example using an astro.new template? That should help us find whether it's a general issue, or something more unique to your setups.

Hey, thanks for the update, I will be back with a codesandbox this evening

@rimzzlabs
Copy link

Hey @tayhimself and @rimzzlabs, thanks for reporting! Do you mind seeing if you can reproduce in a minimal example using an astro.new template? That should help us find whether it's a general issue, or something more unique to your setups.

Here you go sir https://stackblitz.com/edit/withastro-astro-njgj6d?file=src%2Fpages%2Findex.astro,src%2Flayouts%2FLayout.astro,package.json,src%2Fcomponents%2FReactionButton.tsx,astro.config.mjs,src%2Fpages%2Fapi%2Freaction.ts&title=Astro%20Starter%20Kit:%20Basics

@bholmesdev
Copy link
Contributor

@rimzzlabs Hm, I downloaded this sample you sent and I was unable to replicate. Here is what I did:

  1. I updated the configuration to use the Node adapter with output: 'server'
  2. I ran npm run build followed by npm run preview
  3. I tried POSTing to api/reaction by running curl http://localhost:4321/api/reaction -X POST -d '{"hello": "world"}'

This seemed to parse my JSON input correctly. Is it possible you're using output: 'hybrid' for your application? If so, you'll need to add export const prerender = false to your api/reaction file.

@rimzzlabs
Copy link

@rimzzlabs Hm, I downloaded this sample you sent and I was unable to replicate. Here is what I did:

  1. I updated the configuration to use the Node adapter with output: 'server'
  2. I ran npm run build followed by npm run preview
  3. I tried POSTing to api/reaction by running curl http://localhost:4321/api/reaction -X POST -d '{"hello": "world"}'

This seemed to parse my JSON input correctly. Is it possible you're using output: 'hybrid' for your application? If so, you'll need to add export const prerender = false to your api/reaction file.

I didn't read the docs carefully, turns out I can export the prerender variable from a .ts file (endpoints)?
Thanks for your help @bholmesdev , it works well now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
- P4: important Violate documented behavior or significantly impacts performance (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants