Premiers pas avec Sequelize, un ORM Typescript

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(). N’utilisez cette option que pour le développement. En production, Sequelize recommande d’utiliser Umzug pour réaliser vos migrations de base de données

Abonne-toi !

On te partage nos meilleurs conseils et découvertes sur Python et PostgreSQL toutes les deux semaines

Ajout d’un type TypeScript au modèle

Si on inspecte le type de ArticleModele, on obtient ModelCtor<Model<any, any>>

Les propriétés du modèle déduites par TypeScript sont donc de type any pour l’instant. L’initialisation d’un modèle Sequelize ne suffit pas pour obtenir un type utile et précis

Heureusement, le guide d’intégration de TypeScript avec Sequelize donne la solution à ce problème:

A la place de définir un constructeur de modèle via la méthode sequelize.define, on va définir une classe ArticleModele qui hérite de Model, puis initialiser cette classe via la méthode init héritée de Model

import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from "sequelize";
import { sequelize } from "../database";

export class ArticleModele extends Model<InferAttributes<ArticleModele>, InferCreationAttributes<ArticleModele>> {
    declare id: CreationOptional<number>;
    declare titre: string;
    declare description: string;
};

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

Le mot clef declare de TypeScript permet de déclarer que la variable de classe va exister dans le code JavaScript sans que le compilateur TypeScript ne crée cette variable de classe. Ce comportement est souhaitable car les propriétés id, titre et description seront ajoutées à la classe lors de l’appel à la méthode ArticleModele.init

La propriété id est annotée comme étant optionnelle via CreationOptional lors de la sauvegarde d’un nouveau modèle en base de données car la colonne id est auto-générée par AUTOINCREMENT

Le modèle ArticleModele dispose maintenant d’un type TypeScript, ce qui sera utile par la suite

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.

Le code est aussi devenu plus adaptable: si par exemple on ajoute une colonne à la table articles, il sera seulement nécessaire d’ajouter cette colonne à la définition Sequelize du modèle, peu importe le nombre d’appels aux méthodes du modèle. Avec des requêtes SQL écrites à la main, il aurait fallu ajouter cette colonne à chaque requête

Modification et sauvegarde d’une instance de modèle

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();

Notez que si l’instance de modèle est déjà à jour (i.e pas d’opération modifiant l’instance du modèle exécutée depuis le dernier save), Sequelize ne lancera pas de requête SQL d’UPDATE

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

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.

Se connecter pour laisser un commentaire.
Comment créer sa signature mail en HTML