3. Repository
3.1 Introduction au concept de Repository
Pour pouvoir discuter avec une base de données, nous allons organiser notre code de manière claire et structurée.
La méthode que nous allons utiliser est la suivante : Créer une classe dédiée à la gestion de la base de données, que l’on appelle un repository.
Qu’est-ce qu’un repository ?
Un repository est un fichier JavaScript (une classe) qui a pour rôle de :
-
se connecter à la base de données (au moment de sa création),
-
contenir des méthodes (fonctions) qui permettent de faire des actions sur la base.
Chaque méthode aura une mission bien précise :
-
récupérer toutes les données d’une table (
SELECT *
) -
récupérer une donnée par son identifiant (
SELECT WHERE id = ...
) -
ajouter une donnée (
INSERT INTO
) -
éventuellement modifier ou supprimer une donnée (
UPDATE
,DELETE
)
Pourquoi faire ça dans une classe à part ?
-
Cela permet de séparer la gestion des données du reste de l’application (routes, logique du site, etc.)
-
Le code est plus lisible, plus réutilisable, et plus facile à tester
-
Si on change de base de données plus tard, on n’aura à modifier que cette partie
Pour l’instant, vous pouvez voir un repository comme un fichier .js
contenant plusieurs fonctions, et qui est chargé de parler avec la base de données.
3.2 Rappel : classe / objet
Même si vous n’avez pas encore vu ce concept en profondeur, il est utile de connaître quelques bases sur les classes et les objets, car nous allons déjà en utiliser pour les Repository
.
Classe et objet, c’est quoi ?
-
Une classe, c’est un peu comme un modèle ou un plan que l’on écrit dans un fichier.
-
Un objet, c’est une version concrète de ce plan, avec laquelle on peut interagir dans le code.
Par exemple :
On peut imaginer qu’une classe représente un "robot", et chaque objet est un robot réel qui exécute des actions.
Et dans notre cas ?
Dans notre cas, on va créer une classe qui représente un accès à la base de données (ce qu’on appelle un repository).
Ce repository contiendra :
-
une connexion à la base
-
plusieurs méthodes pour effectuer des actions : lire, ajouter, chercher...
Pourquoi utiliser une classe ?
On pourrait faire tout ça dans un simple fichier .js
avec quelques fonctions,
mais ce ne serait pas très organisé.
En utilisant une classe :
-
on suit une structure plus propre et standard (comme dans les vrais projets)
-
on regroupe toutes les fonctions liées à la base de données dans un seul endroit
-
on pourra plus facilement faire évoluer le code plus tard
Ainsi, on va se permettre d'écrire la syntaxe d'une classe même si vous n'avez pas encore bien vu ce concept. Pour le moment, vous pouvez retenir que c'est similaire à un fichier qui contient différentes fonctions similaires.
3.3 Détails sur la classe Repository
Nous allons maintenant construire un exemple concret de repository.
Imaginons que nous avons une base de données contenant une table appelée users
.
Cette table pourrait contenir les colonnes suivantes :
-
id
: identifiant unique -
nom
: le nom de l’utilisateur -
email
: son adresse mail
(Voir image ci-dessous pour une représentation visuelle de la table)
Objectif
Nous allons créer une classe UserRepository
dont le rôle sera de :
-
se connecter à la base de données
-
proposer plusieurs méthodes (=fonctions) pour interagir avec la table
users
Cette classe sera définie dans un fichier à part, par exemple UserRepository.js
.
Pourquoi faire ça ?
Créer une classe comme celle-ci nous permet de :
-
centraliser toute la logique d’accès aux données
-
réutiliser facilement les méthodes dans notre serveur Express
-
écrire un code plus propre, clair et standardisé
Exemple de structure de la classe UserRepository
const sqlite3 = require("sqlite3").verbose();
const path = require("path");
class UserRepository {
constructor() {
// Le constructeur est appelé lorsqu’on crée un "objet" UserRepository
// C’est ici que l’on se connecte à la base de données
this.db = new sqlite3.Database(path.join(__dirname, "../data/site.db"));
}
// Méthode pour récupérer tous les utilisateurs
getAllUsers(callback) {
const query = "SELECT * FROM users";
this.db.all(query, [], (err, rows) => {
if (err) {
return callback(err);
}
callback(null, rows); // rows est un tableau contenant tous les utilisateurs
});
}
// Méthode pour récupérer un utilisateur par son ID
getUserById(id, callback) {
const query = "SELECT * FROM users WHERE id = ?";
this.db.get(query, [id], (err, row) => {
if (err) {
return callback(err);
}
callback(null, row); // row est l’objet représentant l’utilisateur, ou null si non trouvé
});
}
// Méthode pour insérer un nouvel utilisateur
insertUser(nom, email, callback) {
const query = "INSERT INTO users (nom, email) VALUES (?, ?)";
this.db.run(query, [nom, email], function (err) {
if (err) {
return callback(err);
}
// this.lastID contient l’id du nouvel utilisateur inséré
callback(null, { id: this.lastID });
});
}
}
module.exports = UserRepository;
Ce qu’on remarque ici
-
Le constructeur (constructor) est exécuté une fois lorsqu’on crée un objet
UserRepository
-
La base de données est ouverte une seule fois, grâce à
sqlite3.Database(...)
-
Chaque méthode correspond à une action possible sur la base de données :
1. lire tous les utilisateurs 2. lire un utilisateur spécifique 3. ajouter un nouvel utilisateur
3.4 La méthode getAll()
Commençons par la méthode la plus simple : celle qui permet de récupérer tous les éléments d’une table.
Dans notre exemple, on souhaite récupérer tous les utilisateurs présents dans la table users
.
Objectif
La méthode getAllUsers()
va :
-
envoyer une requête SQL à la base de données (
SELECT * FROM users
) -
récupérer tous les utilisateurs sous forme d’un tableau
-
passer ce tableau à une fonction callback, pour que notre serveur Express puisse ensuite les envoyer au frontend en JSON (on en rediscute à la section suivante)
Exemple de méthode getAllUsers()
getAllUsers(callback) {
const query = "SELECT * FROM users";
this.db.all(query, [], (err, rows) => {
if (err) {
return callback(err);
}
callback(null, rows); // rows est un tableau contenant tous les utilisateurs
});
}
Détail du fonctionnement :
-this.db.all(...)
est une méthode fournie par SQLite pour exécuter une requête qui retourne plusieurs résultats.
rows
est un tableau d’objets JavaScript, où chaque objet correspond à une ligne de la tableusers
.
Exemple de contenu de rows :
[
{ id: 1, nom: "Alice", email: "alice@example.com" },
{ id: 2, nom: "Bob", email: "bob@example.com" }
]
callback(null, rows)
permet d’utiliser ce tableau ailleurs dans le code, typiquement pour l’envoyer au navigateur.
Cette méthode est très pratique pour afficher une liste complète d’éléments depuis la base dans une page web. On l’utilisera pour afficher dynamiquement des données avec JavaScript.
Nous verrons comment nous pouvons utiliser cette méthode dans la section suivante.
3.5 La méthode getById()
Cette méthode permet de récupérer un seul élément précis depuis la base de données, en fonction de son identifiant (id
).
Objectif
La méthode getUserById(id, callback)
va :
-
recevoir un identifiant (par exemple
3
) -
exécuter une requête SQL qui recherche l’utilisateur correspondant
-
renvoyer cet utilisateur à travers le callback
Exemple de méthode getUserById()
getUserById(id, callback) {
const query = "SELECT * FROM users WHERE id = ?";
this.db.get(query, [id], (err, row) => {
if (err) {
return callback(err);
}
callback(null, row); // row est l’objet représentant l’utilisateur, ou null si non trouvé
});
}
Détail du fonctionnement
-
On utilise this.db.get(...) car on attend un seul résultat.
-
Le
?
dans la requête est un paramètre sécurisé (empêche les injections SQL). -
row
contient l’objet utilisateur, par exemple :
- Si aucun utilisateur ne correspond à l’ID donné,
row
vaudranull
.
Cette méthode est utile lorsqu’on veut afficher ou utiliser un seul élément précis, identifié par son ID.
3.6 La méthode insert()
Cette méthode permet d’ajouter une nouvelle ligne dans la base de données.
Dans notre exemple, cela signifie ajouter un nouvel utilisateur.
Objectif
La méthode insertUser(nom, email, callback)
va :
- recevoir un nom et un email
- exécuter une requête SQL INSERT INTO
- appeler le callback lorsque l’insertion est terminée
Exemple de méthode insertUser()
insertUser(nom, email, callback) {
const query = "INSERT INTO users (nom, email) VALUES (?, ?)";
this.db.run(query, [nom, email], function (err) {
if (err) {
return callback(err);
}
// this.lastID contient l’id du nouvel utilisateur inséré
callback(null, { id: this.lastID });
});
}
Détail du fonctionnement
-
this.db.run(...)
permet d’exécuter une requête sans attendre de résultat (comme unINSERT
) -
Les
?
sont des paramètres sécurisés -
La fonction callback reçoit un objet
{ id: ... }
contenant l’identifiant du nouvel utilisateur inséré -
On utilise
function (...)
(et non une arrow function) pour pouvoir accéder àthis.lastID
Cette méthode est utile pour enregistrer une nouvelle donnée dans la base, généralement suite à un formulaire rempli par l’utilisateur.
Avant d'apprendre à utiliser ces fonctions pour pouvoir les utiliser dans le backend et le frontend, il va falloir discuter de comment on peut transférer les résultats de ces méthodes du backend au frontend. Pour cela, nous allons introduire AJAX.