Bigi Lui
2019-06-13 ⋅ 17 min read

A Guide for Picking Up Node.js Development for PHP developers

Consider this a follow-up from my article from 2017: Setting up your first Node.js environment! This is sort of the guide I wish I had when I first moved to a job working with node.js in 2017, coming from a PHP world before that.

If you’re a web developer coming from other server-side languages like PHP and a vanilla JS (or jQuery) frontend, chances are you’re already familiar with server side coding paradigms as well as vanilla Javascript syntax. The good news is, that actually gives you a solid foundation to start with node.js, since all node.js is is simply writing server side programs using Javascript syntax.

ES6 (ES2015) and node version 8+

Now, you could conceivably use old school vanilla Javascript to write all of your node.js server code. But you’d be: 1) missing out on a lot of what makes development in node.js easy and convenient (and even performant in some cases), 2) having trouble understanding node.js code snippets you find from the web that everyone uses.

I often like to say that if you already know vanilla JS, you really only need to learn a handful more concepts to code efficiently in ES6 and in node version 8+. There’re newer versions of ES (ECMAScript) that newer node versions make use of (node 10, node 12, and more soon), but most node.js codebases you will find are most commonly using up to ES6 features; which is why it’s the most helpful to get up to speed with, at a minimum.

Here are the handful of ES6 features you’ll want to pick up — for each one a tl;dr summary, and a detail section.

1. let and const instead of var

tl;dr: let and const are new keywords to declare a variable instead of var.

// Old JS (still works, but almost no one uses this anymore)
var foo_1 = 'bar';

// The ES6 / node 8+ way
let foo_2 = 'bar';
foo_2 = 'bar2'; // re-assign its value

// Consts values can't change
const foo_3 = 'bar';
foo_3 = 'bar2'; // this gives an error

Detail: let and var have scoping differences; while const may not be re-assigned.

In short, let has a smaller/more precise scope, which this top answer on Stackoverflow explains, so it’s generally safer to use. If you want to get really advanced and learn really deep, using let prevents you from falling into the trap of temporal dead zone which you can with var.

const has some whole other caveats. First, unlike languages like C++ where constants are usually just literals (strings or numbers), these consts can be objects and arrays, can be the result of a function, and typically can be anything variables are, with the exception that you can’t re-assign it. Note, you can however modify members of an object that is a const. Examples:

const foo_1 = getFoo(1); // This is fine
const foo_2 = { hello: 'world' }; // This is fine
foo_2.hello = 'earth'; // This is fine too!

In a ES6 node.js codebase, what you’ll usually find is that many variables that you normally expect to be declared as variables in similar code in other languages, are declared as consts.

2. Template literals / template strings

tl;dr: An easier way to combine some strings with variables, instead of using plus sign + for concatenation.

const str = 'world';
console.log(`Hello ${str}!`); // prints Hello world!

// The old way would have been...
console.log('Hello ' + str + '!');

// You can put entire expressions within them like a function call
// just like you would anywhere else:
console.log(`Hello ${getStr()}!`);

Detail: That’s mostly all there is to it, but it’s worth noting that sometimes a “tagged template” is used, which is probably most easily explained as “calling a function without using parenthesis”. It’s a little bit more than that, and you can read about it here.

3. require and module.exports

tl;dr: Loads other code files.

// File one: getHello.js
module.exports = function(param) {
  return `Hello ${param}!`;
}

// File two: index.js
const getHello = require('./getHello');

console.log(getHello('world')); // prints Hello world!

Detail: There’s scoping understanding to keep in mind, but generally you can consider it just like require in php or imports in other languages (Java, python).

As far as scoping, the most important thing to understand is that, in a file, unless something is set as a module.exports, no variables or objects or functions inside it would be exposed to any code that tries to require it.

You can set anything as the module.exports of a file. Usually it’s a function or an object (of functions), but it can also be an array, a string, or anything.

4. Arrow functions (fat-arrow functions)

tl;dr: A shorter way to define functions (especially anonymous functions) that everyone uses now:

// Defining a function and giving it a name
const myFunc = (param1, param2) => {
  return 'Hello!';
};

// If there is only one param, even the parenthesis is optional!
// This can look confusing at first. It works the same way.
const myFunc2 = param1 => {
  return 'Hello!';
};

// Passing in a function as a parameter, such as a callback.
// Note the lack of parameters here, which is valid.
setTimeout(() => { console.log('Hi'); }, 1000);

Detail: Binding difference. One of the main difference of using an arrow function is that the this object behaves differently (see here). But chances are that a functional JS codebase won’t be using this very much.

One neat trick, which can be a caveat if you’re not familiar with it, is that arrow functions allow you to return a one-liner directly, if you omit the curly braces:

const getHello = param => `Hello ${param}!`; // Valid function!

console.log(getHello('world')); // prints Hello world!

See here for details!

5. Object and array destructuring

tl;dr: A shorter/more convenient way to “break” members of an object (or array) out to separate variables.

// Example of an object. This can be returned from a function, etc.
const obj = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5
};

// You just need one and four? Sure!
const { one, four } = obj;

// Now the consts "one" and "four" are ready to be used.

// Alternatively, name them something else as you declare them:
const { one: foo, four: bar } = obj;

// Now foo = 1, and bar = 4!

// Similar things can be done with arrays
const arr = [1, 2, 3, 4, 5];

const [one, two] = arr; // one = 1, two = 2

Detail: There’s really not much else to it! The only thing to note is that it is often used as a parameter to function calls. If you’re not familiar with it and weren’t looking out for it, it can look confusing:

function doStuff({one, two, three}) {
  console.log(`Hey ${one} ho ${two} and ${three}!`);
}

// This allows you to pass in arguments in a more flexible manner,
// and also be more clear what you're passing in:
doStuff({three: 'foo', two: 'bar'});

6. Enhanced Object Literals

tl;dr: A short hand way to create an object with key names that you already have variables for.

const one = 1;
const two = 2;
const three = 3;
const myObj = { one, two, three };
// This is equivalent to:
// { one: one, two: two, three: three }

Detail: Not much more to it! It really is just a short hand / convenience feature. Another aspect of enhanced object literals is to have computed property keys:

const f = 'foo';
const myObj = {
  [f + 'bar']: true
};
// myObj is now { foobar: true }

See here for details!

7. Promises

tl;dr: An alternative to callback functions that make code look nicer.

// consider doing two http requests sequentially in callbacks...
const request = require('request');
request('http://www.google.com', function (err, resp, body) {
  request('http://www.yahoo.com', function (err, resp, body) {
    // do stuff with the result
  });
});

// similar code but in promise...
const requestpromise = require('request-promise'); // different lib
requestpromise('http://www.google.com')
  .then(function(body) {
    return requestpromise('http://www.yahoo.com');
  })
  .then(function(body) {
    // do stuff with the result
  })
  .catch(function(err) {
    // error handling
  });

Detail: The problem with callbacks is that as soon as you start having to do multiple steps of asynchronous things in order (e.g. multiple http requests), you start getting into “callback hell” where you wrap more and more callbacks. Promises solve this problem.

Promises also provide a lot of other advantages, most notably taking advantage of node.js’s asynchronous nature and simultaneously running multiple calls/requests that wait on I/O:

const rp = require('request-promise');
Promise.all([
  rp('http://www.google.com'),
  rp('http://www.yahoo.com')
])
  .then(function([body1, body2]) {
    // do stuff with both response bodies
  })
  .catch(function(err) {
    // this catches any error with either of the calls
  });

8. Async/await

tl;dr: Building on top of promises, let you write code that makes use of promises in a procedural way.

const rp = require('request-promise');

// Anything that returns a Promise can be await'd.
const body1 = await rp('http://www.google.com');
const body2 = await rp('http://www.yahoo.com');
// do stuff with body1 and body2

// You can still use Promise.all() to run them in parallel:
const [body1, body2] = await Promise.all([
  rp('http://www.google.com'),
  rp('http://www.yahoo.com')
]);

// Functions that contain "await" in its body need to be declared
// as "async" explicitly.
async function doStuff() {
  const body = await rp('http://www.google.com');
  // do stuff
}

// async functions are automatically implied to return a promise,
// so calling async functions will need an await:
await doStuff();

// Since that's just a regular promise, you can run in parallel too:
await Promise.all([
  doStuff(),
  rp('http://www.yahoo.com')
]);

// Error handling looks like this:
try {
  await doStuff();
} catch (err) {
  // error handling here
}

Detail: I gave quite a bit of examples in the code sample for this one, so that pretty much covers most of it.

The neat thing is, when you come from a world like PHP, the await pattern feels almost more natural than the original callbacks and promises mechanisms in node.js, because it makes code look more synchronous and procedural.


Once you’ve learned the above concepts, you’re already most of the way there in picking up node.js development.

Your next step would be to learn about functional programming and how a functional-based node.js code structure looks like, which is something higher level than language features. Functional-oriented programming is a different paradigm from object-oriented programming; diving into that would be outside of the scope of this article. Maybe next time? ;)