Introduction to Node.js
Node.js is an open-source, cross-platform JavaScript runtime environment that allows you to run JavaScript on the server side. Built on Chrome’s V8 JavaScript engine, Node.js was created to develop scalable network applications. In contrast to traditional JavaScript, which is typically used for client-side scripting in web browsers, Node.js enables JavaScript to be used for server-side scripting.
Key Features of Node.js
-
Asynchronous and Event-Driven: Node.js uses non-blocking, event-driven architecture, making it efficient and lightweight, especially for data-intensive, real-time applications.
-
Single-Threaded: Despite being single-threaded, Node.js can handle numerous concurrent connections due to its non-blocking nature.
-
Cross-Platform: Node.js can run on various platforms like Windows, Linux, Unix, and macOS.
-
JavaScript Everywhere: Node.js extends the reach of JavaScript, allowing developers to write both client-side and server-side code in JavaScript.
-
Large Ecosystem: It has a vast ecosystem of open-source libraries available through the npm (Node.js package manager).
Module Systems: CommonJS vs ES Modules
Node.js supports two different module systems, and understanding the difference is crucial to avoid confusion as you learn JavaScript.
CommonJS (Original Node.js Modules)
CommonJS was Node.js’s original module system, using require() to import and module.exports to export.
// greet.js (CommonJS)
const greet = (name) => {
console.log(`Hello, ${name}!`);
};
const farewell = (name) => {
console.log(`Goodbye, ${name}!`);
};
module.exports = { greet, farewell };
// app.js (CommonJS)
const { greet, farewell } = require('./greet');
greet('Alice'); // Hello, Alice!
farewell('Bob'); // Goodbye, Bob!
Characteristics:
- Uses
require()to import - Uses
module.exportsorexportsto export - Synchronous loading
- Default in Node.js (no configuration needed)
- File extension:
.js
ES Modules (Modern JavaScript Standard)
ES Modules (ESM) is the modern, standardized module system used in browsers and modern JavaScript. It uses import and export.
// greet.mjs (ES Modules)
export const greet = (name) => {
console.log(`Hello, ${name}!`);
};
export const farewell = (name) => {
console.log(`Goodbye, ${name}!`);
};
// app.mjs (ES Modules)
import { greet, farewell } from './greet.mjs';
greet('Alice'); // Hello, Alice!
farewell('Bob'); // Goodbye, Bob!
Characteristics:
- Uses
importto import - Uses
exportto export - Asynchronous loading
- Requires configuration OR
.mjsfile extension - Same syntax as modern browser JavaScript
Enabling ES Modules in Node.js
There are two ways to use ES Modules in Node.js:
Option 1: Use .mjs File Extension
Simply name your files with .mjs instead of .js:
Run with: node app.mjs
Option 2: Add "type": "module" to package.json
Add this to your package.json:
Now all .js files are treated as ES Modules:
Run with: node app.js
Key Differences
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require() / module.exports |
import / export |
| Loading | Synchronous | Asynchronous |
| Top-level await | ❌ Not supported | ✅ Supported |
| File extension | .js (default) |
.mjs or .js with config |
| Browser compatible | ❌ No | ✅ Yes |
| Default in Node.js | ✅ Yes | Requires config |
Practical Examples
Default Exports
CommonJS:
// math.js
module.exports = (a, b) => a + b;
// app.js
const add = require('./math');
console.log(add(2, 3)); // 5
ES Modules:
// math.mjs
export default (a, b) => a + b;
// app.mjs
import add from './math.mjs';
console.log(add(2, 3)); // 5
Named Exports
CommonJS:
// math.js
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// app.js
const { add, subtract } = require('./math');
ES Modules:
// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// app.mjs
import { add, subtract } from './math.mjs';
Which Should You Use?
Use CommonJS when:
- Working with older Node.js code
- Using npm packages that only support CommonJS
- You want the simplest setup (no configuration needed)
Use ES Modules when:
- Starting a new project
- Want modern JavaScript syntax
- Planning to share code with browser JavaScript
- Need top-level
await
Mixing Module Systems
You cannot use require() in ES Modules or import in CommonJS files. Choose one system per project for consistency.
Importing Built-in Node.js Modules
Both systems work with Node.js built-in modules:
CommonJS:
ES Modules:
Modern Node.js Recommendation
For new projects learning Node.js, use ES Modules (import/export) because:
- It’s the standard for modern JavaScript
- Same syntax works in both browser and Node.js
- Better support in modern tools and frameworks
Throughout this course, we’ll primarily use ES Modules, but you’ll see CommonJS in many existing packages and tutorials.
Quick Reference
CommonJS Pattern:
ES Modules Pattern:
Understanding both systems will help you work with any Node.js code you encounter, whether it’s legacy or modern.
npm - Node Package Manager
npm is an integral part of Node.js. It’s a package manager that allows you to install and manage libraries and dependencies for your Node.js applications.
- Installing Packages: Use
npm install package_nameto install a package. - package.json: This file holds metadata about your project and the list of dependencies.
Creating a Basic Web Server
Node.js is well-known for its ability to build fast and scalable web servers. Here’s a simple example:
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
This script creates a basic web server that listens on port 3000 and returns “Hello, World!” for every request.
Understanding Threading and Event Loop in Node.js
Introduction to Threading in Node.js
Node.js, a popular JavaScript runtime built on Chrome’s V8 JavaScript engine, operates on a single-threaded model. This might sound limiting at first, but it’s actually one of the key features that makes Node.js efficient for handling multiple simultaneous operations.
Single-threaded Model
- In Node.js, single-threading means all operations are executed on the same thread. This differs from traditional multi-threaded server models, where each request spawns a new thread, potentially leading to inefficiency and resource overhead.
- Node.js avoids the complexities and overheads of managing multiple threads, resulting in a simpler and more predictable code execution flow.
The Event Loop: Core of Asynchronous Operations
Node.js uses an event-driven architecture. The heart of this architecture is the “event loop,” which enables Node.js to perform non-blocking I/O operations, despite being single-threaded.
How the Event Loop Works
- Looping: The event loop continuously checks if there are any pending operations or tasks to be executed.
- Non-blocking I/O: When an I/O operation (like reading from a file or querying a database) is initiated, it’s handed off to the system kernel (as much as possible). Node.js doesn’t wait for the operation to complete and can handle other tasks in the meantime.
- Callbacks and Events: Once the I/O operation is complete, the callback function associated with that operation is queued to be executed. The event loop then picks up these callbacks and executes them sequentially.
Advantages of Event Loop
- Efficiency: By not blocking the main thread, Node.js can handle a high number of simultaneous operations, making it ideal for I/O-heavy applications.
- Scalability: It can scale well under load, especially for applications with a large number of concurrent connections and I/O operations.
Why So Much Async?
Asynchronous programming is a first-class concept in Node.js due to its non-blocking nature.
Handling I/O Operations
- Most I/O operations in Node.js are asynchronous. For instance, when you read from a file or make a network request, Node.js allows these operations to proceed in the background.
Callbacks, Promises, and Async/Await
- Asynchronous operations often rely on callbacks – functions that are called once an operation completes.
- To manage complex asynchronous operations more effectively, Node.js also uses Promises and the async/await syntax, which provides a cleaner, more manageable way to handle asynchronous code.
Conclusion: Why Node.js and Asynchronous Programming Go Hand in Hand
Node.js’s single-threaded, event-driven architecture, coupled with its non-blocking I/O model, makes asynchronous programming not just a choice, but a necessity. This approach ensures efficient utilization of server resources, especially under heavy I/O operations, which is a common scenario in modern web applications. Understanding and mastering asynchronous programming is key to building fast, scalable, and efficient applications in Node.js.
Some of the most used NPM packages
Lodash
Lodash is a utility library for JavaScript, offering a wide range of functions for tasks like array manipulation, string processing, and more. It’s particularly useful in Node.js for its performance and modular approach.
Express
Express is a minimal and flexible Node.js web application framework, providing a robust set of features for building web and mobile applications. It’s widely used for its simplicity and scalability in server-side development.
Axios
Axios is a promise-based HTTP client, well-suited for Node.js to make HTTP requests. It’s popular for its easy-to-use API and capability to handle requests and responses in a promise-based structure.
Moment
Moment is a date handling library, helping developers parse, validate, manipulate, and display dates and times in JavaScript. Although it’s considered legacy, it’s still widely used in many Node.js applications.
Chalk
Chalk lets you style terminal text in Node.js. It’s a simple way to add colors and styles, enhancing readability and debugging experience in the console.
Debug
Debug is a debugging utility that simplifies the process of logging debug statements. It’s easy to enable and disable and is highly configurable, making it a favorite for Node.js developers.
Bluebird
Bluebird is a promise library focused on performance and features. It allows you to convert callback-based APIs into promises, providing a modern approach to asynchronous programming in Node.js.
Mongoose
Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and translates between objects in code and their representation in MongoDB.
Underscore
Underscore is a utility library providing functional programming helpers without extending built-in JavaScript objects. It’s particularly handy in Node.js for its collection of useful functional programming tools.
Nodemon
Nodemon is a utility that monitors changes in your source code and automatically restarts your server. Ideal for development, it helps save time by eliminating the need for manual restarts.
These libraries are part of what makes Node.js a powerful and efficient environment for server-side programming. Each offers unique functionalities, contributing to easier development processes in various aspects of Node.js applications.
Development environment
Use Visual Studio Code as your editor. It’s free and open-source, and it has a large community of developers contributing to its development. It’s also highly customizable, allowing you to install extensions and themes to suit your needs.
There is also a lot of online editors, like OneCompiler, CodeSandbox, CodePen, StackBlitz, and JSFiddle.