Cours10 min de lecture

Node.js et Express quand on vient de PHP : CRUD et login

Tu connais PHP ? Refais ton CRUD et ton login avec Node.js et Express : npm, MVC, mysql2, bcrypt, express-session. Les équivalences PHP → Node.

Aussi en : العربية

Mehdi en tenue de formateur, couverture de l’article : Node.js + Express

Si tu sais déjà faire un CRUD et un login en PHP, tu sais presque tout faire en Node.js : les idées restent les mêmes, seul le langage change. Dans le cours E14, on reprend le mini CRUD et le login de la série PHP, la même table users dans MySQL, et on les refait avec Express, rangés en MVC. Cet article résume le chemin, avec le tableau des équivalences PHP → Node.

Node.js, c'est quoi exactement ?

Dans Chrome, un moteur appelé V8 lit et exécute le JavaScript. En 2009, Ryan Dahl a eu l'idée de sortir ce moteur du navigateur pour le faire tourner sur le serveur : c'est Node.js.

La différence pratique :

  • dans le navigateur, JavaScript contrôle la page (DOM, clics, affichage) mais ne peut pas lire les fichiers du serveur ;
  • dans Node.js, le même langage peut lire des fichiers, parler à une base de données et répondre à des requêtes.

Un seul langage pour le front et le back : c'est l'idée derrière la pile MERN. Et Express est le framework le plus utilisé sur Node pour construire un site ou une API.

Premier essai, comme dans le cours : un fichier app.js qui contient un console.log, lancé depuis le terminal.

sh
node app.js

Comment démarrer le projet ?

npm, le Composer de Node

npm est le gestionnaire de paquets de Node, comme Composer en PHP. On commence par npm init, qui crée package.json, la carte d'identité du projet. Puis on installe les librairies utilisées dans le cours, chacune avec son rôle :

PaquetRôle dans le projet
expressle serveur et les routes
ejsles pages HTML
mysql2parler à MySQL
bcryptles mots de passe
express-sessionles sessions
dotenvlire le fichier .env
sh
npm init
npm install express ejs mysql2 bcrypt express-session dotenv

Les librairies vont dans node_modules et sont enregistrées dans package.json.

La même organisation MVC

Le projet est rangé comme dans le cours MVC : models/ contient le SQL, controllers/ la logique, views/ les pages EJS, et routes/ relie chaque URL à un controller.

Le fichier .env ne va jamais sur GitHub

.env contient tout ce qui est secret : le mot de passe de la base, le secret de la session, les clés API. Ajoute-le dans .gitignore, avec node_modules.

text
.env
node_modules

Conseil du cours : node --watch (ou nodemon) relance le serveur tout seul quand tu modifies le code.

Pourquoi la base MySQL ne change pas ?

Parce que MySQL n'est pas PHP. C'est un SGBD, un serveur à part, auquel PHP, Node, Python ou n'importe quel langage peut parler. On garde donc la même table users. Dans db.js, on crée un pool avec mysql2 en lisant les informations de connexion dans .env. Chaque requête renvoie un tableau rows qui contient les utilisateurs.

Le CRUD avec Express

Le chemin d'une demande reste celui du MVC : la route appelle une méthode du controller, qui interroge le model, puis rend une vue.

  • Dans app.js, on active express.urlencoded pour lire les formulaires.
  • Dans routes/users, GET /users va à la méthode index du controller.
  • index demande au model await User.findAll(), puis appelle res.render sur la page users/index.

Le code affiché dans le cours, en CommonJS (require, exports) :

js
// controllers/userController.js
const User = require('../models/userModel');

exports.index = async (req, res) => {
  const users = await User.findAll();
  res.render('users/index', { users });
};

// models/userModel.js
const pool = require('../db');

exports.findAll = async () => {
  const [rows] = await pool.execute('SELECT * FROM users');
  return rows;
};

Pourquoi await ?

La base met un peu de temps à répondre. await attend la réponse avant de continuer. Et avec Express 5, si une erreur survient dans une fonction async, Express l'envoie tout seul au middleware d'erreurs.

Ajouter, modifier, supprimer

  • Ajouter : POST /users. La méthode store lit req.body (le nom et l'email), le model fait un INSERT avec execute, puis res.redirect('/users'), l'équivalent de header Location.
  • Modifier : /users/:id/edit. L'id se lit avec req.params.id. Le model fait UPDATE users SET nom = ?, email = ? WHERE id = ?, avec les valeurs dans un tableau, dans le même ordre que les points d'interrogation.
  • Supprimer : en POST, pas en GET, et toujours avec un WHERE. Sans WHERE, tu effaces tous les utilisateurs.

Les points d'interrogation font de chaque requête une requête préparée, comme prepare / execute avec PDO. Ne colle jamais le contenu du formulaire dans le SQL : c'est la porte ouverte à l'injection SQL.

Côté vue, EJS affiche avec <%= %>, qui protège du XSS comme htmlspecialchars.

Test de fin de chapitre : node app.js, puis localhost:3000/users. La liste s'affiche et le formulaire ajoute un utilisateur.

Inscription et login avec bcrypt

La règle numéro un ne change pas : un mot de passe n'est jamais stocké tel quel. Si la base est volée, les mots de passe en clair sont lisibles, et beaucoup de gens réutilisent le même partout.

js
// controllers/authController.js
const bcrypt = require('bcrypt');
const User = require('../models/userModel');

exports.register = async (req, res) => {
  const { nom, email, password } = req.body;
  const hash = await bcrypt.hash(password, 10);
  await User.register(nom, email, hash);
  res.redirect('/login');
};

exports.login = async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findByEmail(email);
  const ok = user && await bcrypt.compare(password, user.password);
  if (!ok) return res.render('login', {
    erreur: 'Email ou mot de passe incorrect' });
  // la session : chapitre suivant
};
  • 10 est le coût : le nombre de fois où bcrypt refait le calcul. Plus il est grand, plus c'est difficile pour un pirate.
  • Le hash contient un sel et il est à sens unique, comme password_hash / password_verify en PHP.
  • En cas d'échec, un seul message : « Email ou mot de passe incorrect ». Le user && évite de comparer quand l'email n'existe pas. Ne dis pas à un attaquant ce qui existe dans la base.
  • L'email est UNIQUE dans la table, pour que deux comptes ne partagent pas la même adresse.

La session et le middleware requireAuth

Le cours reprend l'image du badge d'hôtel : tu montres ta carte une fois, on te donne un badge.

  • express-session distribue le badge, comme session_start.
  • Le badge est un cookie appelé connect.sid (en PHP, c'était PHPSESSID).
  • Le secret de session va dans .env : c'est lui qui signe le badge.
  • Quand le mot de passe est bon : req.session.regenerate() pour changer de badge (comme session_regenerate_id), puis req.session.userId (comme $_SESSION['user_id']).
  • Logout : req.session.destroy(), et l'ancien badge n'ouvre plus aucune porte.

Pour les pages protégées, un petit middleware :

js
// middleware/requireAuth.js
function requireAuth(req, res, next) {
  if (!req.session.userId) return res.redirect('/login');
  next();
}

C'est le isset, header et exit de PHP, mais écrit une seule fois pour toutes les routes. Test : /dashboard sans être connecté renvoie au login ; après connexion, la page affiche « Mer7ba, Sara ! ».

Et les tokens JWT ?

Si le front est une application React ou une application mobile, on utilise souvent un token comme JWT. La différence avec la session : avec la session, le badge reste connu du serveur ; un JWT est une carte signée que le client garde avec lui. Au login, jwt.sign crée le token avec le secret du .env ; à chaque requête, il est envoyé dans le header Authorization: Bearer. Le middleware fait jwt.verify : token valide, next() ; sinon, erreur 401. Côté Laravel, l'idée équivalente s'appelle Sanctum. Le détail complet avec React est dans l'article React + API et login JWT.

Et le front ?

Aujourd'hui, EJS : le serveur construit le HTML et envoie une page toute prête, comme les vues PHP. Demain, React : le serveur ne renvoie que du JSON, et React construit la page dans le navigateur. L'API Express sert alors de back-end au front React.

Questions fréquentes

Faut-il réapprendre MySQL pour utiliser Node.js ?

Non. MySQL est un serveur à part ; Node lui parle avec le paquet mysql2. Le cours garde exactement la même table users que dans la série PHP.

Express ou EJS : qui fait quoi ?

Express gère le serveur et les routes ; EJS fabrique les pages HTML côté serveur, comme les vues PHP. Plus tard, React remplace EJS et Express renvoie du JSON.

Session ou JWT : lequel choisir ?

Le cours utilise la session pour un site rendu par le serveur (EJS). Pour un front React ou une application mobile, il présente le token JWT, envoyé dans le header Authorization: Bearer.

Pourquoi supprimer en POST et pas en GET ?

Parce qu'une suppression modifie les données : le cours la fait en POST, toujours avec un WHERE, pour ne jamais effacer toute la table.

À retenir

PHPNode.js / Express
$_POSTreq.body
$_GET['id']req.params.id
prepare / execute (PDO)pool.execute(sql, [ ])
htmlspecialchars<%= %> dans EJS
password_hash / password_verifybcrypt.hash / bcrypt.compare
session_start, $_SESSIONexpress-session, req.session
isset + header + exitmiddleware requireAuth
header Locationres.redirect

PHP ou Node : les mêmes idées. Quand tu as compris l'idée, changer de langage ne fait plus peur.

Regarder le cours

Le cours complet (environ 8 min 50, en darija) est sur sa page : Lire un exemple Node.js et Express (E14).

  • 0:00 Introduction : JavaScript, même sur le serveur ?
  • 0:28 JavaScript partout : V8, Node.js et Express
  • 1:30 Le projet : npm, MVC et le fichier .env
  • 2:38 La base : MySQL, un SGBD accessible partout
  • 3:09 Le CRUD avec Express (req.body, req.params, requêtes préparées, EJS)
  • 5:12 Inscription et login avec bcrypt
  • 5:57 La session : express-session et le middleware requireAuth
  • 7:07 Et les tokens ? JWT (et Sanctum côté Laravel)
  • 7:46 Et le front ? EJS aujourd'hui, React demain
  • 8:09 Récap PHP / Node et le défi

Pour aller plus loin

Sources

Quiz : 3 questions pour vérifier

Rien n’est envoyé : tes réponses restent dans ton navigateur.

  1. En Express, quel est l'équivalent de $_POST ?
  2. Que représente le nombre 10 dans bcrypt.hash(password, 10) ?
  3. Que fait le middleware requireAuth quand req.session.userId n'existe pas ?

Partager

Rédigé à partir du cours vidéo et de sa transcription, avec l’aide d’outils d’IA, relu et vérifié par Mehdi Laraibi le 4 novembre 2026.