Matt Angelosanto
Posted on November 1, 2023
Written by David Omotayo✏️
Errors can be a significant challenge for developers, often leading to unnecessary time consumption. We might be quick to point fingers at the programming language or environment, but it is fair to acknowledge that many of these errors stem from developer mistakes and how we use these tools.
Node.js has been around for quite some time and has been instrumental in building robust and sophisticated web services that have scaled effectively and stood the test of time. But, like every other runtime environment or platform, Node.js is susceptible to developer mistakes.
In this article, we'll take a look at some of the most common Node.js errors you may encounter and we’ll discuss how to fix them.
Jump ahead:
- Unhandled exceptions in streams
-
JavaScript heap out of memory
error - Environment compatibility errors
- Network and communication errors
- Additional common Node.js errors
Unhandled exceptions in streams
Streams are a fundamental concept in Node.js for reading and writing to asynchronous data sources such as files, sockets, or HTTP requests. Errors can occur at any time during a stream's lifecycle.
Streams emit errors during various operations, such as reading, writing, piping, or transforming data. The errors are emitted through the stream’s error
event. If you do not attach an error handler to the stream, the errors will propagate up the event loop and potentially crash your application.
Here’s an example of an unhandled exception in streams:
const fs = require("fs");
const readStream = fs.createReadStream("nonexistent-file.txt");
// Without an error handler, this error will crash the application.
readStream.pipe(process.stdout);
Without an error handler, if the connection from the client is terminated abruptly, the readStream
may not be closed when the error occurs. Instead, it will remain open indefinitely, leading to memory leaks in your application.
This will likely result in unexpected behaviors and may lead to errors such as unhandled stream error in pipe
, as shown below:
stream.js:60
throw er; // Unhandled stream error in pipe.
^
Error: socket hang up
at createHangUpError (_http_client.js:200:15)
at Socket.socketOnEnd (_http_client.js:285:23)
at emitNone (events.js:72:20)
at Socket.emit (events.js:166:7)
at endReadableNT (_stream_readable.js:905:12)
at nextTickCallbackWith2Args (node.js:437:9)
at process._tickCallback (node.js:351:17)
To address unhandled exception errors in Node.js streams you can use one of several solutions. Let’s take a look.
Attach error event handlers
Always attach an error event handler to catch and handle errors that occur during stream operations. This ensures that errors are caught and properly managed, preventing your app from crashing:
const fs = require('fs');
const readStream = fs.createReadStream('example-file.txt');
readStream.on('error', (err) => {
console.error('An error occurred:', err.message);
});
readStream.pipe(process.stdout);
Use try-catch
with synchronous code
When working with synchronous code that interacts with streams, you can wrap the code in a try-catch
to handle errors effectively. This will ensure that the program does not crash if an error occurs and that the error is handled in a controlled manner:
const fs = require('fs');
try {
const readStream = fs.createReadStream('example-file.txt', 'utf8');
const dataPromise = new Promise((resolve, reject) => {
let data = '';
readStream.on('data', (chunk) => {
data += chunk;
});
// Handle errors from the stream
readStream.on('error', (err) => {
reject(err); // Reject the promise if an error occurs
});
// When the stream ends, resolve the promise with the data
readStream.on('end', () => {
resolve(data);
});
});
const fileData = await dataPromise;
console.log('File contents:', fileData);
} catch (err) {
console.error('An error occurred:', err.message); // Log error to the console
}
In the code above, we created a try-catch
block that encapsulates a promise that is resolved when the stream ends successfully or is rejected if an error occurs. The error is then caught in the catch
block, where it is logged.
Use the pipeline method
The previous options handle errors efficiently, but it can be unmanageable to attach event handlers to every stream when using the pipe
method. Instead, the pipeline
method offers a much cleaner and more manageable way to handle errors.
A pipeline
is a stream method that takes three arguments: a readable stream (the source of the stream), a writable stream (the destination of the stream), and a callback function that will be called if an error occurs during the process:
const fs = require('fs');
const { pipeline } = require('stream');
const readStream = fs.createReadStream("inputexample.txt");
const writeStream = fs.createWriteStream("outputexample.txt");
pipeline(
readStream, // Readable stream
writeStream, // Writable stream
(err) => {
// Callback function to handle errors
if (err) {
console.error("Pipeline failed:", err);
} else {
console.log("Pipeline succeeded");
}
}
);
The pipeline
method is particularly useful for file input/output and network operations, as it provides a cleaner and more robust way to transfer data from a readable stream to a writable stream while efficiently handling errors.
Use the finished()
function
The finished()
function is a stream method that handles cleanup and completion logic for streams. It is triggered when a stream is no longer readable or writable, or when it has experienced an error due to premature termination, such as an aborted HTTP request.
The function emits an end
or finish
event when a stream has successfully ended. However, the function ignores these events and instead invokes a callback function, which it takes as a second argument to handle unexpected errors and prevent the application from crashing:
const { finished } = require('node:stream');
const fs = require('node:fs');
const readStream = fs.createReadStream('input.txt');
finished(readStream, (err) => {
if (err) {
console.error('Read stream encountered an error:', err);
} else {
console.log('Read stream has finished successfully.');
}
});
JavaScript heap out of memory
error
The JavaScript heap out of memory
error can be caused by a number of factors, but the most prevalent is memory leaks in your application. A memory leak occurs when an application allocates memory but does not release it when it is no longer needed.
This can happen when an application creates objects that are never deleted, or when an application holds onto references to objects that are no longer being used. Over time, memory leaks can cause an application to run out of memory and crash:
app/web.1 FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
This error is rather ambiguous, and newer developers are often unsure how to resolve such an error. Let’s take a look at some of the most common causes of memory leaks in Node.js.
Unclosed connections
When a connection to a database or other resource is not closed properly, the connection will remain open and consume memory:
// A route that simulates a long-running operation without closing the connection
app.get('/unclosed', (req, res) => {
// In a real scenario, this could be a database query, a file read, etc.
setTimeout(() => {
// This response is never sent, leaving the connection open
console.log('Request handled, but the response is never sent.');
}, 5000);
});
const port = 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
In this example, the server does not send a response to the client. This causes the connection to remain open, and over time, these open connections can consume memory and lead to performance issues.
To avoid this, your server should send a response to the client, even if it is a simple response indicating that the request was received. The server should also close the connection after sending the response.
Undisposed objects
When an object is no longer needed, it should be disposed of to free up the memory it is using. Failure to do so can lead to memory leaks:
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'username',
password: 'password',
database: 'mydb',
connectionLimit: 10, // Limit the number of concurrent connections
});
// Simulate a query that doesn't release the connection
function querySim() {
pool.query('SELECT 1 + 1 AS result', (error, results) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Result:', results[0].result);
}
});
}
// Periodically simulate queries (e.g., every 1 second)
setInterval(querySim, 1000);
In this example, the querySim
function will continuously create new database connections using the connection pool without releasing them, leading to a memory leak.
To avoid memory leaks in production, always release resources when done with them. Use pool.end()
for database connections to close the connection pool effectively.
Circular references
When two objects refer to each other, they can create a circular reference. This can prevent either object from being garbage collected, which can lead to memory leaks:
// Repeating the process periodically to simulate a memory leak in a
//production environment
setInterval(() => {
// Create new objects with circular references
const newObj1 = {};
const newObj2 = {};
newObj1.child = newObj2;
newObj2.parent = newObj1;
}, 1000);
The above example simulates a situation in which two objects, obj1
and obj2
, are created repeatedly. Each object has a property that points to the other object, creating a circular reference. This prevents garbage collection.
If this process continues over time, it can lead to memory leaks, as the objects with circular references will not be garbage collected and will continue to consume memory. This can eventually lead to the JavaScript heap out of memory
error.
To prevent memory leaks, break circular references when objects are no longer needed. Set properties to null
, or use other techniques that break the circular references when the objects are no longer in use.
Here’s how we could use null
to break the references in the previous example:
let newObj1, newObj2;
function circularObjects() {
// If objects were previously created, break their references
if (newObj1) {
newObj1.child = null;
}
if (newObj2) {
newObj2.parent = null;
}
// Create new objects with circular references
newObj1 = {};
newObj2 = {};
newObj1.child = newObj2;
newObj2.parent = newObj1;
}
setInterval(circularObjects, 1000);
Large applications
The JavaScript heap out of memory
error can also occur when an application grows larger and uses more objects that take up all the available heap memory allocated by Node.js (1.5GB) by default.
To address this error, you can increase the maximum heap memory using the following commands:
Linux or macOS:
node --max-old-space-size=4096 server.js
Windows:
- * Open the Command shell, or PowerShell as an administrator
- Navigate to your Node.js application directory using
cd
- Run the following command:
- Navigate to your Node.js application directory using
node --max-old-space-size=4096 server.js
This command will start your Node.js application with 4GB of memory limit.
A reliable approach to avoid memory leaks in Node.js is to follow best coding practices and utilize tools like the Node.js Inspector to monitor and manage memory usage effectively.
Environment compatibility errors
Environment compatibility errors can arise when code written for a specific environment, such as a web browser, is ported to a different environment where the expected features or objects are not available or behave differently. Let’s take a look at a few common reference errors.
ReferenceError: document is not defined
The ReferenceError: document is not defined
error is the most common environment compatibility error encountered by developers who are used to working in the web browser environment and are new to Node.js:
ReferenceError: document is not defined
at Object.<anonymous> (C:\Users\Desktop\main.js:9:18)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
This error indicates that you are attempting to access the document
global object, which is typically available in web browsers as part of the DOM. However, Node.js is a runtime environment and does not have a DOM by default. That’s why attempting to access document
in a Node.js environment will lead to the ReferenceError: document is not defined
error:
// This code tries to access the `document` object, which is not available outside a web browser
const title = document.title;
console.log(`Document title: ${title}`); // ReferenceError: document is not defined
If your task requires a DOM, you can use a library that provides a DOM for Node.js, such as Cheerio or Puppeteer. Otherwise, you can fix this error by checking the environment using the typeof
operator to determine the specific environment your code is in before accessing the document
object:
if (typeof document === "object") {
// Your browser-specific code here
document.querySelectorAll("p");
document.getElementById("table");
console.log("Running in a browser environment");
} else {
// Your non-browser code here
console.log("Running in a non-browser environment");
}
This code will check if the window
object is available in the runtime environment. It will execute the code in the if
block if it returns true
; otherwise, it will execute the code in the else
block.
ReferenceError: window is not defined
The ReferenceError:
window
is not defined
error occurs when you try to access the window
object, which is specific to web browsers and not available in a Node.js runtime environment. The window
object is the global object in a web browser. It contains properties and methods that are used to interact with the browser and its window.
In a Node.js runtime environment, the global object is called global
, and it does not contain the window
object. Therefore, if you try to access the window
object in a Node.js runtime environment, you will get the ReferenceError: window is not defined
error.
Similar to the previous error, you can fix the ReferenceError:
window
is not defined
error by using a conditional statement and the typeof
operator to check if your code is in the appropriate environment before it is executed:
function nodeTask() {
console.log('Doing a Node-specific task');
}
function browserTask() {
console.log('Doing a Browser-specific task');
}
// Environment-specific code execution
if (typeof window === 'undefined') {
// Check if 'window' is undefined, which typically indicates a Node.js environment
nodeTask(); // Execute Node-specific code
} else {
// If 'window' is defined, assume it's a browser environment
browserTask(); // Execute Browser-specific code
}
ReferenceError: XMLHttp Request is not defined
The ReferenceError: XMLHttpRequest is not defined
error occurs when you try to use the XMLHttpRequest
constructor in Node.js to make HTTP requests, as shown in the following example:
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
} catch (error) {
console.error('Error:', error);
}
The XMLHttpRequest
method is a browser-specific API. However, unlike the window
and document
, it is not a global object, but a constructor for interacting with servers. Due to the agnostic nature of its operation, it is easy to make the mistake of using the XMLHttpRequest
method in a non-browser environment like Node.js.
To fix the ReferenceError: XMLHttpRequest is not defined
error, use an alternative package such as node-fetch
or axios
, which are recent and provide more developer-friendly ways of interacting with the server. You can install these packages using the following commands:
npm install node-fetch
npm install axios
Network and communication errors
Network and communication errors are a series of errors that typically occur during network communication between your Node.js application and other systems, such as databases, web servers, and other networked resources. These errors can be caused by various factors related to network connectivity, data transmission, and more. Let’s look at some common network and communication errors in Node.js and how to solve them.
Error: read ECONNRESET
The Error: read ECONNRESET
error occurs when the connection to a remote server has unexpectedly closed, usually before the response is received, causing the HTTP request to fail:
Error: read ECONNRESET
at errnoException (server.js:900:11)
at TCP.onread (server.js:555:19)
This can be caused by a number of factors, such as the remote server being overloaded, your Node.js application sending too much data, or the remote server experiencing a power outage.
To resolve this error, do any of the following:
- Reduce the amount of data that your Node.js application is sending
- Try to connect to the remote server at a later time
- Make sure your request configuration is correct. This includes the HTTP method (e.g.,
GET
orPOST
), the host name (e.g., logrocket.com), the path (e.g., /blog), and more - Test your network connection
- Increase connection timeout; the server may be timing out before a connection is established
Error: connect ECONNREFUSED
The Error: connect ECONNREFUSED
error occurs when a connection from your Node.js application to a remote server cannot be established:
Error: connect ECONNREFUSED
at errnoException (net.js:770:11)
at Object.afterConnect [as oncomplete] (net.js:761:19)
This error can be caused by a number of factors, such as the remote server being down, the Node.js application not being able to reach the remote server, the remote server refusing the connection, or sending requests to the wrong endpoint or port.
To resolve this error, check the status of the remote server, verify that your Node.js application is running and is able to reach the remote server, and make sure that the remote server is not refusing the connection.
Error: listen EADDRINUSE: address already in use
Error: listen EADDRINUSE: address already in use
is not so much a communication error, but an error that occurs when the port where you’re trying to start your Node.js application is already in use by another process:
Error: listen EADDRINUSE: address already in use :::3000
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1544:8)
at process.processTicksAndRejections (node:internal/process/task_queues:84:21) {
code: 'EADDRINUSE',
errno: -98,
syscall: 'listen',
address: '::',
port: 3000
}
In the above example, the error message indicates that the port 3000
is already in use by another process. This means that the port is currently occupied and cannot be used for the current request.
To resolve this error, you can either stop the process that is using the specified port or configure your applications to run on a different port. To do the former, execute the following command in your terminal:
npx kill-port 3000
Note that this command will kill the process immediately, without any chance to save any unsaved data.
For macOS, use the following command to view information about the process that is using the port:
lsof -i :3000
You will receive a response that includes a process ID, PID
,number. Copy that number and run the following command with it:
kill -9 <PID number>
This command will terminate the process, after which you can start your Node.js application without any errors.
Error: write EPIPE
The Error: write EPIPE
or “broken pipe” error occurs when your Node.js application tries to write data to a socket or a stream that has been closed or unexpectedly terminated at the other end of the connection. The error typically happens when your application continuously tries to write to the closed connection:
Error: write EPIPE
at errnoException (net.js:770:11)
at Socket._write (net.js:552:19)
at Socket.write (net.js:511:15)
To fix the Error: write EPIPE
error, check whether the stream or socket you're writing to is still active and running before attempting to write data. Use a try-catch
block to catch the error and then take appropriate action, such as closing the stream or logging the error.
Additional common Node.js errors
Here are a couple of additional Node.js errors that don’t fit into one of the categories we’ve already discussed, but that you’re likely to encounter sooner or later if you have not already.
.find is not a function
The .find is not a function
error occurs when you call the find()
method on a data type that is not an array:
const data = {
x: 5,
y: 10,
z: 15,
};
data.find(el => el > 1 );
In this example, the find()
method is being invoked on an object. Since the find()
method only works on arrays, the following error will be thrown:
TypeError: arr.find is not a function
To resolve this error, ensure that you invoke the find()
method on a valid array:
const data = [5, 10, 15];
data.find((el) => el > 1); // Returns 5
Uncaught SyntaxError: Unexpected identifier
The Uncaught SyntaxError: Unexpected identifier
error indicates that there is a syntax error or misspelled keyword in your code that is preventing the interpreter from parsing your code correctly. This is especially common when declaring variables, classes, or methods and mistakenly using an uppercase letter instead of a lowercase letter for the keyword:
Let name = "same"; // L should be lowercase l
Const age = 20; // C should be lowercase c
To resolve the Uncaught SyntaxError: Unexpected identifier
error, navigate to the line where the error occurred and ensure that keywords are spelled correctly. Also look for missing symbols like commas, colons, brackets, or parentheses. By identifying the source of the error, you should be able to easily resolve it.
Conclusion
Errors are an integral part of what makes us good developers. By understanding them and why they occur, we gain a deeper understanding of the programming language or environment we are working on.
In this article, we reviewed some of the most common Node.js errors and discussed how to fix them. I hope this article has helped you to understand Node.js and JavaScript a bit better. Happy hacking!
200’s only Monitor failed and slow network requests in production
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Posted on November 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.