Use ECMAScript 6 in Node.js with Babel

ECMAScript 6 in Node.js with Babel

ECMAScript 6 is a great step for JavaScript. It provides some invaluable tools that make developing new code a bliss. If you want to learn about the features that ECMAScript 6 (also known as ECMAScript 2015, ES6 or ES2015), take a look at this two part series:

Unfortunately, Node.js will not let you use ES6 features out of the box. It does provide a way of using some of the ECMAScript 6 features, but not the whole thing. That’s what we are here to fix.

To take you through the process, we’re going to take the timestamp microservice app we developed in this post, and start using ES6 using Babel. You can go ahead and go through the whole post, or simply clone the git project.

Once you got it all set up (and have all of the dependencies installed by running npm install ), we can get started on the good stuff.

First, we need to install Babel. We are going to add the following 3 node modules to our project:

  • babel-core: Core Babel library.
  • babel-register: Register hook for modules (we’ll get to this)
  • babel-preset-es2015: ECMAScript 2015 feature preset.

Without further due, here we go:

npm install --save babel-core babel-register babel-preset-es2015

 

We now need to tell Babel what it needs to do. Create a new file in the project root and call it .babelrc, then, add the following content:

{
  "presets": ["es2015"]
}

 

This will simply tell Babel that it needs to use the ECMAScript 2015 preset to transpile the code.


We now have everything that we need to start rocking. The first thing we’ll do, is reorganize the project structure. Start by creating a new directory in the project root. Call it src. Now, move index.js to this directory, and rename it to app.js. Finally, create a new, empty index.js file, and place it in the root directory. Here’s what the result should look like:

timestamp-microservice
  |
  |-- index.js (empty file we just created)
  |-- Procfile
  |-- .babelrc
  |-- .gitignore
  |-- package.json
  |
  |-- /node_modules
  |-- /src
        |
        |-- app.js (former index.js file with the actual application code)

 

Why move all of this around? It will make sense in just a second. Babel provides an easy way to transpile our files through the require hook, that’s what the babel-register package that we installed previously does. If we import this package into a file, everything else that we import afterwards will get transpiled. In other words, once we change the syntax in app.js to use ECMAScript 6, simply doing this will work:

require('babel-register');

// Everything that we import using require will
// get transpiled.

var app = require('src/app'); // This file can use ES6!

 

Before we can actually import anything from app.js though, we need to export it. Let’s go ahead and refactor app.js to use a few ECMAScript 6 features, such as string interpolation and arrow functions, along with exporting the express application. Additionally I’m going to remove the app.listen() part, and move it to the new index.js file later on. This is what app.js looks like now:

import express from 'express';  // ES6 import!

export const app = express(); // We export app to import it into index.js later on
// PORT is now gone

app.get('/:timestamp', (request, response) => { // Arrow funtion!
  var timestamp = request.params.timestamp;
  response.status(200).json(getTimestampJSON(timestamp)); // I'm sending a 200 OK status with the response JSON no!
});

function getTimestampJSON(timestamp) {
  var result = {
    unix: null,
    natural: null
  };

  var date;
  if (!isNaN(parseInt(timestamp))) {
    date = new Date(parseInt(timestamp));
  } else {
    date = new Date(timestamp);
  }
  if (!isNaN(date.getTime())) {
    result.unix = date.getTime();
    result.natural = getNaturalDate(date);
  }

  return result;
}

function getNaturalDate(date) {
  var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  // String interpolation!
  return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}

 

Now, let get index.js to import our app and take care of initialization:

require('babel-register');

// app is imported through require and so, gets transpiled
const app = require('./src/app').app,
      PORT = process.env.PORT || 3000;

// We initialize the server here
app.listen(PORT, function() {
    console.log('Server listening on port', PORT);
});

 

There we go, our app is whole and working again, doing an npm start  should get you on your way to success!


Extra credit: Abstraction

Since we are at it, how about we move a few more things? Those two functions… getTimestampJSON and getNaturalDate… they make the API code seem a little convoluted, don’t they? Let’s move them somewhere else: create a new folder inside src and call it utils. Now, create a new file inside utils, and name it time-utils.js.

Next, remove both getTimestampJSON and getNaturalDate from app.js and put them in time-utils.js. Finally, we’ll define getTimestampJSON as an export like so:

export function getTimestampJSON(timestamp) {
    var result = {
        unix: null,
        natural: null
    };

    var date;
    if (!isNaN(parseInt(timestamp))) {
        date = new Date(parseInt(timestamp));
    } else {
        date = new Date(timestamp);
    }

    if (!isNaN(date.getTime())) {
        result.unix = date.getTime();
        result.natural = getNaturalDate(date);
    }

    return result;
}

// getNaturalDate is only ever used by getTimeStampJSON, so we don't need to export it
function getNaturalDate(date) {
    var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}

 

Now, import getTimestampJSON into src/app.js:

import express from 'express';
import { getTimestampJSON } from './utils/time-utils';

export const app = express();

app.get('/:timestamp', (request, response) => {
    var timestamp = request.params.timestamp;
    response.status(200).json(getTimestampJSON(timestamp));
});

Extra credit: Root path

When a user navigates to the application URL, they’ll be shown nothing. Only users who call the /:timestamp endpoint directly will see anything in their screen. As an extra step, we are going to respond to requests to the root path and show a simple message:

import express from 'express';
import { getTimestampJSON, getNaturalDate } from './utils/time-utils';

export const app = express();

app.get('/', (request, response) => {
    response.status(200).send('Use a timestamp or natural date as path parameter to get the timestamp object e.g.: /October%201,%202016 or /1477473583462');
});

app.get('/:timestamp', (request, response) => {
    var timestamp = request.params.timestamp;
    response.status(200).json(getTimestampJSON(timestamp));
});