JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.
In this article, we’ll look at some mistakes that are easy to make in Node apps, including joining paths the wrong way, referencing process.env
and more.
No String Concatenation with __dirname
and __filename
In Node.js, __dirname
and __filename
are strings that have the current directory or current script file name respectively.
Many people try to concatenate them like the following to get the full path of a file:
const pathname = __dirname + '/bar.js'
However, the code above is probably since Node apps can be run on different platforms, which have different styles for paths.
For instance, Windows uses backslash a path segment separation, while Linux and macOS use forward slash as separators.
Therefore, concatenation isn’t a reliable way to join paths together. It’s very easy to create an invalid path by concatenation segments together.
Instead, we should use the path.join
method to combine segments together as follows:
const path = require('path');
const pathname = path.join(__dirname, 'bar.js');
Then we won’t have to worry about the path structure of the path causing issues when running in different operating systems.
The path.resolve
will get the fully qualified path with the full path separators. For instance, we can write:
const path = require('path');
const pathname = path.resolve(__dirname, 'bar.js');
and we get the full path generated by Node.js.
No Reference to process.env
The process.env
object is used to store deployment or configuration settings. Putting it everywhere is a maintenance issue because we’re referencing this global dependency everywhere.
It can, therefore, lead to merge conflicts and deployment issues in multiserver setups.
The best practice for storing configuration settings is to store them all in one file and then reference that file instead.
For instance, instead of writing:
const dbPath = process.env.DB_PATH;
We should write:
const config = require("./config");
const dbPath = config.DB_PATH;
No process.exit()
process.exit()
is use to immediately stop a Node process and exit the program.
This isn’t good because there may be lots of things running in the background that are needed by the program.
It can stop a progran complete when an error occurs.
For instance, if we have something like the following:
if (error) {
process.exit(1);
}
Then the program exists immediatelt and there’s no chance for our program to respond to the error is error
is truthy.
The better way to raise errors when they occur is to throw an exception. For instance, we can do that as follows:
if (error) {
throw new Error("error");
}
This way, our program can catch the error and then respond to it. We’ll also see the full stack from the Error
object from what’s been called before the error is thrown.
Don’t Use Synchronous Methods in Big Apps
Since Node is single-threade, running synchronous methods means that they have to finish before anything else can run. If it’s a long running process, then this is definitely a problem.
We don’t want one method to hold up our entire app. For instance, if we’re running a web server app, then we definitely shouldn’t have synchronous methods holding up the web server with one request.
However, for scripts that run line-by-line and don’t have multiple parts, synchronous methods are useful for simplifying the code and doing what we want sequentially without much thinking.
For instance, in a big production app, we shouldn’t be running methods like fs.readFileSync
as follows:
const fs = require('fs');
const file = fs.readFileSync('foo.txt').toString();
We can instead use methods like readFile
rom fs.promise
to read a file:
const fs = require('fs').promises;
(async () => {
const file = await fs.readFile('foo.txt')
const content = file.toString();
console.log(content);
})()
This way, we run asynchronous code in a sequence as we do with synchronous code without holding up the rest of our program.
Conclusion
If we want to create strings for file paths, we should use the path.join
or path.resolve
methods to create full paths.
This way, we won’t have to worry the path structure difference in different OSs.
The best way to store config settings is to put them all in a configuration and then reference everything from that file.
process.exit
shouldn’t be used to exit the program when we encounter errors because it end the program abruptly, giving our program no chance of responding.
Finally, other than small scripts, we shouldn’t be using synchronous methods in our Node apps.