Eroare de CORS pe localhost

Pe frontend am un buton, iar când dau click vreau să-mi schimbe o valoare setată în backend.
Deși am făcut setările de CORS în ambele părți, nu mă lasă browserul să fac requestul de POST.
În Firefox îmi zice

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1/button_state. (Reason: CORS request did not succeed).

În ‘public/index.html’ este:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Is button clicked?</title>
    <style>
      body {
        text-align: center;
      }
    </style>
  </head>

  <body>
    <button>Change status</button>
    <script>
      document.querySelector("button").addEventListener("click", () => {
        fetch("http://127.0.0.1/button_state", {
          method: "POST",
          mode: "cors",
          headers: {
            "Content-type": "application/json charset=UTF-8"
          },
          body: "buttonState=clicked"
        }).catch(err => console.error(`fetch() error: ${err}`));
      });
    </script>
  </body>
</html>

iar în ‘server.js’:

const http = require("http");
const fs = require("fs");
const storage = require("node-persist");
const hostname = "127.0.0.1";
const port = "3000";

(async () => {
  await storage.init({ logging: true, ttl: true });

  let virtualDB = await storage.getItem("isActive");

  if (!virtualDB) {
    virtualDB = await storage.setItem("isActive", true);
  }

  virtualDB = await storage.getItem("isActive");

  console.log("virtualDB", virtualDB);

  /**
   *  if the frontend does not work (CORS issues) use curl
   *  curl -d '{"buttonState":"clicked"}' -H "Content-Type: application/json" -X POST http://localhost:3000/button_state
   *
   *  */

  const server = http
    .createServer((req, res) => {
      if (req.url === "/") {
        fs.readFile("./public/index.html", "UTF-8", function(err, html) {
          if (err) {
            throw err;
          }
          res.writeHead(200, { "Content-Type": "text/html" });
          res.end(html);
        });
      } else if (req.url === "/button_state" && req.method === "POST") {
        let jsonString = "";

        req.on("data", function(data) {
          jsonString += data;
        });

        req.on("end", function() {
          console.log("data from frontend", JSON.parse(jsonString));
          res.writeHead(200, {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET,PUT,POST,DELETE"
          });
          res.write("\n[request received successfully]\n");
          res.end();
        });
      } else {
        res.writeHead(404, { "Content-Type": "text/html" });
        res.end("No Page Found");
      }
    })
    .listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
})();

Singura dependință pentru backend este node-persist, ceva asemănător cu localStorage din browser.

Face browser-ul request de OPTIONS?

vs

as zice ca iti lipseste portul (probabil HTML-ul e servit de alt proces fata de “server.js”).

Așa este. Adăugând și portul în requestul de POST, dispare problema de CORS.
Mai departe vreau ca de fiecare dată când dau click pe buton să mi se schimbe valoarea care o servesc de pe ruta /button_state alternativ în true apoi false și invers.

Am incercat să scimb valoarea lui virtualDB din interiorul req.on("end", () => {}) dar fără rezultat.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Is button clicked?</title>
    <style>
      body {
        text-align: center;
      }
    </style>
  </head>

  <body>
    <button>Change status</button>
    <script>
      let buttonStateOnServer;

      fetch("http://127.0.0.1:3000/button_state")
        .then(data => data.json())
        .then(res => {
          buttonStateOnServer = res;
          console.log("buttonStateOnServer", buttonStateOnServer);
        })
        .catch(err => console.error(`GET error: ${err}`));

      document.querySelector("button").addEventListener("click", () => {
        fetch("http://127.0.0.1:3000/button_state", {
          method: "POST",
          mode: "cors",
          headers: {
            "Content-type": "application/json charset=UTF-8"
          },
          body: `buttonState=${!buttonStateOnServer}`
        }).catch(err => console.error(`POST error: ${err}`));
      });
    </script>
  </body>
</html>

server.js

const http = require("http");
const fs = require("fs");
const { parse } = require("querystring");
const storage = require("node-persist");
const hostname = "127.0.0.1";
const port = "3000";

(async () => {
  await storage.init({ logging: true, ttl: true });

  let virtualDB = await storage.getItem("isActive");

  if (!virtualDB) {
    virtualDB = await storage.setItem("isActive", true);
  }

  virtualDB = await storage.getItem("isActive");

  console.log("virtualDB", virtualDB);

  /**
   *  if the frontend does not work (CORS issues) use curl
   *  curl -d '{"buttonState":"clicked"}' -H "Content-Type: application/json" -X POST http://localhost:3000/button_state
   *
   *  */

  const server = http
    .createServer((req, res) => {
      if (req.url === "/") {
        fs.readFile("./public/index.html", "UTF-8", function(err, html) {
          if (err) {
            throw err;
          }
          res.writeHead(200, { "Content-Type": "text/html" });
          res.end(html);
        });
      } else if (req.url === "/button_state" && req.method === "GET") {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify(virtualDB));
      } else if (req.url === "/button_state" && req.method === "POST") {
        let stringResponse = "";

        req.on("data", function(data) {
          stringResponse += data;
        });

        req.on("end", function() {
          const buttonStateFrontend = parse(stringResponse).buttonState;
          console.log("buttonStateFrontend", buttonStateFrontend);
          res.writeHead(200, {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Methods": "GET,PUT,POST,DELETE"
          });
          res.write("\n[request received successfully]\n");
          /* because parse() returns 'true' or 'false' we have to make the conversion */
          res.end(JSON.stringify(buttonStateFrontend === "true"));
        });
      } else {
        res.writeHead(404, { "Content-Type": "text/html" });
        res.end("No Page Found");
      }
    })
    .listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
})();

Problema ta este(era daca aveai portul setat si nu erai pe localhost)

“Access-Control-Allow-Origin”: “*”,

Nu poti sa precizezi * pentru browserele de acum doar asa, trebuie sa specifici intr-o lista fiecare domeniu de pe care vrei sa ti se poata trimite request-uri. Exceptie daca setezi Request.credentials pe "omit" la fetch pe front end . (ceea ce nu recomand)

Altele :
1.Scoate din server-ul de api servirea fisierului html, serveste fisierul intr-o alta instanta de node pe alt port ca incurci lucrurile asa si arata oribil.
2. Foloseste express, nu http daca nu vrei sa scrii tu un framework de REST. Iti va fi mult mai greu sa intelegi ce se intampla cu http.(ai middleware de rutare, sesiuni si async in express si te ajuta mult)
3. Trebuie sa structurezi codul cu http, prima data ar trebui sa separi tot ce ai scris pentru createServer intr-un handler.js si sa creezi cate un callback (un promise) pe care sa-l chemi la fiecare ruta + metoda, e.g :

 } else if (req.url === "/button_state" && req.method === "POST") {
    saveStatetoDb(req.body).then(raspuns => { res.end(raspuns) }).catch(eroare => {
   res.status(503).send('Baza de date este offline: ' + eroare);
  } 
}
function saveStateToDb(body) {
  return new Promise((resolve, reject) => {
   storage.setItem("isActive", JSON.stringify(body)).then(() => resolve('salvat')).catch(() => reject('nu merge'))
})
}

Eventual poti rescrie ^ cu async :

async function saveStateToDb(body) {
   try {
     const update = await storage.setItem("isActive", JSON.stringify(body));
     return update;
 } catch(error) {
     return error; 
   }