Ce guide présente la mise en place d'une API RESTful permettant la gestion des utilisateurs, incluant les opérations d'inscription, de connexion, de modification de mot de passe et de suppression. L'implémentation repose sur Node.js, Express pour le framework web, et MySQL comme base de données.
Configuration du Projet
Structure des Fichiers :
db/: Contient les requêtes SQL pour les opérations sur la base de données.userSQL.js: Requêtes spécifiques aux utilisateurs.
router/: Définit les routess de l'API.user.js: Routes pour les opérations liées aux utilisateurs.
connect.js: Gère la connexion à la base de données MySQL.index.html: Une page HTML simple pour tester les fonctionnalités de l'API.index.js: Le point d'entrée principal de l'application serveur.
Dépendances (package.json) :
{
"name": "user-management-api",
"version": "1.0.0",
"description": "API pour la gestion des utilisateurs",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"mysql": "^2.17.1"
}
}
Initialisation du Serveur (index.js)
Le fichier principal initialise l'application Express, configure les middlewares et définit les routes de base.
const { app, pool, resJson } = require('./connect');
const userRoutes = require('./router/user');
// Middleware pour gérer les requêtes CORS et le parsing du corps des requêtes
app.use(require('cors')());
app.use(require('body-parser').json());
app.use(require('body-parser').urlencoded({ extended: false }));
// Route d'accueil pour servir le fichier de test HTML
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
// Point de montage pour les routes utilisateur
app.use('/api/users', userRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Serveur démarré sur le port ${PORT}`);
});
Configuration de la Connexion à la Base de Données (connect.js)
Ce module établit la connexion à la base de données MySQL et gère un pool de connexions pour optimiser les performances. Il inclut également une logique de reconnexion en cas de perte de connexion.
const mysql = require('mysql');
const express = require('express');
const router = express.Router();
const app = express();
// Configuration des paramètres de connexion à la base de données
const dbOptions = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'nodecms',
port: process.env.DB_PORT || '3306',
connectionLimit: 10, // Limite de connexions dans le pool
waitForConnections: true,
queueLimit: 0
};
let pool;
// Fonction pour créer ou recréer le pool de connexions
function createConnectionPool() {
pool = mysql.createPool(dbOptions);
pool.on('error', (err) => {
console.error('Erreur de base de données :', err.code);
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.log('Tentative de reconnexion à la base de données...');
setTimeout(createConnectionPool, 2000); // Réessayer après 2 secondes
} else {
throw err;
}
});
console.log('Pool de connexions MySQL créé.');
}
createConnectionPool();
// Middleware pour s'assurer qu'une connexion est disponible avant de traiter la requête
app.use((req, res, next) => {
if (!pool) {
return res.status(503).send('Service indisponible : Connexion à la base de données non établie.');
}
next();
});
// Classe utilitaire pour formater les réponses JSON
class ApiResponse {
constructor(code = 200, message = '', data = {}) {
this.status = code;
this.message = message;
this.payload = data;
}
}
// Fonction utilitaire pour envoyer des réponses JSON formatées
function sendJsonResponse(response, result) {
const formattedResult = new ApiResponse(result.code, result.msg, result.data);
response.json(formattedResult);
}
module.exports = { app, pool, router, sendJsonResponse };
Définition des Requêtes SQL (db/userSQL.js)
Ce fichier centralise toutes les requêtes SQL relatives aux opérations sur la table utilisateur.
const userSqlQueries = {
selectAllUsers: 'SELECT * FROM user',
findUserByUsername: 'SELECT * FROM user WHERE username = ?',
findUserByCredentials: 'SELECT * FROM user WHERE username = ? AND password = ?',
insertUser: 'INSERT INTO user SET ?',
updateUserPassword: 'UPDATE user SET password = ? WHERE username = ?',
deleteUserByUsername: 'DELETE FROM user WHERE username = ?'
};
module.exports = userSqlQueries;
Routage des Opérations Utilisateur (router/user.js)
Ce module définit les points d'API pour chaque opération de gestion d'utilisateurs.
const { pool, router, sendJsonResponse } = require('../connect');
const userSql = require('../db/userSQL');
/**
* Endpoint pour la connexion d'un utilisateur.
* @name GET /api/users/login
* @param {string} name - Le nom d'utilisateur.
* @param {string} password - Le mot de passe.
*/
router.get('/login', (req, res) => {
const { name, password } = req.query;
if (!name) return sendJsonResponse(res, { code: -1, msg: 'Le nom d\'utilisateur est requis.' });
if (!password) return sendJsonResponse(res, { code: -1, msg: 'Le mot de passe est requis.' });
pool.getConnection((err, connection) => {
if (err) return sendJsonResponse(res, { code: -1, msg: 'Erreur de connexion à la base de données.' });
connection.query(userSql.findUserByCredentials, [name, password], (error, results) => {
connection.release(); // Libérer la connexion
if (error) return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la requête.', data: error });
if (results && results.length > 0) {
sendJsonResponse(res, { msg: 'Connexion réussie.', data: { userInfo: { username: name } } });
} else {
sendJsonResponse(res, { code: -1, msg: 'Nom d\'utilisateur ou mot de passe incorrect.' });
}
});
});
});
/**
* Endpoint pour l'inscription d'un nouvel utilisateur.
* @name POST /api/users/register
* @param {string} name - Le nom d'utilisateur.
* @param {string} realname - Le nom réel de l'utilisateur.
* @param {string} password - Le mot de passe.
*/
router.post('/register', (req, res) => {
const { name, realname, password } = req.body;
if (!name) return sendJsonResponse(res, { code: -1, msg: 'Le nom d\'utilisateur est requis.' });
if (!realname) return sendJsonResponse(res, { code: -1, msg: 'Le nom réel est requis.' });
if (!password) return sendJsonResponse(res, { code: -1, msg: 'Le mot de passe est requis.' });
pool.getConnection((err, connection) => {
if (err) return sendJsonResponse(res, { code: -1, msg: 'Erreur de connexion à la base de données.' });
connection.query(userSql.findUserByUsername, [name], (error, results) => {
if (error) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la vérification de l\'utilisateur.', data: error });
}
if (results && results.length > 0) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Cet utilisateur existe déjà.' });
}
const newUser = { username: name, realname: realname, password: password };
connection.query(userSql.insertUser, newUser, (insertError, insertResult) => {
connection.release();
if (insertError) return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de l\'enregistrement.', data: insertError });
sendJsonResponse(res, { msg: 'Inscription réussie.' });
});
});
});
});
/**
* Endpoint pour modifier le mot de passe d'un utilisateur.
* @name PUT /api/users/update-password
* @param {string} name - Le nom d'utilisateur.
* @param {string} oldPassword - L'ancien mot de passe.
* @param {string} newPassword - Le nouveau mot de passe.
*/
router.put('/update-password', (req, res) => {
const { name, oldPassword, newPassword } = req.body;
if (!name) return sendJsonResponse(res, { code: -1, msg: 'Le nom d\'utilisateur est requis.' });
if (!oldPassword) return sendJsonResponse(res, { code: -1, msg: 'L\'ancien mot de passe est requis.' });
if (!newPassword) return sendJsonResponse(res, { code: -1, msg: 'Le nouveau mot de passe est requis.' });
pool.getConnection((err, connection) => {
if (err) return sendJsonResponse(res, { code: -1, msg: 'Erreur de connexion à la base de données.' });
// Vérifier l'ancien mot de passe
connection.query(userSql.findUserByCredentials, [name, oldPassword], (error, results) => {
if (error) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la vérification de l\'ancien mot de passe.', data: error });
}
if (!results || results.length === 0) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Nom d\'utilisateur ou ancien mot de passe incorrect.' });
}
// Mettre à jour le mot de passe
connection.query(userSql.updateUserPassword, [newPassword, name], (updateError, updateResult) => {
connection.release();
if (updateError) return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la mise à jour du mot de passe.', data: updateError });
sendJsonResponse(res, { msg: 'Mot de passe mis à jour avec succès.' });
});
});
});
});
/**
* Endpoint pour supprimer un utilisateur.
* @name DELETE /api/users/:username
* @param {string} username - Le nom d'utilisateur à supprimer.
*/
router.delete('/:username', (req, res) => {
const { username } = req.params;
if (!username) return sendJsonResponse(res, { code: -1, msg: 'Le nom d\'utilisateur est requis.' });
pool.getConnection((err, connection) => {
if (err) return sendJsonResponse(res, { code: -1, msg: 'Erreur de connexion à la base de données.' });
// Vérifier d'abord si l'utilisateur existe
connection.query(userSql.findUserByUsername, [username], (error, results) => {
if (error) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la vérification de l\'utilisateur.', data: error });
}
if (!results || results.length === 0) {
connection.release();
return sendJsonResponse(res, { code: -1, msg: 'Utilisateur non trouvé.' });
}
// Supprimer l'utilisateur
connection.query(userSql.deleteUserByUsername, [username], (deleteError, deleteResult) => {
connection.release();
if (deleteError) return sendJsonResponse(res, { code: -1, msg: 'Erreur lors de la suppression de l\'utilisateur.', data: deleteError });
sendJsonResponse(res, { msg: 'Utilisateur supprimé avec succès.' });
});
});
});
});
module.exports = router;
Interface de Test (index.html)
Une simple page HTML avec des formulaires pour tester les différentes opérations de l'API.
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test API Utilisateurs</title>
<style>
body { font-family: sans-serif; display: flex; flex-wrap: wrap; gap: 20px; }
.form-container { border: 1px solid #ccc; padding: 20px; border-radius: 8px; width: 300px; box-shadow: 2px 2px 5px rgba(0,0,0,0.1); }
.form-container h3 { margin-top: 0; }
label { display: block; margin-bottom: 10px; }
input[type="text"], input[type="password"] { width: calc(100% - 10px); padding: 8px; margin-top: 5px; border: 1px solid #ddd; border-radius: 4px; }
button { background-color: #007bff; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; width: 100%; }
button:hover { background-color: #0056b3; }
.result { margin-top: 15px; padding: 10px; border-radius: 4px; font-size: 0.9em; }
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
</style>
</head>
<body>
<div class="form-container">
<h3>Inscription</h3>
<form id="registerForm">
<label>Nom d'utilisateur: <input type="text" name="name" required></label>
<label>Nom réel: <input type="text" name="realname" required></label>
<label>Mot de passe: <input type="password" name="password" required></label>
<button type="submit">S'inscrire</button>
</form>
<div id="registerResult" class="result"></div>
</div>
<div class="form-container">
<h3>Connexion</h3>
<form id="loginForm">
<label>Nom d'utilisateur: <input type="text" name="name" required></label>
<label>Mot de passe: <input type="password" name="password" required></label>
<button type="submit">Se connecter</button>
</form>
<div id="loginResult" class="result"></div>
</div>
<div class="form-container">
<h3>Modification Mot de Passe</h3>
<form id="updatePasswordForm">
<label>Nom d'utilisateur: <input type="text" name="name" required></label>
<label>Ancien mot de passe: <input type="password" name="oldPassword" required></label>
<label>Nouveau mot de passe: <input type="password" name="newPassword" required></label>
<button type="submit">Modifier</button>
</form>
<div id="updatePasswordResult" class="result"></div>
</div>
<div class="form-container">
<h3>Suppression Utilisateur</h3>
<form id="deleteUserForm">
<label>Nom d'utilisateur à supprimer: <input type="text" name="username" required></label>
<button type="submit">Supprimer</button>
</form>
<div id="deleteUserResult" class="result"></div>
</div>
<script>
const API_BASE_URL = 'http://localhost:3000/api/users';
function handleFormSubmit(formId, endpoint, method, resultElementId) {
const form = document.getElementById(formId);
const resultDiv = document.getElementById(resultElementId);
form.addEventListener('submit', async (e) => {
e.preventDefault();
resultDiv.innerHTML = ''; // Clear previous results
resultDiv.className = 'result'; // Reset class
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => { data[key] = value; });
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.status === 200 || result.status === undefined) { // Assuming 200 or no status means success
resultDiv.textContent = result.message || 'Opération réussie.';
resultDiv.classList.add('success');
} else {
resultDiv.textContent = `Erreur: ${result.message || 'Une erreur inconnue est survenue.'}`;
resultDiv.classList.add('error');
}
} catch (error) {
resultDiv.textContent = `Erreur réseau: ${error.message}`;
resultDiv.classList.add('error');
}
});
}
// Initialiser les gestionnaires de formulaire
handleFormSubmit('registerForm', '/register', 'POST', 'registerResult');
handleFormSubmit('loginForm', '/login', 'GET', 'loginResult'); // Login might use GET for simplicity in this example
handleFormSubmit('updatePasswordForm', '/update-password', 'PUT', 'updatePasswordResult');
// Custom handler for delete form because endpoint needs parameter
const deleteForm = document.getElementById('deleteUserForm');
const deleteResultDiv = document.getElementById('deleteUserResult');
deleteForm.addEventListener('submit', async (e) => {
e.preventDefault();
deleteResultDiv.innerHTML = '';
deleteResultDiv.className = 'result';
const username = deleteForm.username.value;
if (!username) {
deleteResultDiv.textContent = 'Veuillez entrer un nom d\'utilisateur.';
deleteResultDiv.classList.add('error');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/${username}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.status === 200 || result.status === undefined) {
deleteResultDiv.textContent = result.message || 'Utilisateur supprimé.';
deleteResultDiv.classList.add('success');
} else {
deleteResultDiv.textContent = `Erreur: ${result.message || 'Impossible de supprimer l\'utilisateur.'}`;
deleteResultDiv.classList.add('error');
}
} catch (error) {
deleteResultDiv.textContent = `Erreur réseau: ${error.message}`;
deleteResultDiv.classList.add('error');
}
});
</script>
</body>
</html>