|||

Video Transcript

X

Habits of a Happy Node Hacker 2017

It’s been a little over a year since our last Happy Node Hackers post, and even in such a short time much has changed and some powerful new tools have been released. The Node.js ecosystem continues to mature and new best practices have emerged.

Here are 8 habits for happy Node hackers updated for 2017. They're specifically for app developers, rather than module authors, since those groups have different goals and constraints:

1. Lock Down Your Dependency Tree

In modern Node applications, your code is often only the tip of an iceberg. Even a small application could have thousands of lines of JavaScript hidden in node_modules. If your application specifies exact dependencies in package.json, the libraries you depend on probably don’t. Over time, you'll get slightly different code for each install, leading to unpredictability and potentially introducing bugs.

In the past year Facebook surprised the Node world when it announced Yarn, a new package manager that let you use npm's vast registry of nearly half a million modules and featured a lockfile that saves the exact version of every module in your dependency tree. This means that you can be confident that the exact same code will be downloaded every time you deploy your application.

Not to be outdone, npm released a new version with a lockfile of its own. Oh, and it's a lot faster now too. This means that whichever modern package manager you choose, you'll see a big improvement in install times and fewer errors in production.

To get started with Yarn, install it and run yarn in your application’s directory. This will install your dependencies and generate a yarn.lock file which tells Heroku to use Yarn when building your application.

To use npm 5, update locally by running npm install -g npm@latest and reinstall your application's dependencies by running rm -rf node_modules && npm install. The generated package-lock.json will let Heroku know to use npm 5 to install your modules.

2. Hook Things Up

Lifecycle scripts make great hooks for automation. If you need to run something before building your app, you can use the preinstall script. Need to build assets with grunt, gulp, browserify, or webpack? Do it in the postinstall script.

In package.json:

"scripts": {
  "postinstall": "grunt build",
  "start": "node app.js"
}

You can also use environment variables to control these scripts:

"postinstall": "if $BUILD_ASSETS; then npm run build-assets; fi",
"build-assets": "grunt build"

If your scripts start getting out of control, move them to files:

"postinstall": "scripts/postinstall.sh"

3. Modernize Your JavaScript

With the release of Node 8, the days of maintaining a complicated build system to write our application in ES2015, also known as ES6, are mostly behind us. Node is now 99% feature complete with the ES2015 spec, which means you can use new features such as template literals or destructuring assignment with no ceremony or build process!

const combinations = [
  { number: "8.0.0", platform: "linux-x64" },
  { number: "8.0.0", platform: "darwin-x64" },
  { number: "7.9.0", platform: "linux-x64" },
  { number: "7.9.0", platform: "darwin-x64" }
];

for (let { number, platform } of combinations) {
  console.log(`node-v${number}-${platform}.tar.gz`);
}

There are a ton of additions, and overall they work together to significantly increase the legibility of JavaScript and make your code more expressive.

4. Keep Your Promises

Beyond ES2015, Node 8 supports the long-awaited async and await keywords without opting in to experimental features. This feature builds on top of Promises allowing you to write asynchronous code that looks like synchronous code and has the same error handling semantics, making it easier to write, easier to understand, and safer.

You can re-write nested callback code that looks like this:

function getPhotos(fn) {
  getUsers((err, users) => {
    if (err) return fn(err);
    getAlbums(users, (err, albums) => {
      if (err) return fn(err);
      getPhotosForAlbums(albums, (err, photos) => {
        if (err) return fn(err);
        fn(null, photos);
      });
    });
  });
}

into code that reads top-down instead of inside-out:

async function getPhotos() {
  const users = await getUsers();
  const albums = await getAlbums(users);
  return getPhotosForAlbums(albums);
}

You can call await on any call that returns a Promise. If you have functions that still expect callbacks, Node 8 ships with util.promisify which can automatically turn a function written in the callback style into a function that can be used with await.

5. Automate Your Code Formatting with Prettier

We’ve all collectively spent too much time formatting code, adding a space here, aligning a comment there, and we all do it slightly different than our teammate two desks down. This leads to endless debates about where the semicolon goes or whether we should use semicolons at all. Prettier is an open source tool that promises to finally eliminate those pointless arguments for good. You can write your code in any style you like, and with one command it’s all formatted consistently.

prettier

That may sound like a small thing but freeing yourself from arranging whitespace quickly feels liberating. Prettier was only released a few months ago, but it's already been adopted by Babel, React, Khan Academy, Bloomberg, and more!

If you hate writing semicolons, let Prettier add them for you, or your whole team can banish them forever with the --no-semi option. Prettier supports ES2015 and Flow syntax, and the recent 1.4.0 release added support for CSS and TypeScript as well.

There are integrations with all major text editors, but we recommend setting it up as a pre-commit hook or with a lifecycle script in package.json.

"scripts": {
  "prettify": "prettier --write 'src/**/*.js'"
}

6. Test Continuously

Pushing out a new feature and finding out that you've broken the production application is a terrible feeling. You can avoid this mistake if you’re diligent about writing tests for the code you write, but it can take a lot of time to write a good test suite. Besides, that feature needs to be shipped yesterday, and this is only a first version. Why write tests that will only have to be re-written next week?

Writing unit tests in a framework like Mocha or Jest is one of the best ways of making sure that your JavaScript code is robust and well-designed. However there is a lot of code that may not justify the time investment of an extensive test suite. The testing library Jest has a feature called Snapshot Testing that can help you get insight and visibility into code that would otherwise go untested. Instead of deciding ahead of time what the expected output of a function call should be and writing a test around it, Jest will save the actual output into a local file on the first run, and then compare it to the response on the next run and alert you if it's changed.

jest-snapshot-testing

While this won't tell you if your code is working exactly as you'd planned when you wrote it, this does allow you to observe what changes you're actually introducing into your application as you move quickly and develop new features. When the output changes you can quickly update the snapshots with a command, and they will be checked into your git history along with your code.

it("test /endpoint", async () => {
  const res = await request(`http://0.0.0.0:5000/endpoint`);
  const body = await res.json();
  const { status, headers } = res;
  expect({ status, body, headers }).toMatchSnapshot();
});

Example Repo

Once you've tested your code, setting up a good CI workflow is one way of making sure that it stays tested. To that end, we launched Heroku CI. It’s built into the Heroku continuous delivery workflow, and you'll never wait for a queue. Check it out!

Don't need the fancy features and just want a super simple test runner? Check out tape for your minimal testing needs.

7. Wear Your Helmet

For web application security, a lot of the important yet easy configuration to lock down a given app can be done by returning the right HTTP headers.

You won't get most of these headers with a default Express application, so if you want to put an application in production with Express, you can go pretty far by using Helmet. Helmet is an Express middleware module for securing your app mainly via HTTP headers.

Helmet helps you prevent cross-site scripting attacks, protect against click-jacking, and more! It takes just a few lines to add basic security to an existing express application:

const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet());

Read more about Helmet and other Express security best practices

8. HTTPS all the things

By using private connections by default, we make it the norm, and everyone is safer. As web engineers, there is no reason we shouldn’t default all traffic in our applications to using HTTPS.

In an express application, there are several things you need to do to make sure you're serving your site over https. First, make sure the Strict-Transport-Security header (often abbreviated as HSTS) is set on the response. This instructs the browser to always send requests over https. If you’re using Helmet, then this is already done for you!

Then make sure that you're redirecting any http requests that do make it to the server to the same url over https. The express-enforce-ssl middleware provides an easy way to do this.

const express = require('express');
const expressEnforcesSSL = require('express-enforces-ssl');

const app = express();

app.enable('trust proxy');
app.use(expressEnforcesSSL());

Additionally you'll need a TLS certificate from a Certificate Authority. But if you are deploying your application to Heroku and using any hobby or professional dyno, you will automatically get TLS certificates set up through Let’s Encrypt for your custom domains by our Automated Certificate Management – and for applications without a custom domain, we provide a wildcard certificate for *.herokuapp.com.

What are your habits?

I try to follow these habits in all of my projects. Whether you’re new to node or a server-side JS veteran, I’m sure you’ve developed tricks of your own. We’d love to hear them! Share your habits by tweeting with the #node_habits hashtag.

Happy hacking!

Originally published: June 14, 2017

Browse the archives for news or all blogs Subscribe to the RSS feed for news or all blogs.