JavaScript
Article
By Azat Mardan

10 Node.js Best Practices: Enlightenment from the Node Gurus

By Azat Mardan

A Street Fighter-like character leaping into the air, gathering energy between his hands - level up your Node skills

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:

  1. Use npm scripts — Stop writing bash scripts when you can organize them better with npm scripts and Node. E.g., npm run build, start and test. npm scripts are like the single source of truth when Node developers look at a new project.
  2. Use env vars — Utilize process.env.NODE_ENV by setting it to development, or production. Some frameworks will use this variable too, so play by the convention.
  3. Understand the event loopsetImmediate() is not immediate while nextTick() is not next. Use setImmediate() or setTimeout() to offload CPU-intensive tasks to the next event loop cycle.
  4. Use functional inheritance — Avoid getting into mindless debates and a brain-draining trap of debugging and understanding prototypal inheritance or classes by just using functional inheritance like some of the most prolific Node contributors do.
  5. Name things appropriately — Give meaningful names which will serve as a documentation. Also, please no uppercase filenames, use a dash if needed. Uppercase in filenames not just look strange but can cause cross-platform issues.
  6. Consider NOT Using JavaScript — ES6/7 is pathetic addition which was born out of 6 years of meetings when we already had a better JavaScript called CoffeeScript. Use it if you want ship code faster and stop wasting time debating var/const/let, semi-colons, class and other arguments.
  7. Provide native code — When using transpilers, commit native JS code (result of the builds) so your projects can run without the builds
  8. Use gzip — Duh! npm i compression -S and sane logging — not too much not to little depending on the environment. npm i morgan -S
  9. Scale up — Start thinking about clustering and having stateless services from day one of your Node development. Use pm2 or strongloop’s cluster control
  10. Cache requests — Get maximum juice out of your Node servers by hiding them behind a static file server such as nginx and/or request level cache like Varnish Cache and CDN caching.

So let’s bisect and take a look at each one of them individually. Shall we?

Use npm Scripts

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:

"scripts": {
  "preinstall": "node prepare.js",
  "postintall": "node clean.js",
  "build": "webpack",
  "postbuild": "node index.js",
  "postversion": "npm publish"
}

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!):

export PATH="./node_modules/.bin:$PATH"

Use Env Vars

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:

NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js

Nodemon also has a config file where you can put your env vars (example):

{
  "env": {
    "NODE_ENV": "production",
    "MONGO_URL": "mongo://localhost:27017/accounts"
  }
}

Understand the Event Loop

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!

The event loop

Use Functional Inheritance

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):

exports = module.exports = createApplication;
// ...
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
}

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.

Name Things Appropriately

This one is obvious. Good names serve as a documentation. Which one would you prefer?

const dexter = require('morgan')
// ...
app.use(dexter('dev')) // When is the next season?

I have no idea what dexter is doing when I only look at app.use(). How about a different more meaningfulname:

const logger = require('morgan')
// ...
app.use(logger('dev')) // Aha!

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):

events.js
fs.js
http.js
https.js
module.js
net.js
os.js
path.js
process.js
punycode.js
querystring.js

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.

Consider NOT Using JavaScript

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.

Know Express Middleware

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.

Scale up

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:

npm i -g pm2

Then you can start four instances of the same server:

pm2 start server.js -i 4

For Docker, pm2 version 2+ has pm2-docker. So your Dockerfile can look like this:

# ...

RUN npm install pm2 -g

CMD ["pm2-docker", "app.js"]

The official Alpine Linux pm2 image is in the Docker Hub.

Cache Requests

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:

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf

I like to use Docker compose to make multiple containers (nginx, Node, Redis, MongoDB) work with each other. For example:

web:
  build: ./app
  volumes:
    - "./app:/src/app"
  ports:
    - "3030:3000"
  links:
    - "db:redis"
  command: pm2-docker app/server.js

nginx:
  restart: always
  build: ./nginx/
  ports:
    - "80:80"
  volumes:
    - /www/public
  volumes_from:
    - web
  links:
    - web:web

db:
  image: redis

Summary

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:

  • You invalidated your article when you chose to use the extremely subjective suggestion that CoffeeScript is superior to vanilla Javascript, as a best practice. Poor choice, that.

    • Azat Mardan

      The bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. So if you read the actual text, you would agree with my point.

      • incorrect to assume I didn’t read the article. you list a slew of Javascript-like alternatives, and then include CoffeeScript – which is Ruby-like, that transpiles to Javascript. that might suggest that you don’t even fully understand the differences and instead included that for click-bait. and honestly now; pasting the same response snippet to all who comment and disagree with your viewpoint is amateurish at best. you’ve convinced me I don’t need to follow your musings, so thanks for that I guess.

        • Azat Mardan

          I try to reply to each comment. Even comments like yours. :)
          I bet you never build anything with CoffeeScript production-like or for a large team or which is used by millions of users. CS is still better than ES6. Most people who hate on CS never used it for production.

          • “I bet you never build anything with CoffeeScript production-like or for a large team or which is used by millions of users.”

            Because you’ve scoured my body of work and are privy to what I’ve done on a daily basis for the last 20 years? You sure do know how to form conjecture based on flimsy evidence. And you’d be wholly incorrect. My opinion was formed as a direct result of experience in nearly that exact scenario. Next time try asking questions of someone rather than making ridiculous assumptions.

          • Azat Mardan

            So now you are a CS fan out of a sudden? What a fast conversion! Haha. Then what’s all the fuzz about?

            Also, did you even read my first comment or just upset with your life in general and want to pick up a flame war? Go do something more useful! And I’ll repost it again for you (last time): The bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. So if you read the actual text, you would agree with my point. What is there right now was left out from drafts and/or edits. :)

          • I’m not convinced your grasp on the English language is all that solid. Did you mean “all of a sudden”? I also believe the expression is “all the fuss.” Anyhow. My prior reply clearly stated that my opinion – which is that CoffeeScript is definitely NOT superior to vanilla Javascript, or any Javascript-like language – is based upon extensive use in a high-volume, high-traffic production project. That runs counter to your conjecture that I did not have that experience, which you based on seemingly no evidence.

            I also don’t think you can accuse someone of starting a flame war when the accused is simply stating facts. Especially when you, the author, have taken to Twitter to troll me there. That was a classless move, and I’m disappointed that someone of your stature at Capital One would behave with such a lack of dignity. You may reply to this to your heart’s content, but alas this will be my last reply.

          • Azat Mardan

            You wrote ” that CoffeeScript is definitely NOT superior to vanilla Javascript, or any Javascript-like language”. Maybe you can educate yourself on CS more. It’s clearly superior to pathetic ES6.
            There’s no point for me to argue with a person who does not know what he is talking about (CS vs vanilla JS). You can have your opinion but it does not make you right.

            “Twitter to troll me there. That was a classless move” so what happened of not following me? Why are you still writing here? Oh, that’s right. Because you are the troll who starts the flame wars. :)

          • intosh

            Wow. By pursuing this thread as you did, the only thing you did was tarnishing your own credibility.

            Really, the totally subjective and heavy-handed criticism of ES6 being “pathetic” has no place in such article – it screams clickbait/trolling tactic.

            It was a fine article and didn’t need the clumsy shots at ES6, which backfired.

          • remybach

            I’m going to chime in and say that I *have* used coffeescript, in production, on a site that got used by millions of users and I still, definitively hate it.

  • adnan

    “ES6/7 is pathetic addition which was born out of 6 years of meetings when we already had a better JavaScript”? Are you serious, I wish i could downvote this post for this statement, may be you haven’t checked out es6 properly yet, or never used any other language.

    • Azat Mardan

      The bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. So if you read the actual text, you would agree

    • Roman Storm

      says who?

    • maer

      totally agree with you

  • Francisco Pereira

    CoffeScript? Are you kidding me?!

    • Azat Mardan

      The bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. So if you read the actual text, you would agree with my point of not using JS.

      • James Hibbard

        Bullet point has been amended :)

    • markbrown4

      This always makes me sad. If you take the time to look at what CoffeeScript is, you’ll see it’s a simple language that let’s you get more done with less code. It lost a lost of steam when es6 was introduced but there’s zero reason for treating it like a joke, these “ES6” features were first in CoffeeScript and were usable for years.

      – Bound functions =>
      – Default params
      – Destructuring assignment
      – Classes
      – Rest params
      – Template strings

    • Darryl Young

      CoffeeScript was great and I used it at my previous company for a very long time but seeing as most of the features I liked it for are now part of ES6 I’ve left it behind. This article is the first I’ve come across in a while to suggest writing CoffeeScript over vanilla (ES6) JavaScript. I wouldn’t recommend this to people who are just getting into this kind of thing as CoffeeScript development seems to be as good as dead.

  • nrkn

    Overall a good article, shame about the ES6/7 slur. Also I’d argue functional composition over functional inheritance.

    • Azat Mardan

      No shame here. The bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. So if you read the actual text, you would agree with my point.

      • nrkn

        I read the entire article, hence why I said that overall it’s very good. I particularly liked your Express cheat sheet.

        Yes, the section text is more reasonable, and yes, I do for the most part agree with the section text, however that doesn’t make the synopsis text in the introduction any less inflammatory, and I think it’s a shame because for an article purporting to be about best practices (which for the most part it does well), that’s an extremely subjective point of view. It sticks out like a sore thumb from the rest of the content.

        • Azat Mardan

          The synopsis text / bullet point should be “Consider NOT Using JavaScript”, i.e., the same as the heading. What’s there currently is left over from a draft or an edit. Maybe SitePoint will fix the bullet point. Thanks for the kind words and reading the entire article!

          • nrkn

            It’s not the bullet point text, but the synopsis that follows that I’m taking issue with:

            “ES6/7 is pathetic addition which was born out of 6 years of meetings when we already had a better JavaScript called CoffeeScript. Use it if you want ship code faster and stop wasting time debating var/const/let, semi-colons, class and other arguments.”

          • Azat Mardan

            That’s true. ES6 is pathetic and disappointment. For example, there’s no class attributes, no skinny arrow function, and ES6 modules is not implemented properly even by Babel.

  • JoaoReynolds

    This author should be banned from writing on SitePoint. Just read his comments here.

    • Azat Mardan

      Another CoffeeScript hater? Actually SitePoint asked me to stick around and comment. :)

      • JoaoReynolds

        This isn’t about coffeescript, your comments are arrogant, demeaning, and not at all constructive. Your inability to accept others’ opinion and automatically assume they never tried your favorite thing speaks loudly to your own lack of confidence, otherwise, you might engage in uplifting and informative arguments here defending your claims, which I have yet to see here – it would lend to greater credibility to your article. I thought it was a good article initially, but if you’re going to participate in the comments, you have a chance to continue the discussion, which in this case, has spiraled downward. My comment wasn’t to you – but sitepoint – in an attempt to maintain a great, UPLIFTING developer community.

  • Victor Huerta

    Consider NOT Using JavaScript, lol, i totally disagree :)

  • Chanlito

    All his articles says shit about es6, what the heck?

  • thanks for pointing `concurrently` – great module.

  • StevevetS

    I believe the last point you made in bullet (1) regarding explicitly using the path to locally-installed dependencies (or manually updating PATH) is not necessary. According to the npm-run-script docs, npm will automatically add node_modules/.bin to the PATH provided to scripts. So using only the name of a locally-installed dependency in your own script should Just Work.

  • Chanlito

    Nice statement

  • koshelew@live.com

    Using CoffeeScript back in 2013, I remember there were parts of it with indeterministic compilation, i.e. you couldn’t figure out what JS it will result in unless you actually checked, so poorly was the language designed. Very hard to read too, and, most importantly, not slowing any actual problem JS had back then. We threw it away back then and later went for Typescript with good results.

    P.S. CoffeeScript is a mess to maintain. It tries to save a few keystrokes at the cost of taxing hours from people who later have to read it.

  • markbrown4

    Good stuff Azat.

    I disagree with the premise of “Consider NOT Using JavaScript” though.
    Not because of the es6 bashing(es6 is a solid progression) or the love or CoffeeScript(which is awesome) but because you’re encouraging people to bet against js and not be as tightly engaged in the js community.

    It’s best to ride the JS train as it evolves.

  • Bluo Bluk

    This author is embarrassing. He is a classic bigot. He may have some good knowledge to share but he wont get anywhere with his attitude.

  • remybach

    Ok… with that ridiculous comment about ES6, I’m starting to think this guy is just a massive troll himself. Following his GitHub link, you can find the following gem of a repo of his: https://github.com/azat-co/es6

  • Ram

    “So if you read the actual text, you would agree with my point.”
    – Someone unable to understand different points of view.

    Reading the actual text is what makes a lot of people disagree with your point.

  • Demian Scott

    if you stop existing i will stop using es6/7

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.