Introduction Sequelize Et ORM Dans Node - Js

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 32

Introduction à Sequelize et ORM

dans Node.js
Qu'est-ce qu'un ORM ?
En termes simples, un système ORM (Object-relational Mapper) est une technique dans
laquelle vous utilisez un paradigme orienté objet pour créer un mappage entre l'application
et la base de données afin d'effectuer directement la manipulation des données et la
requête.

Lorsqu'il s'agit de récupérer et d'insérer des jointures et des relations, il suivra le même
paradigme pour manipuler ou interroger les données liées aux opérations.

Qu'est-ce que Sequelize ?


Sequelize est un ORM TypeScript et Node.js moderne pour Oracle, Postgres, MySQL,
MariaDB, SQLite et SQL Server, et plus encore. Avec une prise en charge solide des
transactions, des relations, un chargement impatient et paresseux (eager and lazy
loading), une réplication en lecture.

Ce tutoriel vous présentera Sequelize ORM et vous aidera à créer votre première
application Sequelize NodeJS en utilisant l'approche code-first. Il y a 4
questions/exercices dans ce document, assurez-vous d'y répondre.

Code-first avec Sequelize


Créer un projet et se connecter à la base de données
Créez un nouveau projet NodeJS à l'aide de npm init dans un répertoire vide.

$ npm init

Ensuite, vous devrez installer les packages suivants :


$ npm install pg
$ npm install sequelize
$ npm install express body-parser

Pour utiliser Nodemon:


$ npm install —save-dev nodemon
Dans cette application, nous utiliserons postgreSQL, si vous utilisez une autre base de
données, vous devez installer manuellement le pilote de la base de données de votre
choix :

# L'un des éléments suivants :


$ npm install --save pg pg-hstore # Postgres
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # Microsoft SQL Server
$ npm install --save oracledb # Base de données Oracle

La première étape à faire est d'écrire du code pour connecter Sequelize à la base de
données. Nous allons d'abord créer un fichier index.js, le point d'entrée de notre
application. Créez ensuite un dossier "database" et un fichier db.js à l'intérieur de ce
dossier.
$ touch index.js
$ mkdir database
$ touch database/db.js

Dans le fichier db.js, importez "sequelize", puis créez une nouvelle instance de sequelize
en appelant new Sequelize. La fonction constructeur a besoin de certaines options telles
que votre base de données, votre nom d'utilisateur, votre mot de passe, votre port et votre
hôte. Assurez-vous de définir la configuration correcte.
Définir un modèle
Les modèles sont l'essence de Sequelize. Un modèle est une abstraction qui représente
une table dans votre base de données. Dans Sequelize, c'est une classe qui étend Model.

Le modèle indique à Sequelize plusieurs informations sur l'entité qu'il représente, telles
que le nom de la table dans la base de données et les colonnes qu'elle contient (et leurs
types de données).

Un modèle dans Sequelize a un nom. Ce nom ne doit pas nécessairement être le même
que celui de la table qu'il représente dans la base de données. Habituellement, les
modèles ont des noms au singulier (tels que User) tandis que les tables ont des noms au
pluriel (tels que Users), bien que cela soit entièrement configurable.

Nous allons maintenant définir un modèle Client qui représente une table "Client" dans
notre base de données. Créez un répertoire "models" et à l'intérieur, créez le fichier
javascript client.js.

$ mkdir models
$ touch models/client.js

Les modèles peuvent être définis de deux manières équivalentes dans Sequelize. Une
façon consiste à appeler sequelize.define(modelName, attributs, options).

Pour apprendre avec un exemple, nous considérerons que nous voulons créer un modèle
pour représenter les clients, qui ont un identifiant, un prénom et un nom. Nous voulons
que notre modèle s'appelle Client et que la table qu'il représente s'appelle "Client" dans la
base de données.

Cette façon de définir ce modèle est illustrée ci-dessous. Après avoir été défini, nous
pouvons accéder à notre modèle avec sequelize.models.Client.

Le modèle Client aura les attributs suivants :

● id
○ Clé primaire
○ Type entier
○ non nul
○ devrait être unique
● firstname
○ Type String
○ non nul
● lastname
○ Type String
○ non nul
Synchronisation des définitions JS avec la base de données
Sequelize peut créer des tables pour vous et il vous suffit de dire à sequelize de le faire.
Dans ce projet, nous le ferons dans le fichier index.js. Là-dedans, nous voulons nous
assurer que tous nos modèles sont essentiellement transférés dans des tables ou obtenir
une table qui leur appartient chaque fois que nous démarrons notre application.
Et si la table existe déjà, sequelize ne la remplacera bien sûr pas par défaut bien que nous
puissions lui dire de le faire.
Avant de synchroniser le modèle créé, nous allons créer un serveur express dans index.js
et essayer de nous authentifier auprès de la base de données.

Lors de l'exécution de index.js, votre terminal doit ressembler à ceci :

Lorsque vous définissez un modèle, vous indiquez à Sequelize quelques informations sur
sa table dans la base de données. Cependant, que se passe-t-il si la table n'existe même
pas dans la base de données ? Que se passe-t-il si elle existe, mais qu'elle a des
colonnes différentes, moins de colonnes ou toute autre différence ?

C'est là qu'intervient la synchronisation des modèles. Un modèle peut être synchronisé


avec la base de données en appelant model.sync(options), une fonction asynchrone (qui
renvoie une Promise). Avec cet appel, Sequelize effectuera automatiquement une requête
SQL vers la base de données. Notez que cela ne modifie que la table dans la base de
données, pas le modèle côté JavaScript.

● Client.sync() - Cela crée la table si elle n'existe pas (et ne fait rien si elle existe
déjà)
● Client.sync({ force: true }) - Cela crée la table, en la supprimant d'abord si elle
existait déjà
● Client.sync({ alter: true }) - Cela vérifie quel est l'état actuel de la table dans la
base de données (quelles colonnes elle a, quels sont leurs types de données, etc.),
puis effectue les modifications nécessaires dans la table pour faire il correspond au
modèle.
Exemple:

Lorsque vous exécutez index.js, vous devriez avoir une sortie dans votre terminal similaire
à ceci :

Que faire si vous avez plusieurs modèles et que vous souhaitez synchroniser tous les
modèles en même temps ? Vous pouvez utiliser sequelize.sync() pour synchroniser
automatiquement tous les modèles. Exemple:
Les associations
Sequelize prend en charge les associations standard : One-To-One, One-To-Many et
Many-To-Many.

Pour cela, Sequelize propose quatre types d'associations qu'il convient de combiner pour
les créer :

● L'association HasOne
● L'association BelongsTo
● L'association HasMany
● L'association BelongsToMany

Association One-to-One
Supposons que chaque client a un passeport. Dans ce cas, la table clients dans la base
de données serait la table parent et sa clé primaire apparaît comme clé étrangère dans la
table enfant.

Dans le répertoire "models", créez un fichier passeport.js pour ajouter un nouveau modèle:

$ touch models/passport.js
Créons un fichier index.js dans le dossier "models" pour importer toutes les entités de
modèles.

Nous pouvons maintenant ajouter les associations dans notre fichier index.js (le point
d'entrée de l'application) comme suit :
Lors de l'exécution de "node index.js", vous pouvez vérifier la base de données et voir la
table nouvellement créée "passports" qui contient la clé étrangère clientId.
Nous allons maintenant ajouter une fonction seed_data() qui va insérer deux clients et
deux passeports dans la base de données. Ensuite, cette fonction liera chaque client à
son passeport à l'aide d'une fonction setter. Lors de l'association d'un client à un
passeport à l'aide d'une association one-to-one, Sequelize crée automatiquement des
fonctions getters et setters telles que setPassport et getPassport. Notez que le nom de
ces fonctions dépend du nom des modèles (singulier ou pluriel par exemple).
Copiez et collez ce code :

async function seed_data(){


let clients = [
{id: 100, firstname: 'Abigail', lastname: 'Kylie'},
{id: 110, firstname: 'Anna', lastname: 'Carolyn'}
]
let passports = [
{country: 'France', passportNumber: 111201, issueDate:
'2014-12-07',expirationDate:'2024-12-07'},
{country: 'USA', passportNumber: 901222, issueDate:
'2019-11-07',expirationDate:'2027-11-07'}
]
await Client.bulkCreate(clients);
await Passport.bulkCreate(passports);

for (let i = 0; i < passports.length; i++){


passport = passports[i];
cl = clients[i];
pass = await Passport.findOne({where : { passportNumber:
passport.passportNumber}});
client = await Client.findOne({where : { id: cl.id}});
await client.setPassport(pass);
}
}

Vous pouvez vérifier les deux tables après avoir exécuté index.js :
Pour rendre les choses plus excitantes, créons une route dans notre fichier index.js
permettant à l'utilisateur de publier des données relatives aux utilisateurs et aux
passeports (HTTP POST). Dans ce projet, par souci de démonstration, nous n'utiliserons
pas le modèle Routeurs-Contrôleurs-Modèles/Services. Nous mettrons toutes les
routes dans notre fichier index.js (ce qui n'est pas recommandé lorsque l'on travaille sur
une vraie application).

1- Ajoutez une route /clients qui accepte les messages HTTP POST. En utilisant la
fonction .create, nous allons créer un nouveau client. Sequelize a créé la fonction
.createPassport lorsque nous avons fait les associations afin que nous puissions l'utiliser
pour créer un passeport lié à un certain client.

2- Testez votre API avec Postman ou cURL :


3- Vérifiez les données de votre base de données :

Association One-to-Many

Prenons le cas où les articles sont vendus. Nous pouvons immédiatement identifier deux
entités : SALE et ITEM. Une vente (SALE) peut contenir de nombreux articles (ITEM) et
un article peut apparaître dans de nombreuses ventes. Chaque vente est réalisée par un
client, et un client peut réaliser plusieurs ventes. Intéressons-nous maintenant à la relation
entre un client et une vente.

Les associations One-To-Many connectent une source à plusieurs cibles, alors que toutes
ces cibles ne sont connectées qu'à cette seule source. Cela signifie que, contrairement à
l'association One-To-One, dans laquelle nous devions choisir où placer la clé étrangère, il
n'y a qu'une seule option dans les associations One-To-Many. Par exemple, si un client a
plusieurs ventes (et de cette façon chaque vente appartient à un client), alors la seule
implémentation sensée est d'avoir une colonne clientId dans la table SALES. L'inverse est
impossible, puisqu'un Client a plusieurs Ventes.

Dans cet exemple, nous avons les modèles Client et Sale. Nous voulons dire à Sequelize
qu'il existe une relation un à plusieurs entre eux, ce qui signifie qu'un Client a plusieurs
Sales, tandis que chaque Sale appartient à un seul Client.

Ajoutons un nouveau modèle "sale.js" dans notre répertoire "models" :

$ touch models/sale.js
Dans index.js (point d'entrée de l'application), assurez-vous d'importer également "Sale"
en haut du fichier :

const {Client,Passport, Sale} = require("./models");


Ajoutez les associations suivantes :

Exécutez votre fichier index.js et vérifiez la table dans votre base de données :

Notez que, dans les deux modèles ci-dessus (Client et Sale), le nom de la table
(clients/sales) n'a jamais été explicitement défini. Cependant, le nom du modèle a été
donné (client/sale). Par défaut, lorsque le nom de la table n'est pas donné, Sequelize
met automatiquement au pluriel le nom du modèle et l'utilise comme nom de table.

Exercice 1 :
Créez une route POST /sales dans laquelle vous pouvez insérer une nouvelle vente dans
la base de données avec une requête HTTP Post. Remplissez le tableau des ventes
(sales) avec des données factices (au moins 2 ventes pour chaque utilisateur).

Exemple requête HTTP Post :


Exemple de contenu de table :

Chargement impatient vs chargement paresseux (Eager Loading vs Lazy Loading)

Les concepts de Eager Loading et de Lazy Loading sont fondamentaux pour


comprendre le fonctionnement des associations de récupération dans Sequelize. Le Lazy
Loading fait référence à la technique consistant à récupérer les données associées
uniquement lorsque vous le souhaitez vraiment ; Eager Loading, quant à lui, fait
référence à la technique consistant à tout récupérer en même temps, depuis le début,
avec une requête plus large.

Testons d'abord le Lazy Loading. Créez une route GET /clients/lazy/:id pour obtenir
toutes les informations relatives à un client avec un identifiant donné. Notez que dans
l'exemple ci-dessous, nous avons fait deux requêtes SQL, ne récupérant les ventes
associées que lorsque nous voulions les utiliser. Cela peut être particulièrement utile si
nous pouvons ou non avoir besoin des ventes, peut-être voulons-nous les récupérer sous
condition, seulement dans quelques cas ; de cette façon, nous pouvons gagner du temps
et de la mémoire en ne le récupérant que lorsque cela est nécessaire.
Testons maintenant le Eager Loading. Créez une route GET /clients/eager/:id pour
obtenir toutes les informations relatives à un client avec un identifiant donné. Eager
Loading est effectué dans Sequelize à l'aide de l'option include. Observez dans
l'exemple ci-dessous qu'une seule requête a été effectuée sur la base de données (qui
apporte les données associées avec l'instance).

Exercice 2 :
Créez une route PUT /clients/:id qui prend l'id du client dans les paramètres et ses
prénom et nom dans le corps (body) et mettez à jour la table clients. Pour cette requête,
vous utiliserez la méthode update et la clause where avec sequelize.
Voici la syntaxe de mise à jour d'une requête extraite de la documentation de sequelize :
Vous devriez avoir des résultats similaires :

Exercice 3 :
créer une route DELETE /sales/:id qui prend l'id d'une vente dans les paramètres et
supprimer la vente de la table "sales". Pour cette requête, vous utilisez la méthode
"destroy" et la clause where avec Sequelize.
Voici la syntaxe d'une opération de suppression tirée de la documentation sequelize:
Vous devriez avoir des résultats similaires :

Faisons une requête intéressante avec Sequelize. Créez une route GET /sales/topclient.
Nous voulons renvoyer le client qui a les ventes les plus élevées et dont les ventes sont
supérieures à 2.

Supposons que dans votre table des ventes (sales), vous ayez ces enregistrements ou
quelque chose de similaire (un client qui a plus de 2 ventes):

Vous souhaitez recréer la requête suivante dans sequelize :


Il n'est pas toujours possible pour Sequelize de prendre en charge toutes les
fonctionnalités SQL de manière propre. La requête ci-dessus n'est certainement pas
simple. Il peut parfois être préférable d'écrire la requête SQL vous-même.

Ceci peut être fait de deux façons:

● Soit écrire vous-même une requête brute complète,


● ou utilisez la fonction literal() fournie par Sequelize pour insérer du SQL brut
presque n'importe où dans les requêtes construites par Sequelize.

La requête ci-dessus peut être effectuée comme suit dans Sequelize :


Association Many-to-Many

Dans votre répertoire "models", créez un nouveau modèle javascript "item.js". Chaque
article aura un numéro d'article, un nom d'article, un type et une couleur.

$ touch models/item.js

Vous pouvez arrêter l'auto-pluralisation effectuée par Sequelize à l'aide de l'option


freezeTableName: true. De cette façon, Sequelize déduira que le nom de la table est égal
au nom du modèle, sans aucune modification.

Par défaut, Sequelize ajoute automatiquement les champs createdAt et updatedAt à


chaque modèle, en utilisant le type de données DataTypes.DATE. Ces champs sont
également gérés automatiquement - chaque fois que vous utilisez Sequelize pour créer
ou mettre à jour quelque chose, ces champs seront définis correctement. Le champ
createdAt contiendra l'horodatage représentant le moment de la création, et updatedAt
contiendra l'horodatage de la dernière mise à jour. Ce comportement peut être désactivé
pour un modèle avec l'option timestamps : false.
Lorsque nous avons une relation m:m, nous créons une entité associative pour stocker
des données sur la relation. Dans ce cas, nous devons stocker des données sur les
articles vendus. Nous ne pouvons pas stocker les données avec SALE car une vente peut
avoir de nombreux articles et une instance d'une entité ne stocke que des faits à valeur
unique. De même, nous ne pouvons pas stocker de données avec ITEM car un article
peut apparaître dans de nombreuses ventes. Comme nous ne pouvons pas stocker de
données dans SALE ou ITEM, nous devons créer une autre entité pour stocker des
données sur la relation m:m. Pour cela, dans votre répertoire "models", créez le modèle
javascript "saleitem.js".

$ touch models/saleitem.js
Mettez à jour le fichier models/index.js comme suit :
Dans index.js (point d'entrée de l'application), assurez-vous d'importer également "Item"
et “SaleItem” en haut du fichier :

const {Client,Passport, Sale, Item, SaleItem} = require("./models");

Pour cet exemple, nous allons considérer les modèles Sale et Item. Une vente peut
contenir de nombreux articles et un article peut être impliqué dans de nombreuses ventes.
La table de jonction qui gardera une trace des associations s'appellera SaleItem, qui
contiendra les clés étrangères saleno et itemno.

La principale façon de procéder dans Sequelize est la suivante :

Exécutez "node index.js" et vérifiez votre base de données.


Mettez à jour la fonction seed_data() pour ajouter des données factices aux tables
nouvellement créées.

async function seed_data(){


let clients = [
{id: 100, firstname: 'Abigail', lastname: 'Kylie'},
{id: 110, firstname: 'Anna', lastname: 'Carolyn'}
]
let passports = [
{country: 'France', passportNumber: 111201, issueDate:
'2014-12-07',expirationDate:'2024-12-07'},
{country: 'USA', passportNumber: 901222, issueDate:
'2019-11-07',expirationDate:'2027-11-07'}
]
await Client.bulkCreate(clients);
await Passport.bulkCreate(passports);

for (let i = 0; i < passports.length; i++){


passport = passports[i];
cl = clients[i];
pass = await Passport.findOne({where : { passportNumber:
passport.passportNumber}});
client = await Client.findOne({where : { id: cl.id}});
await client.setPassport(pass);
}

// insert dummy sales


let dummy_sales = [
{saledate: '2020-01-15', saletext: 'sale 1', clientId: 100},
{saledate: '2020-01-18', saletext: 'sale 2', clientId: 100},
{saledate: '2020-01-21', saletext: 'sale 3', clientId: 110},
{saledate: '2020-01-25', saletext: 'sale 4', clientId: 110},
{saledate: '2020-02-25', saletext: 'sale 5', clientId: 110},
]
await Sale.bulkCreate(dummy_sales);

// insert dummy items


let dummy_items = [
{itemname: 'Pocket knife—Nile', itemtype: "E",itemcolor:"Brown"},
{itemname: 'Pocket knife—Avon', itemtype: "E",itemcolor:"Brown"},
{itemname: 'Compass', itemtype: "N",itemcolor:"_"},
{itemname: 'Hammock', itemtype: "F",itemcolor:"Khaki"},
{itemname: 'Safari cooking kit', itemtype: "E",itemcolor:"_"}
]
await Item.bulkCreate(dummy_items);

let dummy_sale_items = [
{quantity: 2, price: 10, saleno: 1, itemno: 3},
{quantity: 3, price: 24, saleno: 1, itemno: 1},
{quantity: 1, price: 8, saleno: 2, itemno: 1},
{quantity: 2, price: 10, saleno: 3, itemno: 3},
{quantity: 3, price: 60, saleno: 3, itemno: 5},
{quantity: 1, price: 20, saleno: 4, itemno: 4},
{quantity: 1, price: 10, saleno: 4, itemno: 2},
{quantity: 2, price: 10, saleno: 5, itemno: 3}
]

await SaleItem.bulkCreate(dummy_sale_items);
}
Défi : créez une route HTTP GET /items/lowestup qui envoie l'article au prix le plus bas.

Il existe une fonctionnalité intéressante dans Sequelize appelée Virtual fields. Les
champs virtuels sont des champs que Sequelize remplit, mais en réalité ils n'existent
même pas dans la base de données. Par exemple, disons que nous avons les attributs de
prix et de quantité pour un SaleItem. Ce serait bien d'avoir un moyen simple d'obtenir
directement le prix unitaire (unitprice) ! Nous pouvons combiner l'idée des getters avec le
type de données spécial que Sequelize fournit pour ce genre de situation :
DataTypes.VIRTUAL.

Le prix unitaire d'un article peut être calculé en divisant le prix par la quantité dans la table
“saleItem”. En SQL, nous pouvons simplement le faire en ajoutant une colonne calculée
et en lui donnant un alias. Dans Sequelize, pour ce faire, ouvrez votre modèle SaleItem
(models/saleitem.js) et ajoutez un attribut "unitprice" au modèle et dans la fonction
getter, vous pouvez renvoyer le résultat du prix/quantité comme suit. Le champ
VIRTUAL n'entraîne pas l'existence d'une colonne dans la table. En d'autres termes, le
modèle ci-dessous n'aura pas de colonne de prix unitaire. Cependant, il semblera l'avoir!
Notez que pour pouvoir obtenir le prix unitaire à partir de la fonction findAll(), vous devez
vous assurer que les colonnes utilisées pour obtenir ce champ (dans ce cas la quantité et
le prix) sont incluses dans les attributs renvoyés.
Après avoir mis à jour le modèle SaleItem, accédez à votre fichier index.js (où se trouve
le serveur Express), et ajoutez la route suivante pour obtenir l'article au prix le plus bas.
Testez la route et assurez-vous qu'elle fonctionne.
Exercice 4 : Créez une route qui retourne le client qui a dépensé le plus d'argent. La sortie
pourrait ressembler à ceci :
Exercice 5 : Chaque article (item) a un type d'article. Lorsque nous avons créé le modèle
"Item", nous avons défini le type de chaque article (itemtype) sous forme de texte codé
en dur.

Une meilleure approche consiste à créer un modèle "itemType" séparé et à l'associer au


modèle "item".

1. Mettez à jour le modèle "item" actuel et créez le modèle "itemType", puis associez
les deux modèles l'un à l'autre.
2. Mettez à jour la fonction seed_data(), ajoutez des types d'articles factices et liez-les
aux articles.
a. Lier un article (item) à un type d'article (itemType) doit être aléatoire. Vous
parcourez les articles et, pour chaque item, vous sélectionnez un itemType
aléatoire dans la base de données (recherchez comment utiliser une fonction
random avec Sequelize) et liez le type d'article renvoyé à l'article.
3. Créez la route GET /itemtypes/:id/items qui renvoie tous les articles appartenant à
un type d’article donné.
4. Créez la route GET /itemtypes/bestsale qui renvoie le type d'articles qui a le plus
grand nombre d'articles vendus (utilisez le champ de quantité).
Un exemple de sortie de l'exercice 5.3 :

Un exemple de sortie de l'exercice 5.4 :

Vous aimerez peut-être aussi