Home >Web Front-end >JS Tutorial >10 Node.js Best Practices: Enlightenment from the Node Gurus
10 Node.js Best Practices: Enlightenment from the Node Gurus is by guest author Azat Mardan. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the Web community.
In my previous article 10 Tips to Become a Better Node Developer in 2017, I introduced 10 Node.js tips, tricks and techniques you could apply to your code today. This post continues in that vein with a further 10 best practices to help you take your Node skills to the next level. This is what we’re going to cover:
So let’s bisect and take a look at each one of them individually. Shall we?
It’s almost a standard now to create npm scripts for builds, tests, and most importantly to start the app. This is the first place Node developers look at when they encounter a new Node project. Some people (1, 2, 3, 4) have even ditched Grunt, Gulp and the likes for the more low-level but more dependable npm script. I can totally understand their argument. Considering that npm scripts have pre and post hooks, you can get to a very sophisticated level of automation:
<span>"scripts": { </span> <span>"preinstall": "node prepare.js", </span> <span>"postintall": "node clean.js", </span> <span>"build": "webpack", </span> <span>"postbuild": "node index.js", </span> <span>"postversion": "npm publish" </span><span>} </span>
Often times when developing for the front-end, you want to run two or more watch processes to re-build your code. For example, one for webpack and another for nodemon. You can do this with && since the first command won’t release the prompt. However, there’s a handy module called concurrently which can spawn multiple processes and run them at the same time.
Also, install dev command line tools such as webpack, nodemon, gulp, Mocha, etc. locally to avoid conflicts. You can point to ./node_modules/.bin/mocha for example or add this line to your bash/zsh profile (PATH!):
<span>export <span>PATH</span>="./node_modules/.bin:<span>$PATH"</span> </span>
Utilize environment variables even for the early stages of a project to ensure there’s no leakage of sensitive info, and just to build the code properly from the beginning. Moreover, some libraries and frameworks (I know Express does it for sure) will pull in info like NODE_ENV to modify their behavior. Set it to production. Set your MONGO_URI and API_KEY values as well. You can create a shell file (e.g. start.sh) and add it to .gitignore:
<span>NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js </span>
Nodemon also has a config file where you can put your env vars (example):
<span>{ </span> <span>"env": { </span> <span>"NODE_ENV": "production", </span> <span>"MONGO_URL": "mongo://localhost:27017/accounts" </span> <span>} </span><span>} </span>
The mighty and clever event loop is what makes Node so fast and brilliant by utilizing all the time which would have been wasted waiting for input and output tasks to complete. Thus, Node is great at optimizing I/O-bound systems.
If you need to perform something CPU-intensive (e.g., computation, hashing of passwords, or compressing), then in addition to spawning new processes for those CPU-tasks, you might want to explore the deferring of the task with setImmediate() or setTimeout() — the code in their callbacks will continue on the next event loop cycle. nextTick() works on the same cycle contrary to the name. Argh!
Here’s a diagram from Bert Belder who worked on the event loop. He clearly knows how the event loop works!
JavaScript support prototypal inheritance which is when objects inherit from other objects. The class operator was also added to the language with ES6. However, it’s overtly complex compared to functional inheritance. Most Node gurus prefer the simplicity of the latter. It’s implemented by a simple function factory pattern, and does NOT require the use of prototype, new or this. There are no implicit effects when you update the prototype (causing all the instances to change as well) since in functional inheritance each object uses its own copy of methods.
Consider code from TJ Holowaychuk, the prolific genius behind Express, Mocha, Connect, Superagent and dozens of other Node modules. Express uses functional inheritance (full source code):
<span>"scripts": { </span> <span>"preinstall": "node prepare.js", </span> <span>"postintall": "node clean.js", </span> <span>"build": "webpack", </span> <span>"postbuild": "node index.js", </span> <span>"postversion": "npm publish" </span><span>} </span>
To be objective, core Node modules use prototypal inheritance a lot. If you follow that pattern, make sure you know how it works. You can read more about JavaScript inheritance patterns here.
This one is obvious. Good names serve as a documentation. Which one would you prefer?
<span>export <span>PATH</span>="./node_modules/.bin:<span>$PATH"</span> </span>
I have no idea what dexter is doing when I only look at app.use(). How about a different more meaningfulname:
<span>NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js </span>
In the same fashion, file names must correctly reflect what is the purpose of the code inside. If you take a look at the lib folder of Node (GitHub link) which has all the core modules bundled with the platform, then you will see clear naming of the files/modules (even if you are not very familiar with all the core modules):
<span>{ </span> <span>"env": { </span> <span>"NODE_ENV": "production", </span> <span>"MONGO_URL": "mongo://localhost:27017/accounts" </span> <span>} </span><span>} </span>
The internal modules are marked with an underscore (_debugger.js, _http_agent.js, _http_client.js) just like methods and variable in the code. This helps to warn developers that this is an internal interface and if you are using it, you are on your own — don’t complain if it gets refactored or even removed.
Huh? Did you just read it correctly? But what the heck? Yes. That’s correct. Even with ES6 and the two features added by ES2016/ES7, JavaScript still has its quirks. There are other options besides JavaScript which you or your team can benefit from with very little setup. Depending on the expertise level and the nature of the app, you might be better off with TypeScript or Flow which provide strong typing. On the other end of the spectrum, there’s Elm or ClojureScript which are purely functional. CoffeeScript is another great and battle-tested option. You might take a look at Dart 2.0 as well.
When all you need is just a few macros (macros allow you to build exactly the language you want), not an entire new language, then consider Sweet.js which will do exactly that — allow you to write code which generates code.
If you go the non-JavaScript route, please still include your compiled code because some developers might not understand your language well enough to build it properly. For example, VS Code is one of the largest TypeScript projects, maybe after Angular 2, and Code uses TypeScript to patch Node’s core module with types. In the vscode/src/vs/base/node/ of VS Code repo (link), you can see familiar module names like crypto, process, etc. but with the ts extension. There are other ts files in the repo. However, they also included vscode/build with native JavaScript code.
Express is a great and very mature framework. It’s brilliance comes from allowing myriads of other modules to configure its behavior. Thus, you need to know the most used middleware and you need to know how to use it. So why not grab my Express cheat sheet. I have the main middleware modules listed there. For example, npm i compression -S will give reduce the download speed by deflating the responses. logger('tiny') or logger('common') will provide less (dev) or more (prod) logs respectively.
Node is great at async due to its non-blocking I/O and it keeps this async way of coding simple because there’s just one thread. This is an opportunity to start scaling early on, maybe even with the first lines of code. There’s the core cluster module which will allow you to scale vertically without too many problems. However, an even better way would be is to use a tool such as pm2 or StrongLoop’s cluster control.
For example, this is how you can get started with pm2:
<span>"scripts": { </span> <span>"preinstall": "node prepare.js", </span> <span>"postintall": "node clean.js", </span> <span>"build": "webpack", </span> <span>"postbuild": "node index.js", </span> <span>"postversion": "npm publish" </span><span>} </span>
Then you can start four instances of the same server:
<span>export <span>PATH</span>="./node_modules/.bin:<span>$PATH"</span> </span>
For Docker, pm2 version 2 has pm2-docker. So your Dockerfile can look like this:
<span>NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js </span>
The official Alpine Linux pm2 image is in the Docker Hub.
This is a DevOps best practice which will allow you to get more juice out of your Node instances (you get more than one with pm2 or the like, see above). The way to go is to let Node servers do app stuff like making requests, processing data and executing business logic and offload the traffic to static files to another web server such as Apache httpd or Nginx. Again, you probably should use Docker for the set up:
<span>{ </span> <span>"env": { </span> <span>"NODE_ENV": "production", </span> <span>"MONGO_URL": "mongo://localhost:27017/accounts" </span> <span>} </span><span>} </span>
I like to use Docker compose to make multiple containers (nginx, Node, Redis, MongoDB) work with each other. For example:
exports <span>= module.exports = createApplication; </span><span>// ... </span><span>function createApplication() { </span> <span>var app = function(req<span>, res, next</span>) { </span> app<span>.handle(req, res, next); </span> <span>}; </span> <span>mixin(app, EventEmitter.prototype, false); </span> <span>mixin(app, proto, false); </span> app<span>.request = { __proto__: req, app: app }; </span> app<span>.response = { __proto__: res, app: app }; </span> app<span>.init(); </span> <span>return app; </span><span>} </span>
In this day and age of open-source software, there are no excuses not to learn from the trusted and tested code which is out in the open. You don’t need to be in the inner circle to get in. Learning never stops and I’m sure soon we will have different best practices based on the failures and successes which we will experience. They are guaranteed.
Finally, I wanted to write about how software is eating the world and how JavaScript is eating the software… there are great things like yearly standard releases, lots and lots of npm modules, tools and conferences… but instead I’ll finish with a word of caution.
I see how more and more people chase the next new framework or language. It’s the shiny object syndrome. They learn a new library every week and a new framework every month. They compulsively check Twitter, Reddit, Hacker News and JS Weekly. They use the overwhelming level of activity in the JavaScript world to procrastinate. They have empty public GitHub histories.
Learning new things is good but don’t confuse it for actually building stuff. What matters and what pays your salary is actually building things. Stop over engineering. You’re not building the next Facebook. Promises vs. generators vs. async await is a moot for me, because by the time someone replied to a thread in a discussion, I already wrote my callback (and used CoffeeScript to do it 2x faster than in plain ES5/6/7!).
The final best practice is to use best practices and the best of the best is to master fundamentals. Read source code, try new things in code and most importantly write tons of code yourself. Now, at this point, stop reading and go ship code that matters!
And just in case this post is not enough here is some more reading on best Node practices:
Node.js development involves several best practices that can significantly enhance the efficiency and scalability of your applications. These include using asynchronous programming, which allows for non-blocking operations and improves performance. It’s also crucial to handle errors properly to prevent application crashes. Other best practices include using a linter to enforce code quality, using environment variables for configuration, and writing small modules to keep your codebase manageable and understandable.
There are several ways to improve the performance of your Node.js application. One of the most effective methods is to use the Cluster module, which allows you to create child processes that all share server ports. This can significantly improve the performance of your application by allowing it to handle more requests simultaneously. Additionally, you can use tools like PM2 to manage and monitor your Node.js applications, which can help you identify and address performance issues.
Some common mistakes to avoid when developing with Node.js include blocking the event loop, not handling errors properly, and not using tools like linters to enforce code quality. Blocking the event loop can lead to performance issues, as it prevents other operations from being executed. Not handling errors properly can lead to application crashes, while not using linters can lead to inconsistent code quality and potential bugs.
Ensuring the security of your Node.js application involves several best practices. These include using HTTPS for secure communication, validating and sanitizing user input to prevent injection attacks, and using security headers to protect against common web vulnerabilities. It’s also important to keep your dependencies up to date, as outdated dependencies can contain known security vulnerabilities.
Testing is a crucial part of Node.js development, and there are several best practices to follow. These include writing unit tests to test individual components of your application, integration tests to test how these components interact, and end-to-end tests to test your application as a whole. It’s also important to use a continuous integration (CI) system to automatically run your tests whenever changes are made to your codebase.
Managing dependencies in Node.js is typically done using npm, the default package manager for Node.js. It’s important to specify exact versions of your dependencies in your package.json file to ensure that your application works as expected. You should also regularly update your dependencies to benefit from bug fixes and security patches.
Error handling is a crucial part of Node.js development. Best practices include using try/catch blocks to catch synchronous errors, using error-first callbacks to handle asynchronous errors, and using a centralized error handling mechanism to handle all errors in one place. It’s also important to log errors for debugging purposes and to respond to the client with appropriate error messages.
Ensuring code quality in Node.js involves several best practices. These include using a linter to enforce code quality, following a consistent coding style, and writing tests to catch bugs early. It’s also important to use version control (like Git) to track changes to your codebase and to perform code reviews to catch potential issues.
Scaling a Node.js application can be achieved in several ways. One common method is to use the Cluster module to create child processes that share server ports, allowing your application to handle more requests simultaneously. You can also use load balancing to distribute incoming network traffic across multiple servers, and horizontal scaling (adding more machines) or vertical scaling (adding more resources to a single machine) depending on your needs.
Deploying Node.js applications involves several best practices. These include using environment variables for configuration, using a process manager like PM2 to manage your application, and using a continuous integration (CI) system to automatically deploy your application when changes are made to your codebase. It’s also important to monitor your application to identify and address performance issues.
The above is the detailed content of 10 Node.js Best Practices: Enlightenment from the Node Gurus. For more information, please follow other related articles on the PHP Chinese website!