Node.js has been widely adopted over the last few years, even by such well-known giants as Netflix, IBM, Walmart, Intel and NASA. Benefits like a support plan and a vast collection of open-source libraries account for much of the growth in Node adoption in the enterprise, replacing typical enterprise solutions like Java and .Net. Node.js went from a common technology in the startup world to a mainstream development approach used by companies of all sizes.
Node.js is easy to learn, especially if you already know Javascript. And maintaining it can be less tiresome than some other languages. However, when you are a developer with a strong background in languages like Java, there are some pitfalls that you can face when using Node.js for projects more complex than a trivial "hello world", and if this happens you might run into trouble keeping a big project organized. To avoid this and save you some time (and money) I am going to share with you a list of features that will help you use the full power of NodeJS.
Lack of Testing and Linting
Node.js is an interpreted language, which is highly useful. The following checklist may be useful to improve your project:
The require command is always "require once", after you import a file for the first time. Node uses a cache and will always return the same object. So, there's no need to create huge files with big functions:
The "globals" variables must be avoided. Consider removing from your code the keyword global.
global.myNumber = 5; //Global variable initialized to value 5.
For example, it is acceptable to use the proccess.env.NODE_ENV on the configuration file. However, the other modules should avoid accessing that. You should consider writing your business rules.
When your application is starting, you can use methods like `fs.readDirSync`, `fs.readFileSync`, `fs.accessSync`, `fs.changeModSync`, `fs.chownSync`, `fs.closeSync` and `fs.existsSync`. However, when the application is ready, you can't call synchronous methods. When you execute a synchronous function, you will block the event loop.
Try to avoid creating functions with a long list of arguments. Consider the following function:
function createProduct(id, name, description, sku, colors, sizes, price, imageURL, thumbnailURL){ // code... // code... }
When you need to receive more than three or four parameters, you should consider using only a parameter called `options` with all values stored as a map.
Many libraries write functions using optional arguments. However, when you write functions for your application with some business rules, it's a common necessity to evolve and make changes. So, you should avoid creating functions with optional arguments because it is hard to evolve with more arguments.
Ignoring Error Statement
If you ignore an Error Statement, it can lead to unexpected behavior: namely, always returning when calling callbacks. The following code has one problem because, if an error occurs, then the execution will not stop, but will continue.
/* missing a RETURN statement */ fs.readFile(`${filePath}`, (err, data) => { if (err) { console.log(`Error reading file: ${filePath}`) } //... //code //... }Be careful when “parsing”
The Node.js provides a rich API to handle I/O asynchronous operations. You should be careful when parsing huge JSON data, because the method `JSON.Parse` is synchronous and can block the event loop too.
In JavaScript, over-usage of callback functions often leads to complex chains of functions. It is hard to understand the flow of execution. Many times, code ends up looking like a lot of nested callbacks:
fs.readdir(dir, (err, files) => { if (err) { return console.log(`Error finding files: ${err}`) } else { files.forEach( (fileName, fileIndex) => { let filePath = path.resolve(dir, fileName); fs.readFile(`${filePath}`, (err, data) => { if (err) { return console.log(`Error reading file: ${filePath}`) } let params = { Key: fileName, Body: data }; s3bucket.upload(params, (err, data) => { if (err) { return console.log(`There was an error uploading to AWS S3 the file: ${filePath} at ${bucket}/${fileName} - ${err}`) } fs.unlink(filePath, (err) => { if (err) { return console.error(`There was an error deleting the file ${filePath} - ${err}`) } }) }) }) }) } })
Before you go further on advanced topics, you can resolve that by keeping your code shallow and still using the JavaScript callbacks and consider take a look at library called async. Or if you are starting a new project, consider using Node.js 7.6+, a new version with default support to async/await. If we rewrite our code using that feature, the code will look like "synchronous", increasing its readability. The same code above is rewritten here using async/await:
async function start(){ const files = await readDirAsync(dir) files.forEach( async (fileName, fileIndex) => { const filePath = path.resolve(dir, fileName) const data = await readFileAsync(`${filePath}`) const params = { Key: fileName, Body: data }; await s3UploadAsync(params) await unlinkAsync(filePath) }) }
Keep in mind that you should wrap await in try/catch to capture and handle the errors in awaited promises. Besides that, you should also consider using the generator or promises.
In summary, these are some of the most common "anti-patterns" to avoid, or at least be aware of when working in Node. My hope is that this helps someone who is starting on Node.js. If you are starting with software-as-a-service apps, consider reading The Twelve FactorApp.
I also highly recommend checking out DevMedia’s DevCast: Introducing Node.js. In this video, you’ll learn about the purpose of Node.js, discover the advantages of its asynchronous, event-oriented programming model, and understand how it differs from object-oriented/multi-paradigm languages such as PHP, C #, Python, and others. Let us know what you think in the comments below!