Node.js ORMs: Why it's bad for you?
Early in the development of software programs, Node.js ORM was widely used and considered essential. As a result, programmers were only trained to work with ORM and not think about projects that didn't use ORM.
As programmers gained experience, they realized that ORM added unnecessary complexity to projects, but they could write or design a project without it.
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?
In programming, Object-relational mapping (ORM) refers to a mapping technique, a process, that maps relational database systems to objects. Using an object-oriented programming language, it converts data to incompatible type systems.
By using ORM, data migration between databases can be streamlined and seamless. ORM maintains objects despite changes in the apps and sources of database access, since different database systems access data in different ways.
One should not use ORM for a variety of reasons. Prior to going into them, let's examine some of the advantages of ORM.
ORM provides the following benefits if used properly:
- Easy switching between databases
- Reduces duplication of code
- Puts less emphasis on writing interfaces and more on business logic
- Object-oriented queries are converted to SQL for multiple tables.
#2 Issues with ORM
ORM usually introduce the following mentioned problems to web developers while working on several projects:
- The speed of ORM does not allow for the optimization of code, as it has its own speed.
- It is difficult to find specific codes in documentation, as it is fragmented and often massive.
- Visibility into how things are worked on is minimal.
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 Should we use an ORM?
Here are three important reasons why one must be cautious of using an ORM.
i.) Maybe you are learning or using ORM incorrectly
ORM is often learned by developers to save themselves time and effort learning SQL underlying concepts. SQL is widely believed to be difficult to learn, and that learning an ORM can simplify code writing or make writing applications easier.
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.
Interestingly, ORM also incorporates some of the most sophisticated libraries available. There are many ORM libraries and syntaxes, which makes it difficult to become familiar with all of them. One faces this issue particularly when switching from one platform to another, such as from 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.
Usage of Sequelize:
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.) Inefficiencies in complex ORM calls
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.) A single ORM can't perform every task
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
An ORM is ideal and suitable for beginner web developers or small-scale projects. In the case of more complex queries or subqueries, and when ORMs are scalable to a large number of documents, they can often become bottlenecks.
By adding a database driver and ORM's additional complexity to the process, it can be difficult to generate a dynamic query. Therefore, take this into consideration whenever you are planning your next project.
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.
#1 Solution for Logs, Traces & Metrics
APM
Kubernetes
Logs
Synthetics
RUM
Serverless
Security
More