A quoi sert un ORM ?

Un ORM permet de faire le pont entre une base de données comme SQLite et la représentation d’un objet dans un langage de programmation comme Typescript.

Pour cela, un ORM fournit un ensemble d’outils qui permettent d’intéragir entre le code et la base de données. Ces outils permettent de:

  • Définir le sockage des données en base avec les modèles
  • Récupérer, sauvegarder, supprimer un objet Typescript en base de données (CRUD)
  • Construire des requêtes SQL complexes pour la base de données

Dans cet article, on va voir quelques utilisations de base d’un ORM avec pour exemple Sequelize qui utilisera une base de données SQLite pour continuer sur la lancée de mon dernier article.

Installation de Sequelize

Sequelize s’installe via npm: npm install --save sequelize. On installe ensuite les définitions Typescript de Sequelize: npm install --save-dev @types/sequelize. Si vous n’avez pas installé le driver SQLite dans le projet, vous devez le faire (même procédure que dans l’article précédent).

Connexion à la base SQLite

Le premier avantage d’un ORM est qu’il permet d’intéragir avec plusieurs types de bases de données SQL, et de prendre en charge les spécificités de chaque base de données tout en fournissant une interface générique. Sequelize permet de se connecter aux bases de données suivantes:

  • SQLite
  • MySQL/MariaDB
  • PostgreSQL

Pour commencer, on crée un fichier database.ts qui contient la connexion à la base de données SQLite:

import { Sequelize } from "sequelize";

export const sequelize = new Sequelize(
  { dialect: 'sqlite', storage: __dirname + '/db.sqlite' }
);

Pour créer une connexion à la base SQLite, on indique à Sequelize que le type de base de données SQL (sqlite). Puis on précise que le stockage des données se fait dans le fichier db.sqlite, situé à la racine du projet.

C’est l’unique étape où on doit spécifier à Sequelize qu’on utilise SQLite. Si on choisit par exemple de migrer vers MariaDB par la suite, on devra seulement modifier ce fichier de connexion.

Définition d’un modèle de données

On part du mini-projet réalisé dans l’article d’intégration de SQLite avec Typescript dans lequel on a déjà installé le driver SQLite et réalisé un petit script d’introduction.

Dans cet exemple, on a créé une table d’articles, et inséré quelques exemples en base de données. Puis on a réalisé quelques requêtes vers la base SQLite.

L’objectif va être de transformer ces requêtes SQL en appels Typescript à l’ORM Sequelize.

Un modèle Sequelize va faire le lien entre la table SQL articles et l’objet Javascript Article. On stocke ce fichier dans models/article.ts.

import { DataTypes } from "sequelize";
import { sequelize } from "../database";

export const ArticleModele = sequelize.define('Article', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
  },
  titre: {
    type: DataTypes.STRING,
    allowNull: false
  },
  description: {
    type: DataTypes.TEXT,
    allowNull: false
  }
}, { tableName: 'articles', timestamps: false });

La table articles présente trois colonnes, id, titre et description. Les propriétés du modèle Article correspondent aux colonnes de la base de données.

On a précisé le nom de la table dans laquelle sont stockées les articles via l’option tableName: 'articles'.

L’option timestamps: false désactive l’ajout de deux colonnes createdAt et updatedAt qui servent à stocker la date d’insertion et de modification d’un article.

Vous pouvez demander à Sequelize de créer la table correspondant au modèle Article avec ArticleModele.sync().

Manipulation des données

Dans l’article précédent sur SQLite, on a vu comment manipuler les données de la base avec le driver SQLite.

On écrivait nos requêtes SQL (parfois avec des paramètres), puis on demandait au driver de les exécuter et de récupérer les résultats.

C’est maintenant l’ORM Sequelize qui va construire les requêtes SQL à partir des arguments qu’on va lui fournir.

Lecture

Comme dans le premier tutoriel, on va récupérer les deux articles dont la description est la plus longue:

import { ArticleModele } from "./models/article";

async function main () {
  const deuxArticlesPlusLongs = await ArticleModele.findAll({
    order: [[sequelize.fn('length', sequelize.col('description')), 'DESC']],
    limit: 2
 });
}

main();

On peut afficher la requête générée par Sequelize avec l’option logging: console.log passée à la méthode findAll. L’appel à Sequelize précédent génère cette requête SQL:

SELECT `id`, `titre`, `description`
FROM `articles` AS `Article`
ORDER BY length(`description`) DESC
LIMIT 2

La requête SQL générée par Sequelize correspond à la requête précédemment écrite à la main. Sauf que cette fois, on a seulement indiqué la partie importante de la requête, à savoir le nombre d’articles à récupérer et la méthode de tri.

Modification et sauvegarde d’une instance

Avec Sequelize, on peut faire des modifications sur les objets retournés par la méthode findAll de Sequelize, puis les sauvegarder avec les méthodes set et save disponibles sur chacun des objets renvoyés.

import { ArticleModele } from "./models/article";

async function main () {
  const deuxArticlesPlusLongs = await ArticleModele.findAll({
    order: [[sequelize.fn('length', sequelize.col('description')), 'DESC']],
    limit: 2
 });

  const premierArticle = deuxArticlesPlusLongs[0];
  premierArticle.set('titre', 'Article le plus long');
  await premierArticle.save();
}

main();

Suppression

Pour supprimer une instance Sequelize de la base de données, il suffit d’appeler la méthode destroy.

  const secondArticle = deuxArticlesPlusLongs[1];
  await secondArticle.destroy();

Ce morceau de code a supprimé le deuxième article retournée par findAll de la base SQLite.

Conclusion

L’ORM Sequelize permet de faciliter l’intégration d’une base de données SQL externe à votre application Typescript. Vous pouvez récupérer une archive du projet correspondant à ce tutoriel.

Sequelize fournit des méthodes permettant de se connecter à la base, de définir son schéma de données et de réaliser les opérations CRUD.