Type something to search...
The ORM Dilemma: To Use or Not to Use

The ORM Dilemma: To Use or Not to Use

Introduction

In the world of software engineering, seasoned professionals are often confronted with pivotal decisions that wield substantial influence over the course and outcome of a project. Among these choices is the perennial dilemma of whether to employ an Object-Relational Mapping (ORM) tool for handling database interactions. Should you eschew an ORM entirely? Or should you harness the convenience it offers when working with technologies like Node.js and TypeScript, particularly in conjunction with Express? This article endeavors to dissect the pros and cons of employing an ORM and provide insights into scenarios where alternatives might be a more judicious choice.

A Glimpse into ORM

Before we plunge into the nuanced discourse of whether or not to utilize ORM, let’s briefly acquaint ourselves with what ORM entails and the functions it serves. At its core, an Object-Relational Mapping tool is a software framework that streamlines communication between an application and a relational database. It adroitly abstracts the intricacies of database interactions, enabling developers to interact with database entities as if they were conventional objects in their chosen programming language.

The Advantages of ORM

ORM proffers several compelling advantages that render it an alluring choice for developers:

  1. Database Complexity Abstraction: One of ORM’s prime virtues lies in its ability to shield developers from the labyrinthine depths of SQL and the idiosyncrasies of database-specific operations. This abstraction proves invaluable, particularly when handling intricate queries.

  2. Seamless Language Integration: ORM seamlessly dovetails with the programming language being employed. This harmonious integration permits developers to manipulate database records using native language constructs, culminating in code that is not only more maintainable but also notably more readable.

  3. Cross-Database Compatibility: A plethora of ORM libraries offer robust support for multiple database systems, making the task of transitioning from one database to another a relatively painless endeavor. This flexibility can prove invaluable in evolving projects.

  4. Expedited Development: ORM expedites the development process by alleviating the burden of crafting boilerplate code for routine database operations such as Create, Read, Update, and Delete (CRUD) actions.

While the advantages of ORM are compelling, we must now pivot our attention to the flip side and meticulously examine the disadvantages.

The Disadvantages of ORM

Undoubtedly, ORM is a potent tool, but it does not traverse the software landscape unburdened. Here are the principal disadvantages that warrant careful consideration:

1. Performance Overheads

Introducing ORM into the mix invariably adds an abstraction layer between your application and the database. While this abstraction might be conducive to code readability and maintainability, it often exacts a performance toll. The SQL queries generated by ORM may not always be as optimized as handcrafted SQL, particularly when grappling with intricate queries or high-throughput scenarios.

2. The Learning Curve

The adoption of an ORM tool often entails navigating a learning curve. Developers must acquaint themselves with the intricacies of the ORM’s Application Programming Interface (API), a process that can consume valuable time. Additionally, comprehending how the ORM translates high-level operations into SQL queries becomes paramount when striving to optimize performance.

3. Limited Control

ORM’s modus operandi involves abstracting database operations, which inherently implies a degree of control relinquishment over the SQL queries it generates. In scenarios necessitating fine-grained control over queries for performance optimization, the rigid structure of ORM might prove restrictive.

4. Code Bloat

While ORM effectively trims the fat of boilerplate code in common scenarios, it can paradoxically lead to code bloat in more complex situations. Achieving fine-grained control over database interactions often mandates the crafting of custom code within the confines of the ORM framework, a practice that can be verbose and challenging to maintain.

The Necessity of ORM: A Delicate Balance

Having scrutinized both the advantages and disadvantages of ORM, a pertinent question lingers: Is an ORM an indispensable tool? The resounding answer is, “It depends.”

When to Contemplate the Adoption of ORM

  1. Rapid Prototyping: If your project’s primary objective revolves around expeditiously materializing a minimum viable product (MVP), an ORM can serve as an invaluable ally. It liberates you from the quagmire of SQL queries, allowing you to channel your energies into molding the logic of your application.

  2. Team Expertise: If your development ensemble boasts greater proficiency in the programming language of your choice as opposed to SQL, then opting for ORM is a judicious course of action. It empowers your team to function efficaciously, culminating in the delivery of high-quality code.

  3. Cross-Database Compatibility: Should your project necessitate support for multiple database systems, an ORM shines as a pragmatic choice. It transcends the discrepancies that segregate databases, rendering transitions from one database to another a relatively seamless endeavor.

When to Exercise Caution with ORM

  1. Exacting Performance Demands: In scenarios where your application is subjected to stringent performance prerequisites, particularly when grappling with intricate queries or high transaction rates, the deployment of raw SQL or a database-specific library might be the wiser selection. This affords you the latitude to fine-tune SQL queries for optimal performance.

  2. Database-Specific Features: Projects that heavily rely on database-specific features or the execution of advanced SQL operations that elude the grasp of ORM might discover greater efficiency in resorting to native SQL.

  3. Requisite Query Control: Should the exigencies of your project necessitate granular control over the SQL queries executed by your application, the use of ORM may impose unwarranted limitations. In such instances, the composition of custom SQL queries emerges as the more judicious course of action.

Pragmatic Alternatives to ORM

For scenarios where the adoption of ORM might not align seamlessly with your project’s requisites, it becomes imperative to explore pragmatic alternatives. Below, we outline a couple of alternatives worthy of contemplation.

1. Query Builders

Query builders, exemplified by libraries such as Knex.js, proffer a middle-ground solution. They empower you to programmatically construct SQL queries using JavaScript, striking a harmonious balance between raw SQL and the abstraction provided by ORM. Query builders become especially invaluable when the need for query control converges with the desire for code that remains readable.

Consider a TypeScript and PostgreSQL example using Knex.js:

1
import * as Knex from 'knex';
2
3
const knex = Knex({
4
client: 'pg',
5
connection: {
6
host: 'your-database-host',
7
user: 'your-username',
8
password: 'your-password',
9
database: 'your-database-name',
10
},
11
});
12
13
async function getUsers() {
14
return await knex.select('*').from('users');
15
}
16
17
async function addUser(user: any) {
18
return await knex('users').insert(user);
19
}
20
21
async function getComplexData(
22
country: string,
23
orderDate: Date,
24
category: string,
25
) {
26
// Define Common Table Expressions (CTEs)
27
const usersFromCountry = knex('users').where('country', country);
28
const ordersFromLast30Days = knex('orders').where(
29
'order_date',
30
'>=',
31
orderDate,
32
);
33
const booksOrderItems = knex('order_items')
34
.join('products', 'order_items.product_id', '=', 'products.id')
35
.where('products.category', category);
36
37
// Build the main query using CTEs
38
const query = knex
39
.with('users_from_country', usersFromCountry)
40
.with('orders_from_last_30_days', ordersFromLast30Days)
41
.with('books_order_items', booksOrderItems)
42
.select(
43
'users_from_country.name',
44
'orders_from_last_30_days.order_date',
45
'books_order_items.product_name',
46
)
47
.from('users_from_country')
48
.leftJoin(
49
'orders_from_last_30_days',
50
'users_from_country.id',
51
'orders_from_last_30_days.user_id',
52
)
53
.leftJoin(
54
'books_order_items',
55
'orders_from_last_30_days.id',
56
'books_order_items.order_id',
57
);
58
59
return query;
60
}

2. Raw SQL

In cases where optimal performance is paramount, and you yearn for absolute control over your queries, the embrace of raw SQL queries emerges as the superior choice. While this path demands a heightened level of diligence to fend off the specter of SQL injection, it confers an unparalleled degree of control and efficiency.

Here’s an example of TypeScript code executing a raw SQL query with the pg library for PostgreSQL:

1
import {Pool} from 'pg';
2
3
const pool = new Pool({
4
user: 'your-username',
5
host: 'your-database-host',
6
database: 'your-database-name',
7
password: 'your-password',
8
port: 5432, // PostgreSQL default port
9
});
10
11
async function getUsers() {
12
const client = await pool.connect();
13
try {
14
const result = await client.query('SELECT * FROM users');
15
return result.rows;
16
} finally {
17
client.release();
18
}
19
}
20
21
async function addUser(user: any) {
22
const client = await pool.connect();
23
try {
24
const query = {
25
text: 'INSERT INTO users(name, email) VALUES($1, $2)',
26
values: [user.name, user.email],
27
};
28
await client.query(query);
29
} finally {
30
client.release();
31
}
32
}
33
34
async function getComplexData(
35
country: string,
36
orderDate: Date,
37
category: string,
38
) {
39
const client = await pool.connect();
40
41
try {
42
// Define the SQL query with placeholders for parameters
43
const sqlQuery = `
44
WITH
45
users_from_country AS (
46
SELECT * FROM users WHERE country = $1
47
),
48
orders_from_last_30_days AS (
49
SELECT * FROM orders WHERE order_date >= $2
50
),
51
books_order_items AS (
52
SELECT * FROM order_items
53
JOIN products ON order_items.product_id = products.id
54
WHERE products.category = $3
55
)
56
57
SELECT
58
users_from_country.name,
59
orders_from_last_30_days.order_date,
60
books_order_items.product_name
61
FROM users_from_country
62
LEFT JOIN orders_from_last_30_days
63
ON users_from_country.id = orders_from_last_30_days.user_id
64
LEFT JOIN books_order_items
65
ON orders_from_last_30_days.id = books_order_items.order_id
66
`;
67
68
// Execute the SQL query with parameters
69
const result = await client.query(sqlQuery, [country, orderDate, category]);
70
return result.rows;
71
} finally {
72
client.release();
73
}
74
}

Conclusion

In the realm of software engineering, complexity often begets simplicity. When confronted with the decision of whether to adopt ORM in the context of Node.js, TypeScript, and Express, it’s wise to consider a more streamlined approach. Instead of embracing ORM, consider these insights:

  1. Prioritize Performance: For projects with demanding performance requirements, especially those involving intricate queries or high transaction volumes, opting for raw SQL or a database-specific library can lead to more efficient outcomes.

  2. Embrace Database Expertise: If your team is well-versed in SQL and the intricacies of the chosen database, utilizing that expertise directly can lead to more optimized database interactions.

  3. Maintain Control: When granular control over SQL queries is imperative, using ORM may limit your flexibility. Crafting custom SQL queries allows you to fine-tune operations for optimal performance.

In essence, the decision not to use ORM can be a strategic one, particularly when project goals align with performance, database expertise, and query control. So, when navigating the ORM dilemma, consider a more direct route to achieve your project’s objectives.

Related Posts

Check out some of our other posts

Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example

Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example

Introduction All code from this tutorial as a complete package is available in this repository. If you find this tutorial helpful, please share

read more
Setting up JWT Authentication in Typescript with Express, MongoDB, Babel, Prettier, ESLint, and Husky: Part 2

Setting up JWT Authentication in Typescript with Express, MongoDB, Babel, Prettier, ESLint, and Husky: Part 2

Introduction Why do we even need an authentication mechanism in an application? in my opinion, it doesn't need to be explained. The phrases authentication and authorization have likely crossed yo

read more
Run TypeScript Without Compiling

Run TypeScript Without Compiling

Introduction In this post, I will show you how to run TypeScript without compiling it to JavaScript. This is useful for debugging and testing. In this post, I will show you how to do it. Setu

read more
Introduction to Spring Boot Framework

Introduction to Spring Boot Framework

Introduction For creating web apps and microservices, many developers utilize the Spring Boot framework. The fact that it is built on top of the Spring Framework and offers a number of advantages

read more
Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript

Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript

Introduction In this tutorial, we will be building a customizable image slider in React using hooks, SCSS, and TypeScript. An image slider is a common UI element used in web applications to displ

read more