I am starting my end-of-year study project (due by mid-July :)).
I’m trying to make a small site, and to stand out I’m not doing it in php but with node.js.
I discovered this technology and I am already facing a first problem.
(After having trouble coding DB) I want to do an authentication but I am unable to retrieve the data from the <form>
.
When I do a log
of my req.body.password
, it returns undefined
and for req.body
, {}
.
I do not understand why.
Here is the code I already made:
index.js
require('dotenv').config();
const express = require("express");
const https = require("https");
const path = require("path");
const fs = require("fs");
const port = process.env.PORT || 4242;
const app = express();
// Analyser les corps de requête au format URL-encoded
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.set('views', './views');
app.set('view engine', 'ejs');
app.use('/', require('./controllers/homeController'));
app.use('/images', express.static(path.join(__dirname, 'images')));
const sslServer = https.createServer({
key: fs.readFileSync(path.join(__dirname, 'cert', 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'cert', 'cert.pem'))
}, app);
sslServer.listen(port, () => {
console.log("Le serveur fonctionne !");
console.log(process.env.PORT)
});
home.ejs
...
<main class="main">
<div class="parent">
<form method="post" class="enfant" id="loginForm">
<input type="password" name="password" placeholder="password" id="pwd" required>
<button type="submit" id="bouton">valider</button>
</form>
</div>
</main>
...
<script>
document.getElementById("loginForm").addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(this);
fetch('/', {
method: "POST",
body: formData
})
.then(response => {
if (response.ok) {
window.location.href = "/dashboard"; // Rediriger vers la page du tableau de bord si la connexion réussit
} else {
return response.text();
}
})
.then(message => {
document.getElementById("message").innerText = message;
//alert(message);
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
User.js
const mysql = require("mysql2/promise");
require('dotenv').config();
class User {
constructor(){
this.dbConnect = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
}
async get(){
try{
const connexion = await this.dbConnect.getConnection();
const [res, fields] = await connexion.execute(`SELECT * FROM User;`);
connexion.release();
return await res;
}
catch(err){
return await err;
}
finally{
this.dbConnect.end();
}
}
}
module.exports = User;
homeController.js
const express = require('express');
const limiter = require('express-rate-limit');
const User = require('../models/User');
const app = express();
const homeRegex = /^/(home)?$/;
// Middleware pour analyser les corps de requête
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//limiter à 5 tentatives de log sur 5 minutes
const signInlimiter = limiter({
windowMs: 5*60*1000,
limit: 1,
message: (req, res) => {
date = (new Date(req.rateLimit.resetTime)).toLocaleTimeString();
return `Trop de tentatives ! Vous ne pourrez pas retenter avant ${date} ⏰.`;
},
keyGenerator: (req, res) => {req.ip}
});
//REQUETES
app.get(homeRegex, (req, res) => { res.render("home.ejs", { message: "" }); });
// 429 par défaut
app.post(homeRegex, signInlimiter, async (req, res) => {
// Vérifier les informations de connexion et renvoyer une réponse appropriée (pour l'instant pwd = password)
const password = req.body.password;
console.log(req.body)
try {
const userData = await getUser();
const pwdData = userData[0].pwd;
//non haché
if (password === pwdData) {
res.status(200).send("Bienvenue 💙🤍❤️");
} else {
res.status(401).send("Invalide 💀💀💀");
}
} catch (err) {
}
});
async function getUser() {
let user = new User();
try {
const userData = await user.get();
return JSON.parse(JSON.stringify(userData, null, 2));
} catch (err) {
console.log(err);
return null;
}
}
module.exports = app;
I’ve already asked ChatGPT but obviously it’s not all powerful ;b
Can you explain to me why it doesn’t work.
Everything seems ok to me though (from my point of view, new to this tech).
Thanks.
2
Answers
With your answers I was able to modify the code so that everything works. Thanks.
Here are the changes made:
home.ejs
To explain the issue, I’ll first explain some details about how the
express.json()
andexpress.urlencoded()
middleware work. Or just skip to the solution.Background
When you make a HTTP POST request, the body data itself is basically just text or bytes, and the
Content-Type
header is used to tell the server how it should interpret that data. The various body parsing middleware that Express provides look at this header to decide whether they need to take action or not.For example, looking at the ExpressJS docs, the default Content-Type values they’re looking for are the following:
express.json()
application/json
req.body
express.urlencoded()
application/x-www-form-urlencoded
?
in an URL, aka "query string") into an object toreq.body
express.raw()
application/octet-stream
Buffer
express.text()
text/plain
req.body
Typically if using a
<form>
element to make a POST (i.e. allowing the browser to submit the form), you can specify one of the following via itsenctype
attribute, and the browser will handle the encoding before sending the request:application/x-www-form-urlencoded
(default if not specified)multipart/form-data
– used when uploading files as it allows you to send parts of the data with differentContent-Type
s per parttext/plain
When using
fetch
, you can manually specify aheaders
object in the options and set e.g."Content-Type": "application/x-www-form-urlencoded"
, but there are also some classes with special handling that set it for you and encode the data for you. These are:FormData
, which you’ve used here, setsContent-Type: multipart/form-data
URLSearchParams
which setsContent-Type: application/x-www-form-urlencoded
The issue
Onto the issue with your snippet. The
<script>
‘s JavaScript code usesFormData
withfetch
. However, none of the middleware you’re using handle this type.The solution
You can keep using
FormData
if you intend on uploading files with this form, but you’ll need to install and use e.g. themulter
package, which handles parsing ofmultipart/form-data
data.If you don’t intend on adding files to this form, I would recommend using either JSON or urlencoded data, as you already have the middleware for parsing those:
JSON
Reference: Uploading JSON data
urlencoded
For a list of what
fetch
accepts into itsbody
, see: Body – Using the Fetch API.Other notes
Other notes about your snippets:
homeController.js
, don’t create a new Express app, but useexpress.Router()
(replaceconst app = express()
withconst app = express.Router()
)index
andhomeController
files apply the.json()
and.urlencoded()
middleware. This is not necessary, as the middleware applied beforeapp.use("/", require(...))
are also applied to that router (homeController.js
). Consider only doing it before routes that you actually expect to get such input in, so only inhomeController
.keyGenerator
of your rate limiter middleware always returnsundefined
so every user is probably subject to the same shared limit. This is because(req, res) => {req.ip}
has{}
so it has a block body. Either change it to(req, res) => req.ip
or(req, res) => { return req.ip }