0

There are other questions related to this one, but none are specific to this particular issue.

None that I could find. None of them ask the following question:

"Why does my server-side node-fetch API work in Node Express, but it immediately falls apart on the client side, returning this error message: "Uncaught ReferenceError: body is not defined"?

I am fairly confident the server-side API returns the right response.

I have tried putting body in the headers, both as "body: body" and as "body: JSON.stringify(body).

I also tried to stringify the API response. No dice.

In the Server Side Screenshot below, Developer tools in Chrome browser indicate a successful request to the NASA server. Under Network, the payload is right. Response is also correct, with the gallery populated by image URLs from the Mars Rover chosen by the user.

In the Client Side Screenshot, however, it returns a "Reference Error: body is not defined", regardless of what I try. As shown in the image, this message becomes an "Invalid Rover Name" error from NASA, with the gallery suddenly empty of image URLs.

In the command prompt, I see similar results.

The rover is defined correctly on the server side at http://localhost:3000/userinput, after I choose a rover and click submit. When I come back to http://localhost:3000/, the same rover name logged in the command prompt suddenly changes to "undefined."

I cannot see what I am doing wrong.

I suspect the problem lies in these two lines from the server-side API.

.then(response => response.json()) // send json to client
.then(gallery => response.send({gallery})) // send gallery to browser (so I can see it)

As I thought I understood, 1) the top one returns the json response to the client side, and 2) the second one sends the response as text to the browser.

Clearly, what I see in the browser is not what the client fetch API receives, but I don't how that can be when the NASA server is returning the right response. I see the correct image URLs coming back and sent to the browser at "/userinput."

I understand the difference between server and client Node Expres, or thought I did. But I don't understand what I am doing wrong here. If it is an async problem, I don't see it.

Can anyone shed some light on the problem?

Thank you for reviewing my question.

Please Note: I received a message from Stack Overflow that I am not allowed to embed images in my questions yet.

But it has included links to those images.

I did know when I wrote the question.

Relevant Code from HTML Form:

<form action="http://localhost:3000/userinput" method="POST">

<button id="submit" name="submit">Send Images</button>

Relevant Server Side API Code:

    app.use('/', express.static(path.join(__dirname, '../public')));
    
    app.use(bodyParser.urlencoded({ extended: true }));
    
    app.use(cors("*"));
    
    app.post('/userinput', async (request, response) => {
    
      const rover = request.body.rover
    
      console.log("SERVER SIDE ROVER: ", rover)
    
      try {
        const gallery = await fetch(`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover}/latest_photos?api_key=DH46IQlx0gMyXPmLAgkxXDSPo2OrbIjPs8OJLj6L`)
        .then(response => response.json()) // send json to client
        .then(gallery => response.send({gallery})) // send gallery to browser (so I can see it)
      } catch (err) {
        console.log(`RESPONSE STATUs: ${response.status}`, err);
      }
      return response.json();
    
    });
    
    const port = 3000;
    
    app.listen(port, () => {
      console.log(`Server running on port${port}`);

Relevant Client Side API code:

// Single async higher-order/callback function
// Retrieves image data and update store/new state
// getRoverPhotos is Higher Order function.
// RoverPhotos as callback function.

const getRoverPhotos = async (state, fn) => {

    const response = await fetch("http://localhost:3000/userinput", {
        method: "POST",
        headers: {'Content-Type': 'application/JSON' }
})

console.log("RESPONSE CLIENT: ", response)

const jsonImageData = await response.json()
console.log("JSON Image Data")
console.log(jsonImageData)

const ImageData = await jsonImageData.gallery.latest_photos.map(photo => {
    return photo.img_src;

});

    const newState = jsonImageData.gallery
    updateStore(store, newState)
    return ImageData
}

getRoverPhotos(store, updateStore)

Server Side Screenshot

Shows NASA response in browser and successful gallery of image URLs returned in Developer Tools => Network => Response

Client Side Screenshot

Shows failed response. Gallery emptied under Developer Tools => Console

  • ```const response = await fetch("http://localhost:3000/userinput", { method: "POST", headers: {'Content-Type': 'application/JSON' } })``` — There's definitely no body there. – Quentin Jul 25 '22 at 16:21
  • Thanks for your comment. I know my question is too long, but I did mention that I have tried "putting body in the headers, both as "body: body" and as "body: JSON.stringify(body)" and I get the same results. I even bought the O'Reilly book on Web Development in Node & Express and followed his examples. Is there a better way? Sincerely, thanks for reviwing the question. – Robert Pfaff Jul 25 '22 at 17:40
  • "Uncaught ReferenceError: body is not defined" — You haven't defined `body` anywhere. The error message seems pretty clear. What data are you trying to send? – Quentin Jul 25 '22 at 19:01
  • Sorry if I missed the point. Can you give me an example? As I wrote above, I have included body in the headers in prior attempts and in several ways, if that's what you mean. Same errors. I don't know why, but the client-side API worked (without headers) until I fixed the server-side API. As I understand the sequence, the form sends the username, rover name and camera to the server-side API, which retrieves the NASA images. NASA returns the images to the server-side API, which sends it to the client. The client processes the response. The code displays the gallery in the browser. – Robert Pfaff Jul 25 '22 at 20:47
  • I checked the MDN page. Body is discussed as an option, not a requirement. The requirement is response.json() which extracts body from response in json format. I could be wrong. My mind is fried. But, as I understand it, it's not essential to define body explicitly in the init. It is implicity defined in the response object and extracted by the json() method. Am I wrong? Please tell me. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#body – Robert Pfaff Jul 25 '22 at 20:54
  • While sending a body is optional, it doesn't make sense to make a POST request unless you send one, your `Content-Type` header says that your non-existent body is made up of JSON, and your server side code says `const rover = request.body.rover` so is trying to read data from the body you aren't sending. – Quentin Jul 25 '22 at 21:36
  • Oh, great. So I was looking at the whole thing backwards. If I understand it now, 1) the user clicks the button, and 2) the form data goes to client-side API. Then, 3) it posts to the server, and then 4) to NASA and 5) then back to the server. Then, back to the client, where its processed and displayed to the user? Not exactly backwards, but I missed a big step. Do I have that right now? Well, that helps a lot. Thank you. – Robert Pfaff Jul 26 '22 at 00:09

1 Answers1

0

I'll answer my question since there were several issues.

For the most part, I had forgotten one step, namely preparing the request for the server side on the client-side before sending it to the server-side api.

This preparing, sending and recept all takes place in one function (getRoverPhotos()) with the server-api as an intermediary to the NASA server.

The body was not defined because I had my head stuck in the minutia and not on how to best structure the request. In the end, I was sending the request straight to the server side without preparing the body as a JSON string/object.

It also looked like it was working because I saw the right results at "localhost:3000/userinput" address on the server side, but it fell when I returned to the client side and reset the request. I was correct about the two lines in the server-side app.post being the main culprits. NASA was sending the correct results to the browser at the server-side address, but it was not returning to the client side in the right format. Hence, the "body not defined" error even though I could see it in my browser on the server side.

I hope that makes. It makes sense to me...now.

Thanks to Quentin in the comments for pointing me down this path.

Here is the relevant code from my form.

<form name="form" id="form" method="POST" action="/results"></form>

<input id="submit" name='submit' type="button" value="Transmit Images" onclick="getRoverPhotos(store,updateStore)" />

There are two important points about the form:

  1. The form action attribute contains the new server-side API address.
  2. The onclick button attribute invokes the function that sends the request to that address.

Here is the relevant code from the client-side.

The button event submits the user input to the client-side function getRoverPhotos() which converts it into a JavaScript object, strigifies it, then sends it to fetch the data from the server-side api.

The response includes the latest photos from the Mars rover chosen by the user which is ultimately displayed in a gallery with related info (e.g. date taken).

const getRoverPhotos = async (store, fn) => {

    const name = document.getElementById('name').value;
    const rover = document.getElementById('rover').value;
    const camera = document.getElementById('camera').value;

    const userInfo = new Object();
    userInfo.name = name
    userInfo.rover = rover
    userInfo.camera = camera

    const body = JSON.stringify(userInfo)

    const options = {
      method: "POST",
      headers: {'Content-Type': "application/json"},
      body: body
    };

    const response = await fetch('/results', options)
    console.log("Okay? ", response.ok)

    const json = await response.json();
    console.log(json)

    if (response.ok === true) {
        const newState = json
        updateStore(store, newState)
        console.log("Response OK. New Store: ", store)
        return store
    } else {
        throw new Error()
}};

Here is the relevant code from the server side.

The two "then" lines are the culprits I mention above. One returns the data as text ton the browser at the server-side address. But the other was failing to return the data the client side.

try {
    const gallery = await fetch(`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover}/latest_photos?api_key=DH46IQlx0gMyXPmLAgkxXDSPo2OrbIjPs8OJLj6L`)
    **.then(response => response.json()) // send json back to client
    .then(gallery => response.send({gallery})) // send gallery to browser (so I can see it)**
  } catch (err) {
    console.log(`RESPONSE STATUS: ${response.status}`, err);
  }
  console.log("GALLERY:")
  console.log(response.json())
  return response.json()

});

Once it receives the request, the app.post/results api on the server-side fetches the data from NASA's servers based on the rover chosen by the user.

The app.post/results api function then returns the data BACK to the getRoverPhotos function on the client as JSON, where its used to update the main memory object (called store) in the callback function updateStore().

That's it, and it works!