After spending over 10 years building production-grade APIs in languages like Java, I’ve had the chance to work with various backend technologies. Lately, I’ve been diving into Node.js. As much as it’s been praised as a production ready alternative to languages such as Java, Python and Go, I’ve run into several issues that make Node.js less ideal for serious backend work.
In this post, I’ll share some of the key limitations I’ve come across with Node.js, especially compared to my experience with Java.
1. Object Equality and Data Structures
In Node.js, object comparison works strictly based on references, meaning that two objects are considered equal only if they point to the exact same memory location e.g.
type Person = {
name: string
age: number
};
const timA = {
name: 'Tim',
age: 25
}
const timB = {
name: 'Tim',
age: 25
}
console.log(timA === timA) // true, reference is the same
console.log(timB === timB) // false, references are the same
Wait! I can just write a custom equality method:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
equals(other: Person): boolean {
return other instanceof Person
&& this.name === other.name
&& this.age === other.age;
}
}
const timA = new Person('Tim', 25);
const timB = new Person('Tim', 25);
console.log(timA.equals(timA)); // true, same object
console.log(timA.equals(timB)); // true, deep comparison shows they are equal
Problem solved… right?
The problem
When building API’s, dealing with complex objects is part and parcel of the engineering process. In addition to this, being able to store and retrieve them from data structures such as a Map
and Set
is quite common. The complication is that while these custom comparison functions allow for direct equality checks on the object, it won’t be respected by any type of data structure e.g.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
equals(other: Person): boolean {
return other instanceof Person && this.name === other.name && this.age === other.age;
}
}
const timA = new Person('Tim', 25);
const timB = new Person('Tim', 25);
// Checking equality using equals method
console.log(timA.equals(timB)); // true, deep comparison shows they are equal
// Using Map and Set
const personMap = new Map<Person, string>();
personMap.set(timA, 'First Tim');
personMap.set(timB, 'Second Tim');
console.log(personMap.size); // 2, because timA and timB are different references
const personSet = new Set<Person>();
personSet.add(timA);
personSet.add(timB);
console.log(personSet.size); // 2, because timA and timB are different references
As stated above, the Map
and Set
data structures in JavaScript rely on reference equality ===
to determine whether two keys (in the case of Map) or elements (in the case of Set) are identical. This behaviour is ingrained into the language and can’t be overridden.
How is this is different in Java or Python?
This is different from languages like Python and Java, where you can override the equality behaviour by customising methods such as __eq__
in Python or equals()
in Java e.g.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
if isinstance(other, Person):
return self.name == other.name and self.age == other.age
return False
def __hash__(self):
# Combine the name and age into a hash value
return hash((self.name, self.age))
# Creating two instances with the same data
timA = Person('Tim', 25)
timB = Person('Tim', 25)
# Showing the equality
print(timA == timB) # True, because they are equal in terms of content
# Using Person instances in a set
person_set = {timA, timB}
print(len(person_set)) # 1, because timA and timB are considered equal
# Using Person instances as keys in a dict
person_dict = {timA: 'First Tim', timB: 'Second Tim'}
print(len(person_dict)) # 1, because timA and timB are considered equal
print(person_dict[timA]) # 'Second Tim', as the value was updated by timB
These methods allow developers to define what it means for two objects to be considered equal based on their content rather than just their reference. More importantly, these overridden equality checks are automatically respected by key data structures i.e. Map
, Set
.