🔥Limited Offer: Get 50% OFFon AI & Full Stack Courses🔥
Back to Node.js Notes
Topic #36

Node ES6+


What is ES6+?

ES6 (ECMAScript 2015) and later versions add powerful new features to JavaScript that make your code more expressive, concise, and safer.

Node.js has excellent support for modern JavaScript features.

Node.js Compatibility: All modern versions of Node.js (10+) have excellent support for ES6+ features.

Newer versions support even more recent JavaScript additions from ES2020 and beyond.

These modern JavaScript features help you:

  • Write cleaner, more readable code
  • Avoid common JavaScript pitfalls
  • Create more maintainable applications
  • Reduce the need for external libraries

let and const

The let and const keywords replaced var as the preferred way to declare variables:

  • let allows you to declare variables that can be reassigned
  • const declares variables that cannot be reassigned (but object properties can still be modified)
  • Both are block-scoped, unlike var which is function-scoped

Example: let and const

// Using let (can be changed)
let score = 10;
score = 20;

// Using const (cannot be reassigned)
const MAX_USERS = 100;

// Block scope with let
if (true) {
  let message = 'Hello';
  console.log(message); // Works here
}

Arrow Functions

Arrow functions provide a concise syntax for writing functions and automatically bind this to the surrounding context.

Key benefits of arrow functions:

  • Shorter syntax for simple functions
  • Implicit return for one-line expressions
  • Lexical this binding (arrow functions don't create their own this context)

Example: Arrow Functions

// Traditional function
function add(a, b) {
  return a + b;
}

// Arrow function (same as above)
const addArrow = (a, b) => a + b;

// Single parameter (no parentheses needed)

const double = num => num * 2;

// No parameters (parentheses needed)
const sayHello = () => 'Hello!';

// Using with array methods
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled);

Warning: When NOT to use arrow functions: Object methods (where you need this to reference the object) Constructor functions (arrow functions can't be used with new) Event handlers where this should refer to the element


Template Literals

Template literals (template strings) provide an elegant way to create strings with embedded expressions using backticks (```).

Key features of template literals:

  • String interpolation with ${expression} syntax
  • Multi-line strings without escape characters
  • Tagged templates for advanced string processing

Example: Template Literals

// Basic string interpolation
const name = 'Alice';
console.log(`Hello, ${name}!`);

// Multi-line string
const message = `
  This is a multi-line
  string in JavaScript.
`;
console.log(message);

// Simple expression
const price = 10;
const tax = 0.2;
console.log(`Total: $${price * (1 + tax)}`);

Destructuring

Destructuring allows you to extract values from arrays or properties from objects into distinct variables with a concise syntax.

Key features of destructuring:

  • Extract multiple values in a single statement
  • Assign default values to extracted properties
  • Rename properties during extraction
  • Skip elements in arrays
  • Extract deeply nested properties

Example: Object Destructuring

// Basic object destructuring
const user = { name: 'Alice', age: 30, location: 'New York' };
const { name, age } = user;
console.log(name, age);

Example: Array Destructuring

// Basic array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third);

// Skipping elements
const [primary, , tertiary] = colors;
console.log(primary, tertiary);

Spread and Rest Operators

The spread and rest operators (both written as ...) allow you to work with multiple elements more efficiently.

  • Spread operator: Expands iterables (arrays, objects, strings) into individual elements
  • Rest operator: Collects multiple elements into a single array or object

Example: Spread Operator

// Array spread - combining arrays
const numbers = [1, 2, 3];
const moreNumbers = [4, 5, 6];
const combined = [...numbers, ...moreNumbers];
console.log(combined);

// Array spread - converting string to array of characters
const chars = [...'hello'];
console.log(chars);

Example: Rest Operator

// Rest parameter in functions
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5));

Default Parameters

ES6+ allows you to specify default values for function parameters, eliminating the need for manual parameter checking in many cases.

Key benefits of default parameters:

  • Cleaner function definitions without manual checking
  • More explicit function signatures
  • Default values are only used when parameters are undefined or not provided
  • Default values can be expressions, not just simple values

Example: Default Parameters

// Basic default parameter
function greet(name = 'Guest') {
  return `Hello, ${name}!`;
}

console.log(greet());
console.log(greet('Kai'));

Classes

ES6 introduced class syntax to JavaScript, providing a clearer and more concise way to create objects and implement inheritance.

Under the hood, JavaScript classes are still based on prototypes.

Key features of JavaScript classes:

  • Cleaner syntax for creating constructor functions and methods
  • Built-in support for inheritance using extends
  • Static methods attached to the class, not instances
  • Getter and setter methods for more controlled property access
  • Private fields for better encapsulation (ES2022+)

Example: Class Basics

// Simple class with constructor and method
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, I'm ${this.name}!`;
  }
}

// Create an instance
const person = new Person('Alice', 25);
console.log(person.greet()); // Hello, I'm Alice!

Example: Class Inheritance

// Parent class
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound.`;
  }
}

// Child class
class Dog extends Animal {
  speak() {
    return `${this.name} barks!`;
  }
}

const dog = new Dog('Rex');
console.log(dog.speak());

Example: Private Class Fields (ES2022+)

// Class with private field (# prefix)
class Counter {
  #count = 0;  // Private field

  increment() {
    this.#count++;
  }

  getCount() {
    return this.#count;
  }
}

const counter = new Counter();
counter.increment();
console.log(counter.getCount());
// console.log(counter.#count); // Error: Private field

Promises and Async/Await

Modern JavaScript provides powerful tools for handling asynchronous operations, making it much easier to work with code that involves delays, API calls, or I/O operations.


Promises

Promises represent values that may not be available yet.

They provide a more elegant way to handle asynchronous operations compared to callbacks.

A Promise is in one of these states:

  • Pending: Initial state, neither fulfilled nor rejected
  • Fulfilled: Operation completed successfully
  • Rejected: Operation failed

Example: Basic Promises

// Creating a promise
const fetchData = () => {
  return new Promise((resolve, reject) => {
     // Simulating an API call
     setTimeout(() => {
       const data = { id: 1, name: 'Product' };
       const success = true;

       if (success) {
         resolve(data);  // Fulfilled with data
       } else {
         reject(new Error('Failed to fetch data')); // Rejected with error
       }
     }, 1000);
  });
};

// Using a promise
console.log('Fetching data...');

fetchData()
  .then(data => {
    console.log('Data received:', data);
    return data.id; // Return value is passed to the next .then()
  })
  .then(id => {
    console.log('Processing ID:', id);
  })
  .catch(error => {
    console.error('Error:', error.message);
  })
  .finally(() => {
    console.log('Operation completed (success or failure)');
  });

console.log('Continuing execution while fetch happens in background');

Async/Await

Async/await (introduced in ES2017) provides a cleaner syntax for working with promises, making asynchronous code look and behave more like synchronous code.

Example: Async/Await

// Function that returns a promise
const fetchUser = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: `User ${id}` });
       } else {
        reject(new Error('Invalid user ID'));
       }
     }, 1000);
  });
};

// Using async/await
async function getUserData(id) {
  try {
    console.log('Fetching user...');
    const user = await fetchUser(id); // Waits for the promise to resolve
    console.log('User data:', user);

    // You can use the result directly
    return `${user.name}'s profile`;
  } catch (error) {
     // Handle errors with try/catch
     console.error('Error fetching user:', error.message);
     return 'Guest profile';
   }
}

// Async functions always return promises
console.log('Starting...');
getUserData(1)
  .then(result => console.log('Result:', result))
  .catch(error => console.error('Unexpected error:', error));
console.log('This runs before getUserData completes');

Warning: Common Async/Await Mistakes: Forgetting that async functions always return promises Not handling errors with try/catch Running operations sequentially when they could run in parallel Using await outside of an async function Awaiting non-promise values (unnecessary but harmless)


ES Modules

ES Modules (ESM) provide a standardized way to organize and share code. They were introduced in ES2015 and are now supported natively in Node.js.

Key benefits of ES Modules:

  • Static module structure (imports are analyzed at compile time)
  • Default and named exports/imports
  • Better dependency management
  • Tree-shaking (eliminating unused code)

Example: ES Modules

// Named exports
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// Default export
export default class Calculator {
  add(a, b) {
    return a + b;
  }

  subtract(a, b) {
    return a - b;
  }
}

To use ES Modules in Node.js, you can either:

  1. Use the .mjs extension for module files
  2. Add "type": "module" to your package.json
  3. Use the --experimental-modules flag (older Node.js versions)

Note: The CommonJS module system (require() and module.exports) is still widely used in Node.js. ES Modules and CommonJS can coexist in the same project, but they have different semantics.


Enhanced Object Literals

ES6+ introduced several improvements to object literals that make object creation more concise and expressive.

Example: Enhanced Object Literals

// Property shorthand
const name = 'Alice';
const age = 30;

// Instead of {name: name, age: age}
const person = { name, age };
console.log(person);

// Method shorthand
const calculator = {
  // Instead of add: function(a, b) { ... }
  add(a, b) {
    return a + b;
  },
  subtract(a, b) {
    return a - b;
  }
};
console.log(calculator.add(5, 3));

Optional Chaining and Nullish Coalescing

Modern JavaScript introduces syntax to safely access nested properties and provide fallback values.

Optional Chaining (?.)

Optional chaining lets you access deeply nested object properties without worrying about null or undefined values in the chain.

Example: Optional Chaining

function getUserCity(user) {
  return user?.address?.city;
}

const user1 = {
  name: 'Alice',
  address: { city: 'New York', country: 'USA' }
};

const user2 = {
  name: 'Bob'
};

const user3 = null;

console.log(getUserCity(user1)); // 'New York'
console.log(getUserCity(user2)); // undefined
console.log(getUserCity(user3)); // undefined

Nullish Coalescing (??)

The nullish coalescing operator (??) provides a default value when a value is null or undefined (but not for other falsy values like 0 or "").

Example: Nullish Coalescing

function calculatePrice(price, tax) {
  // Only uses default if tax is null or undefined
  return price + (tax ?? 0.1) * price;
}

console.log(calculatePrice(100, 0));    // 100 (correct! tax of 0 was used)
console.log(calculatePrice(100, null)); // 110 (using default)

Modern Asynchronous Patterns

Modern JavaScript provides powerful patterns for handling asynchronous operations. Understanding when to use sequential vs parallel execution can significantly improve your application's performance.

Note: Sequential vs Parallel Execution: Sequential: Operations run one after another, each waiting for the previous to complete Parallel: Operations run simultaneously, which is more efficient when operations are independent

Example: Sequential Execution

// Helper function to simulate an API call
function fetchData(id) {
  return new Promise(resolve => {
    setTimeout(() => resolve(`Data for ID ${id}`), 1000);
  });
}

// Sequential execution (~3 seconds total)
async function fetchSequential() {
  console.time('sequential');
  const data1 = await fetchData(1);
  const data2 = await fetchData(2);
  const data3 = await fetchData(3);
  console.timeEnd('sequential');
  return [data1, data2, data3];
}

// Run the sequential example
fetchSequential().then(results => {
  console.log('Sequential results:', results);
});

Example: Parallel Execution

// Parallel execution (~1 second total)
async function fetchParallel() {
  console.time('parallel');
  const results = await Promise.all([
    fetchData(1),
    fetchData(2),
    fetchData(3)
  ]);
  console.timeEnd('parallel');
  return results;
}

// Run the parallel example
fetchParallel().then(results => {
  console.log('Parallel results:', results);
});

Warning: When to Use Each Pattern: Use sequential when operations depend on previous results Use parallel when operations are independent and can run simultaneously Be mindful of rate limits when making parallel API calls Consider using libraries like p-queue for controlled concurrency


Summary

ES6+ introduced numerous features that have transformed JavaScript programming, making code more readable, maintainable, and robust:

  • let/const: Block-scoped variables with clearer semantics
  • Arrow functions: Concise syntax and lexical this binding
  • Template literals: String interpolation and multi-line strings
  • Destructuring: Extract values from objects and arrays easily
  • Spread/rest operators: Work with collections more efficiently
  • Default parameters: Simpler function definitions
  • Classes: Cleaner syntax for object-oriented programming
  • Promises and async/await: Better asynchronous code management
  • ES Modules: Standardized code organization
  • Enhanced object literals: More concise object syntax
  • Optional chaining & nullish coalescing: Safer property access and defaults

These modern features are fully supported in current Node.js versions, allowing you to write cleaner, more expressive code while avoiding common JavaScript pitfalls.

Want to go beyond the notes?

Join CodingNow 2.0's Node.js course — live mentorship, real projects, and 100% placement support.

Enroll Now — Free Demo Available

Node ES6+ – FAQs

Quick answers about learning Node ES6+ in Node.js.

This free note from CodingNow 2.0 explains Node ES6+ in Node.js — concept, syntax and worked code examples you can copy, run and revise before interviews.
Yes. Every Node.js topic on CodingNow 2.0, including Node ES6+, is 100% free with no signup required.
With focused practice, most students grasp Node ES6+ in 1–3 days from these notes; pairing it with CodingNow 2.0's mentor-led course takes you to job-ready depth faster.
Use the code examples in this note, then ask doubts for free on the CodingNow 2.0 Community (/community) — expert instructors answer within 24 hours.
WhatsApp
Call NowEnroll Now