Home > front end >  NodeJS error while using Express JS | "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after
NodeJS error while using Express JS | "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after

Time:12-01

I am beginning learning the basics of developing APIs and am following along with a video on YouTube by a developer named Ania Kubow. There are three javascript libraries in use which are ExpressJS, Cheerio & Axios. I have picked up what she is telling us and going well enough. I am however, getting an error when executing the code so far. Below here is the main part of the example API:

Note:The "app" variable is referring to Express JS

app.get('/news', (req, res) => {
    axios.get('https://www.theguardian.com/environment/climate-crisis')
        .then((response) => {

            // Store the html retrieved via the axios http GET request
            const html = response.data;

            // Load the retrieved html into the cheerio instance and store to $ cheerio selector
            const $ = cheerio.load(html);

            // DEBUG purpose stuff
            var loopCounter = 0;
            var climate_A_Elements = $('a:contains(climate)', html).length;
            console.log('Number of articles found: '   climate_A_Elements);
            //

            // Get all articles that contain 'climate' in the <a /> element
            var allFoundArticles = $('a:contains("climate")', html);

            // Iterate through all found articles using cheerio forEach loop
            allFoundArticles.each(function () {

                // Assign article title and url it is located at to variables
                var title = $(this).text();
                var url = $(this).attr('href');

                // Push the previously defined vars to the previously defined array
                articlesData.push({
                    title,
                    url,
                });

                // Output the article details to page as response
                res.json(articlesData);

                // Add to loop count for debugging purposes and log in console
                loopCounter  = 1;
                console.log('Loops: '   loopCounter);

            }).catch(err => {
                    console.log(err);
            })
        })
})

After completing one iteration of the Cheerio each loop, the application errors and gives the below error output:

node:internal/errors:484
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:393:5)
    at ServerResponse.setHeader (node:_http_outgoing:644:11)
    at ServerResponse.header (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\express\lib\response.js:794:10)
    at ServerResponse.json (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\express\lib\response.js:275:10)
    at Element.<anonymous> (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\index.js:65:21)
    at LoadedCheerio.each (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\cheerio\lib\api\traversing.js:519:26)
    at C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\index.js:52:30
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

Node.js v18.12.1
[nodemon] app crashed - waiting for file changes before starting...

I have of course, left a question as a comment on the video mentioned above, but realistically I don't expect to receive a response, so after trying to find the issue for a while decided to try here for some help and guidance.

Any guidance would be greatly appreciated. If you require anything to help further please ask.

Thankyou.

I tried changing to the version of node installed on the video mentioned to see if it was a version issue, but it didn't solve anything. I tried refactoring the code and stepping through a few times with no results and tried searching the web but couldn't find the answer to this particular issue.

CodePudding user response:

You are sending response mulitple times in a single request Try to push you result in an array then send it after your loop executed successfully. Try this code.

app.get('/news', (req, res) => {
  axios.get('https://www.theguardian.com/environment/climate-crisis')
    .then((response) => {
      // artical data array
      const articlesData = [];
      // Store the html retrieved via the axios http GET request
      const html = response.data;

      // Load the retrieved html into the cheerio instance and store 
      //to $ cheerio selector
      const $ = cheerio.load(html);

      // DEBUG purpose stuff
      var loopCounter = 0;
      var climate_A_Elements = $('a:contains(climate)', html).length;
      console.log('Number of articles found: '   climate_A_Elements);
      //

      // Get all articles that contain 'climate' in the <a /> element
      var allFoundArticles = $('a:contains("climate")', html);

      // Iterate through all found articles using cheerio forEach 
      //loop
      allFoundArticles.each(function () {

        // Assign article title and url it is located at to variables
        var title = $(this).text();
        var url = $(this).attr('href');

        // Push the previously defined vars to the previously defined 
        //array
        articlesData.push({
          title,
          url,
        });

        // Output the article details to page as response


        // Add to loop count for debugging purposes and log in 
        //console
        loopCounter  = 1;
        console.log('Loops: '   loopCounter);

      })
      res.json(articlesData);
    }).catch(err => {
      console.log(err);
    })
})

CodePudding user response:

You should always ask yourself why you got this error, in your case because you put res.json inside the loop, you should always call res.json /sending the response once time , The recommendation from folks will help you,

CodePudding user response:

Asad's answer is correct; you're sending responses for each element in the loop. The request is completed after the first iteration, so when the second iteration occurs, Express complains that you're trying to respond a second time to an already-completed response.

The minimal failing pattern is:

app.get("/news", (req, res) => {
  for (let i = 0; i < 2; i  ) {
    res.json({message: "this won't work"});
  }
});

The fix is:

app.get("/news", (req, res) => {
  for (let i = 0; i < 2; i  ) {
    // do whatever you need to in the loop
  }

  // respond once after the loop is finished
  res.json({message: "this works"});
});

Here's a complete, runnable example:

const app = require("express")();
const axios = require("axios");
const cheerio = require("cheerio");

app.get("/news", (req, res) => {
  axios
    .get("https://www.theguardian.com/environment/climate-crisis")
    .then((response) => {
      // Store the html retrieved via the axios http GET request
      const html = response.data;

      // Load the retrieved html into the cheerio instance and store to $ cheerio selector
      const $ = cheerio.load(html);

      // DEBUG purpose stuff
      var loopCounter = 0;
      var climate_A_Elements = $("a:contains(climate)", html).length;
      console.log("Number of articles found: "   climate_A_Elements);
      //

      // Get all articles that contain 'climate' in the <a /> element
      var allFoundArticles = $('a:contains("climate")', html);

      // Iterate through all found articles using cheerio forEach loop
      ///////////////////////////
      const articlesData = []; // <--------- Added
      ///////////////////////////

      allFoundArticles
        .each(function () {
          // Assign article title and url it is located at to variables
          var title = $(this).text();
          var url = $(this).attr("href");

          // Push the previously defined vars to the previously defined array
          articlesData.push({
            title,
            url,
          });

          // Add to loop count for debugging purposes and log in console
          loopCounter  = 1;
          console.log("Loops: "   loopCounter);
        })

      // Output the article details to page as response

      //////////////////////////
      res.json(articlesData); // <--------- Moved down
      //////////////////////////
    });
});
app.listen(3000);

Test it:

$ curl localhost:3000/news | jq 'length'
14
$ curl localhost:3000/news | jq '.[0].title'
"Australia news  Australia???s banks likely to reduce lending to regions and sectors at risk of climate change impacts, regulator says "

Dropping the extraneous code gives a pretty simple result:

const app = require("express")();
const axios = require("axios");
const cheerio = require("cheerio");

app.get("/news", (req, res) => {
  const url = "https://www.theguardian.com/environment/climate-crisis";
  axios.get(url).then(({data}) => {
    const $ = cheerio.load(data);
    const articles = [...$('a:contains("climate")')].map(e => ({
      title: $(e).text().trim(),
      url: $(e).attr("href"),
    }));
    res.json(articles);
  })
  .catch(err => {
    res.status(500).json({message: err.message});
  });
});
app.listen(3000);

Or with async/await:

app.get("/news", async (req, res) => {
  try {
    const url = "https://www.theguardian.com/environment/climate-crisis";
    const {data} = await axios.get(url);
    const $ = cheerio.load(data);
    const articles = [...$('a:contains("climate")')].map(e => ({
      title: $(e).text().trim(),
      url: $(e).attr("href"),
    }));
    res.json(articles);
  }
  catch (err) {
    res.status(500).json({message: err.message});
  }
});

CodePudding user response:

I had a problem with the same error. You are trying to send a response multiple times with res.json(articlesData)

Try this:

app.get('/news', (req, res) => {
  axios.get('https://www.theguardian.com/environment/climate-crisis')
      .then((response) => {

          // Store the html retrieved via the axios http GET request
          const html = response.data;

          // Load the retrieved html into the cheerio instance and store to $ cheerio selector
          const $ = cheerio.load(html);

          // DEBUG purpose stuff
          var loopCounter = 0;
          var climate_A_Elements = $('a:contains(climate)', html).length;
          console.log('Number of articles found: '   climate_A_Elements);
          //

          // Get all articles that contain 'climate' in the <a /> element
          var allFoundArticles = $('a:contains("climate")', html);

          // Iterate through all found articles using cheerio forEach loop
          allFoundArticles.each(function () {

              // Assign article title and url it is located at to variables
              var title = $(this).text();
              var url = $(this).attr('href');

              // Push the previously defined vars to the previously defined array
              articlesData.push({
                  title,
                  url,
              });

              // Output the article details to page as response

              // Add to loop count for debugging purposes and log in console
              loopCounter  = 1;
              console.log('Loops: '   loopCounter);

          }).then(res.json(articlesData))
          .catch(err => {
                  console.log(err);
          })
      })
})
  • Related