I often find myself developing websites with React or Preact.
Lately, Preact has become my favorite, as it's much faster and lighter.
Sometimes I don't need to use a real server—for example, when I'm teaching a class, making a demo, or working without internet. It's also helpful when I need to simulate requests to a server, even if it's local.
To test my frontend, I create a simple PHP file that reads the data sent via POST and returns a JSON object similar to the one that the real server would provide.
(At the end of the article, there is a Node.js version of the PHP server.)
php -S 127.0.0.1 -t server.php
<?php
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!isset($data['username']) || !isset($data['password'])) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Username or password not provided'
]);
exit;
}
if ($data['username'] === 'nicolas' && $data['password'] === 'Correct-h0rse-Battery-staple') {
echo json_encode([
'status' => 'success',
'token' => 'fake_token'
]);
} else {
http_response_code(401);
echo json_encode([
'status' => 'error',
'message' => 'Invalid username or password'
]);
}
I like to use PHP because it has no dependencies, which is great for a demo. But this code doesn't work. The error is:
Response body is not available to scripts (Reason: CORS Failed)
After reading the title, you might have guessed that the error comes from CORS. But what is CORS?
CORS stands for Cross-Origin Resource Sharing. It is a security mechanism that allows you to control who can access the server's resources.
So, all I have to add to my PHP code is a couple of HTTP headers, right?
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: http://localhost:5173');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
My code still doesn't work! My request doesn't reach the server. But why?
Upon inspection, Axios is sending a request before the POST request I'm making. This first request is known as a "preflight request." It's an OPTIONS request; it only asks the server for the types of requests that are allowed. Knowing that, let's modify our headers and add a small if-statement:
// headers
header('Access-Control-Allow-Methods: POST, OPTIONS');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// fake login
Now it works! Generally, browsers try to enforce the "Same Origin" policy, which limits how JavaScript code that's running in one origin can access resources in another origin. (Origins can be domains, protocols, or ports.)
Finally, our PHP code looks like this:
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: http://localhost:5173'); // CORS
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!isset($data['username']) || !isset($data['password'])) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Username or password not provided'
]);
exit;
}
if ($data['username'] === 'nicolas' && $data['password'] === 'Correct-h0rse-Battery-staple') {
echo json_encode([
'status' => 'success',
'token' => 'fake_token'
]);
} else {
http_response_code(401);
echo json_encode([
'status' => 'error',
'message' => 'Invalid username or password'
]);
}
In case you don't have the PHP interpreter on your system, here's a version of the same program in Node.js with Express:
pnpm init && pnpm add express body-parser cors
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
const port = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(cors());
app.post("/login", (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
res
.status(400)
.json({ status: "error", message: "Username or password not provided" });
return;
}
if (username === "nicolas" && password === "Correct-h0rse-Battery-staple") {
res.json({ status: "success", token: "fake_token" });
} else {
res
.status(401)
.json({ status: "error", message: "Invalid username or password" });
}
});
app.listen(port, () => {
console.log(`Node.js server running at http://localhost:${port}`);
});
Cover credits:
Top comments (0)