Node.js ORMs and Why You Shouldn’t Use Them

In the earlier days of programming, Node.js ORM was widely used and was considered as the life of software programs and projects. As a result, programmers were trained to only use ORM and not think about projects not using ORM. But with experience, programmers started to realize that ORM only added unnecessary complexities into the projects, but they can write or design a project without ORM with ease.

In today’s article, we shall discuss what Node.js ORM is, problems using ORM, ORM really required and necessary, Sequelize with Node.js, and more.

So, let’s just get right into it.

  1. What is Node.js ORM?
  2. Problems with using ORM
  3. Is ORM really required?

#1 What is Node.js ORM?

ORM (Object-Relational Mapping) is a programming technique or process of mapping between relational database systems and objects. Using an object-oriented programming language, it converts data to incompatible type systems.

ORM is commonly used to make the data migration between databases seamless and in a streamlined manner. This is because several database systems access data in different ways, and ORM maintains objects even if the apps and sources of the database access change with time.

There are multiple reasons why one must not use ORM. But before we jump on them, let us have a look at some of the advantages of ORM.

ORM provides the following benefits if used properly:

  1. Easily switches from one database to another
  2. Avoid redundant code
  3. Focuses less on the writing interfaces and more on business logic
  4. The query for multiple tables (converts object-oriented query to SQL)

#2 Problems with using ORM

ORM usually introduce the following mentioned problems to web developers while working on several projects:

  • It is difficult and nearly impossible to optimize a code to experience an enhanced speed, as ORM has its own speed.
  • The documentation is fragmented and usually massive, making the search difficult for specific codes.
  • It offers minimum visibility into what is happening.

All these problems combined hamper the productivity of web developers.

Although ORM brings several issues and problems, they are powerful tools compatible with communicating with SQL (Structured Query Language) backends such as MySQL, MSSQL, SQLite, and PostgreSQL, an open-source source and powerful SQL server.

There are also ORMs that are compatible with communicating with NoSQL backends like Mongoose ORM backed up with MongoDB. So the question then arises, are ORMs useful and necessary?

Let us find out.

#3 Is ORM really required?

Here are three important reasons why one must be cautious of using an ORM.

i.) You are learning the incorrect thing or using ORM for the wrong purpose

Many people and developers learn ORM to save their effort and time to learn the underlying SQL. Many people believe that SQL is competitively hard to learn and that learning an ORM can simplify their code writing process, or it is easy to write applications using one language instead of two.

This mindset might seem true at first read because ORM is written in the exact same language as the other applications, while SQL uses a completely unique and different syntax comparatively. However, it is not completely true, and there comes a problem with it.

The thing is, ORM also uses some of the complex libraries one can get their hands on. ORM’s surface area is very wide, and learning all its libraries and syntax inside out is not a cup of tea. This case is especially true when one switches from one platform to another, like JS/Node.js to C#/.NET.

In addition, it also applies true when one switches from one ORM to the other in the same platform, such as Bookshelf to Sequelize with Node.js.

Let us look at the following ORM examples that generate a list of fruits that are tropical.

Sequelize

// $ npm install sequelize pg

const Sequelize = require('sequelize');
const { Op, DataTypes } = Sequelize;
const connection = require('./connection.json');
const DISABLE_SEQUELIZE_DEFAULTS = {
  timestamps: false,
  freezeTableName: true,
};

const sequelize = new Sequelize({
  database: connection.database,
  username: connection.user,
  host: connection.host,
  port: connection.port,
  password: connection.password,
  dialect: 'postgres',
  operatorsAliases: false
});

const Item = sequelize.define('item', {
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  name: { type: DataTypes.STRING },
  type: { type: DataTypes.STRING }
}, DISABLE_SEQUELIZE_DEFAULTS);


Item
  .findAll({where: {type: 'fruits'}})
  .then(rows => {
    console.log('tropical:');
    for (let row of rows) {
      console.log(`${row.dataValues.id}t${row.dataValues.name}`);
    }
    sequelize.close();
  });

Bookshelf

// $ npm install bookshelf knex pg

const connection = require('./connection.json');
const knex = require('knex')({
  client: 'pg',
  connection,
  // debug: true
});
const bookshelf = require('bookshelf')(knex);

const Item = bookshelf.Model.extend({
  tableName: 'item'
});

Item
  .where('type', 'fruits')
  .fetchAll()
  .then(result => {
    console.log('tropical:');
    for (let row of result.models) {
      console.log(`${row.attributes.id}t${row.attributes.name}`);
    }
    knex.destroy();
  });

Objection:

// $ npm install knex objection pg

const connection = require('./connection.json');
const knex = require('knex')({
  client: 'pg',
  connection,
  // debug: true
});
const { Model } = require('objection');

Model.knex(knex);

class Item extends Model {
  static get tableName() {
    return 'item';
  }
}

// select "item".* from "item" where "type" = ?
Item
  .query()
  .where('type', '=', 'fruits')
  .then(rows => {
    for (let row of rows) {
      console.log(`${row.id}t${row.name}`);
    }
    knex.destroy();
  });

The simple read operation syntax varies largely between all these examples. Furthermore, with the increasing complexity, the ORM syntax varies more between implementations.

There are hundreds of ORMs for each platform and dozens of ORMs for Node.js. Learning each and every one of these tools would be a huge nightmare. In contrast, there are only a few SQL dialects one needs to learn or worry about when it comes to SQL. Once you learn to generate queries using raw SQL, you can easily use and transfer that knowledge to several different platforms.

ii.) Complex ORM calls could be inefficient

As we discussed above, ORM’s function is to take the underlying data that is stored in the database to map it to an object through which one can interact with their applications. In such situations, when one uses ORM to fetch certain data, it leads to many inefficiencies and unproductivity.

ORM queries are a lot more different than other queries written by pg driver or generated with knex query builder, as it tries to fetch more information than one requires or asks for. The ORM queries also generally have more operation costs than other defined queries. This behavior of ORM creates inconvenience for developers, so it is recommended not to use them.

iii.) An ORM cannot do each and every function

Not every query can be represented as an ORM function. Therefore, when it comes to generating these ORM queries, one must generate the SQL query by hand. This simply means that when a developer is working on one such project, he is required to know and use both the ORM syntax and the underlying SQL query syntax.

When a query contains a subquery, ORM functions do not work too well or properly in such situations. ORM needs to use and inject some raw SQL within the query interface for a better and clear representation of a query. In order to execute raw SQL, Sequelize provides a .query() method, which makes it feel that one was using the underlying database driver.  Objection ORMs and bookshelf offer access to the raw Knex object, which one can provide during instantiation. The Knex object also offers a .raw() method for raw SQL execution. And while on the other hand, Sequelize provides the Sequelize.literal() method to intersperse raw SQL into several parts of a Sequelize ORM call. But in order to generate certain queries, one is still needed to know to have an underlying knowledge of the underlying SQL.

Conclusion

ORMs are ideal and good for small-scale projects or beginner web developers. But when a project requires more complex queries or sub-queries and starts to scale to a large number of documents or queries, ORMs usually become a bottleneck. Generating a dynamic query with the help of a database driver and ORM’s additional complexity can get too difficult. Therefore, whenever you are considering working on your next project, take this point into consideration.


Monitor Your Database with Atatus

Atatus provides you an in-depth perspective of your database performance by uncovering slow database queries that occur within your requests, as well as transaction traces, to give you actionable insights. With normalized queries, you can see a list of all slow SQL calls to see which tables and operations have the most impact, know exactly which function was used and when it was performed, and see if your modifications improve performance over time.

Atatus can be beneficial to your business, which provides a comprehensive view of your application, including how it works, where performance bottlenecks exist, which users are most impacted, and which errors break your code for your frontend, backend, and infrastructure.

Try your 14-day free trial of Atatus.

Vaishnavi

Vaishnavi

CMO at Atatus.
Chennai

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.