What is Sequelize ORM, what are its use cases ?

An ORM allows you to bridge your SQL database (SQLite for instance) and Typescript objects

To fulfill that binding between the code and the database, Sequelize & ORMs provide the following tooling and abstractions:

  • Models allow you to define Typescript objects and define how they are stored in the SQL database
  • Models instances have handy methods to perform CRUD operations (create, update, delete) on the database
  • With model querying, write SQL queries to the database from a Typescript query description

Let’s see some classic usages of the Sequelize ORM with SQLite database

Installing Sequelize

Sequelize can be installed with npm, the node package manager, with npm install --save sequelize. Then, you have to install the DefinitelyTyped package for Sequelize in order to use Sequelize with Typescript: npm install --save-dev @types/sequelize

Binding a SQLite database to Typescript using Sequelize

An advantage of an ORM over using raw SQL query is that it will adapt the Typescript model query to any SQL database, considering each SQL implementation specificity. You can use the following SQL databases with Sequelize:

  • SQLite
  • MySQL/MariaDB
  • PostgreSQL

To get started, make sure you have setup your Typescript project and installed the Typescript driver associated to the database of your choice. In this article, we will setup SQLite and use it with Sequelize

We will start with Typescript code that connects to SQLite using Sequelize in the file database.ts:

import { Sequelize } from "sequelize";

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

In order to create a connection to the SQLite backend, we tell SQLite that the implementation of the SQL database we are using is sqlite. Then, we specify that the SQLite DB will be stored in filename db.sqlite, located at project root

Its the only part of the where we will have to tell Sequelize specific information about the SQLite backend. If we choose later in the project to migrate to MariaDB, we will only have to update this file (and to transfer data from SQLite to MariaDB of course)

Defining your first Sequelize model

We will reuse the example project of the integration of SQLite with Typescript and create a model adapted to an existing SQL database schema

In the SQLite example, we created a SQL table containing articles, and inserted a few examples rows in that table. Then, we done some queries on that DB

The goal is to migrate these SQL queries into Typescript calls to Sequelize ORM

So let’s create a Sequelize model that will link the articles SQL table to the Typescript object Article. Let’s store that model definition into models/article.ts

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

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

The table articles has three columns, id, title and description. The properties Article model are mapped to the database columns

We also configured the model to be mapped to the table articles with the option tableName: 'articles'

Another option is whether or not you want to store row creation/update time in the database (adding createdAt/updatedAt model properties). Here we disabled that using timestamps: false

In development mode, you can also create the schema definition of the Article model using synchronization feature ArticleModel.sync(). In production, Sequelize recommends using Umzug to run Database migrations when upgrading your software

Typing the model with TypeScript

If we inspect ArticleModel typing using our IDE, we get ModelCtor<Model<any, any>>

TypeScript have little information about ArticleModel: the ArticleModel attributes and creation attributes are typed as any. The definition of a Sequelize model using sequelize.define is not enough to get a precise and useful type

Fortunately, Sequelize+TypeScript guide give us the solution to this issue:

Instead of defining a Model constructor using sequelize.define, we will create a class ArticleModel inheriting from Model. Then, we will initialize this class with the init method inherited from Model

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

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

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

TypeScript keyword declare is used to tell the TypeScript compiler that the variable will exists in the JavaScript code without the need for TypeScript compiler to define it in compiled JavaScript code. This is useful in this case because the model class variables will be defined on the ArticleModel.init method call

We type the id as optional on creation using CreationOptional because it has a default value in the SQL database: it is generated using AUTOINCREMENT

ArticleModel now has a precise TypeScript definition, which will be useful in the later code when we manipulate model instances

Manipulating data using a Sequelize model

In the previous article on SQLite, we saw how to manipulate data using SQLite driver

We used to write our SQL queries (sometime with parameters), and then ask the driver to execute them, and then fetching the results

We now can perform these operations with Sequelize ORM which will write the queries using the argument operation and query description

Reading data

As in the first tutorial, we want to fetch the two articles in the database with the longest description:

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

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

main();

It is possible to print the query generated and executed by Sequelize using the option logging: console.log. In this case, this SQL code is generated:

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

The SQL generated by Sequelize corresponds to the previously hand writen query. But that time, we only had to specify the business logic part of the query: the limit to apply and the sorting method

The code is also more adaptable: if we add a column to the articles table, we only have to update the Sequelize model, no matter how many calls it had which is not the case using plain SQL statements

Updating and saving a model instance

With Sequelize, it is possible to update the models instances returned by write operations like findAll. When modified with set method, a model instance is not saved in the database. To do that, you can use save method on modified model instance

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

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

  const lengthiestArticle = twoLongestDescriptions[0];
  lengthiestArticle.set('title', 'Lengthiest article');
  await lengthiestArticle.save();
}

main();

If the model is already up to date (no modifications since last model saving), no query will be ran on the database

Removing lines from the Database

To remove an Sequelize model instance from the database, you can use destroy method:

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

Conclusion

Sequelize ORM can facilitate the integration of a SQL database into your Typescript application. The project maintenance is also simplified, since database definition is centralised in models definition, and boilerplate SQL code is written automatically

Before using an ORM, good SQL knowledge is a must, so don’t hesitate to check out the SQLite documentation on SQL syntax and familiarize with SQL logic and possibilities