10 JavaScript Concepts Every Node.js Developer Should Know | Syncfusion Blogs
Detail-Blog-Page-skeleton-loader-new

Summarize this blog post with:

TL;DR: Struggling to adapt your JavaScript skills to Node.js? You’re not alone. Node.js uses JavaScript, but its server-side environment changes how core concepts like globals, events, and modules work. This guide breaks down the 10 essential JavaScript concepts every Node.js developer needs to master, starting with key differences from browser-based JavaScript to help you avoid pitfalls and write cleaner, more reliable code. Whether you’re debugging unexpected behavior or optimizing performance, these insights will help you build scalable, efficient server-side applications.

JavaScript is one of the most popular programming languages worldwide, especially for web development. Its flexibility and dynamic nature make it easy to learn and widely adopted.

This popularity paved the way for Node.js, a powerful runtime environment that lets developers run JavaScript on the server side using the Chrome V8 engine. While Node.js uses the same JavaScript syntax, its execution environment introduces key differences.

This guide covers the top 10 concepts you need to understand to leverage JavaScript effectively in Node.js, ensuring you avoid runtime surprises and build robust applications.

Syncfusion JavaScript UI controls are the developers’ choice to build user-friendly web applications. You deserve them too.

1. Global scope and runtime differences

In browser-based JavaScript, the global object is window, which provides access to Web APIs like setTimeout, setInterval, and DOM manipulation via the document object. In Node.js, the global object is global, and it operates differently, with unique scope and accessibility rules. It can access server-specific APIs like setImmediate, nextTick, and others, but lacks browser-specific objects like alert, prompt, confirm, or document.

// JavaScript
alert('syncfusion');
localStorage.setItem("syncfusion", "hello");
// Nodejs
// alert is not defined
// localStorage is not defined

This distinction is critical because developers often expect browser-like behavior in Node.js. For example, using this in the global scope of a Node.js module refers to module.exports, not global, which can lead to bugs when accessing global variables or methods.

//Browser
console.log(this); // window
//Nodejs
console.log(this); // {}
console.log(global); // [global object]

DOM access

Node.js runs outside the browser, so it cannot access the DOM or BOM APIs. This is a common stumbling block for frontend developers moving to backend JavaScript.

// JavaScript
document.getElementById('syncfusion').innerText = "hello";
// Nodejs
// ReferenceError: document is not defined

2. Event loop and timers

Node.js introduces setImmediate() and nextTick(), but at different phases of the event loop. nextTick() executes immediately after the current action but before other I/O events, whereas setImmediate() executes in the next event loop cycle after an I/O operation. setImmediate() is only supported in Node.js.

//Nodejs
process.nextTick(() => console.log('nextTick'));
setImmediate(() => console.log('setImmediate'));
console.log('main');

// Output:
// main
// nextTick
// setImmediate

Understanding these timers is crucial for managing asynchronous tasks and optimizing performance in Node.js applications.

3. Asynchronous programming with promises and Async/Await

JavaScript is single threaded but supports asynchronous operations via promises and async/await. These are essential for writing non-blocking server-side code.

While Node.js is event-driven, so is async programming at its core. To wait for the existing operations to finish and move forward, we rely on promises or async-await.

Every property of the Syncfusion JavaScript controls is completely documented to make it easy to get started.

Promises

Promises allow you to handle asynchronous operations by invoking callback functions when an action completes, but they can lead to callback hell with nested callbacks.

// Example: promise
await fetch('https://api.example.com/data')
    .then(res => res.json())
    .then(json => {
        console.log(json);
    }).catch(err => {
        console.error(err);
    }).finally(() => {
        console.log('action completed');
    });

Async/Await

Async/await is an alternative to promises, which helps us execute asynchronous tasks synchronously using the await keyword.

// Example: async/await
async function fetchData() {
    try{
        const response = await fetch('https://api.example.com/data');
        const json = await response.json();
        console.log(json);
    }catch(e){
        console.error(e);
    }finally(){
        console.log('action completed');
    }
}

Node.js supports fetch() natively from version 18 onward, aligning browser and server HTTP request APIs. Check the official documentation for more details.

4. Handling large data with streams

Streams are essential for handling large files or network responses without loading everything into memory. This is especially useful for performance optimization.

const fs = require('fs');
fs.createReadStream('file.txt')
  .pipe(fs.createWriteStream('copy.txt'));

You can also listen to stream events:

const fs = require('fs');
const filePath = largeFile.txt';
const readableStream = fs.createReadStream(filePath, { encoding: 'utf8' });
readableStream.on('data', (chunk) => { 
    console.log(`${chunk.length} characters of data is received.`); 
}

readableStream.on('end', () => { 
    console.log(Completed reading the file.');
});

readableStream.on('error', (error) => { 
    console.error(`An error occurred: ${error.message}`); 
});

5. Modules: CommonJS and ES Modules

Modules are fundamental for writing scalable and maintainable JavaScript applications. They allow you to isolate logic into reusable pieces, reducing redundancy and improving organization. Node.js supports two module systems, each with distinct syntax and use cases.

1. CommonJS

CommonJS is the traditional Node.js module system, using require() to import and module.exports to export.

// export
// example.js
function add(a, b) {
    return a + b;
}

module.exports = {
    add
};

// import
const {add} = require('./math');
console.log(add(2, 3));      // 5

CommonJS is used by default in .js files and is widely supported across Node.js versions.

2. ES Modules

ES Modules use import and export syntax, aligning with browser-based JavaScript.

// example.mjs
export function add(a, b) {
    return a + b;
}

export default function log(message) {
    console.log(`[LOG]: ${message}`);
}

import { add} from './example.mjs';
log('App initialized'); // [LOG]: App initialized
console.log(add(2, 3));      // 5

ES Modules are supported for .mjs files or when type: module is set in package.json.

To make it easy for developers to include Syncfusion JavaScript controls in their projects, we have shared some working ones.

Event handling with EventEmitter

Node.js excels at event-driven programming, using the EventEmitter class to create and listen to custom events, unlike browser JavaScript’s DOM-based event listeners. This is useful for decoupling logic and building scalable systems.

window.addEventListener('click', (e) => {
    console.log('clicked', e.target);
});
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('log', msg => console.log(msg));
emitter.emit('log', 'An event has occurred!');

This approach allows passing data to multiple parts of an application efficiently.

7. Multi-threading with worker threads

For CPU-intensive tasks, Node.js supports multi-threading via worker_threads, offloading heavy computations without blocking the main thread. This contrasts with browser-based web workers.

const { Worker } = require('worker_threads');
new Worker('./heavyComputation.js');

8. Custom error handling

Creating custom error classes in Node.js improves error clarity and control, especially in large applications.

class NotFoundError extends Error {
    constructor(message) {
        super(message);
        this.name = "NotFoundError";
        this.status = 404;
    }
}

9. Closures and higher-order functions

Closures and higher-order functions are core JavaScript concepts that shine in Node.js for creating middleware, managing state, and enabling functional programming patterns.

Closures

Closures allow functions to retain access to their lexical scope, which is useful for creating Higher-order functions and middleware patterns.

function outer() {
    let count = 0;
    return function inner() {
        return ++count;
    };
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2

Higher-order functions

Higher-order functions take or return other functions, enabling functional programming patterns.

const add = x => y => x + y;
const add5 = add(5);
console.log(add5(3)); // 8

These are particularly useful in Node.js for building reusable middleware and managing async workflows.

10. Understanding ‘this’ in JavaScript functions

The this is a special keyword in JavaScript whose value is decided at runtime depending upon the context in which it was called.

Normal function

In a normal function, this refers to the global object in Node.js.

function example(){
    // in Node.js, this refers to the global object
    console.log(this === global);
}

example();
// true

As a method

When invoked as a method, this refers to the object.

const obj = {
    blog: "syncfusion",
    displayBlog: function (){
        console.log(this.blog);
    }
};

obj.displayBlog();
// "syncfusion"

As a constructor

When used as a constructor, this refers to the new instance.

function Example(blog){
    this.blog = blog;
    this.displayBlog = function(){
        console.log(this.blog);
    };
};

const example = new Example("syncfusion");
example.displayBlog(); // "syncfusion"

Runtime binding

You can update this at runtime using call or apply.

const obj = {
    blog: syncfusion,
};

function example(name) {
    console.log(`${name} runs ${this.blog}`);
};

example.call(obj, 'Daniel');
// "Daniel runs syncfusion"

example.apply(obj, ['Daniel']);
// "Daniel runs syncfusion"

The only difference between call and apply is that call accepts individual values, whereas apply accepts an array of values along with the context as the first argument.

Permanent binding:

The bind method permanently sets this.

const obj = {
    name: 'Daniel',
};

function example(blog) {
    console.log(`${this.name} runs ${blog}`);
};

const bounded = example.bind(obj);

bounded('MDN');
// "Daniel runs MDN"

Easily build real-time apps with Syncfusion’s high-performance, lightweight, modular, and responsive JavaScript UI components.

Conclusion

Node.js lets you write server-side code using JavaScript, but its environment introduces key differences that can trip up even experienced developers. By mastering these 10 JavaScript concepts, especially how they behave in Node.js, you’ll avoid common pitfalls, write cleaner code, and build scalable backend applications. Try applying these concepts in your next Node.js project and explore the Node.js documentation for deeper insights!

Be the first to get updates

Prashant YadavPrashant Yadav profile icon

Meet the Author

Prashant Yadav

Senior Frontend Engineer at Razorpay. On a journey to become Frontend Architect. Writes about JavaScript and Web development on learnersbucket.com

Leave a comment