Event-Driven Architecture is a software design pattern where components communicate and interact through events. Node.js, being built on this architecture, heavily relies on events to manage asynchronous operations. In this architecture, actions or events trigger responses, rather than relying on direct function calls or continuous polling.
-
Event Emitter:
- An object that emits events. When an event is emitted, all the listeners registered for that event are called.
-
Event Listener:
- A function that waits for a specific event to occur. Once the event is emitted, the listener executes a callback function.
-
Pub/Sub Pattern:
- Publishers emit events, and subscribers (listeners) react to those events without knowing about each other's internal details.
-
EventEmitterClass:-
Node.js has a built-in
eventsmodule with anEventEmitterclass, which is at the core of EDA in Node.js. -
Example:
const EventEmitter = require('events'); const eventEmitter = new EventEmitter(); // Define a listener eventEmitter.on('greet', (name) => { console.log(`Hello, ${name}!`); }); // Emit the event eventEmitter.emit('greet', 'Skyy');
-
Explanation:
on('greet', callback)sets up a listener for thegreetevent.emit('greet', 'Skyy')triggers the event, which invokes the listener.
-
-
Asynchronous Nature:
- Node.js handles multiple events concurrently using its non-blocking, single-threaded event loop.
- When an event is emitted, its listeners are executed asynchronously without blocking the main thread.
-
Error Handling in Events:
- You can listen for
errorevents to gracefully handle errors in your application:eventEmitter.on('error', (err) => { console.error(`Error occurred: ${err.message}`); }); eventEmitter.emit('error', new Error('Something went wrong!'));
- You can listen for
-
Scalability:
- EDA allows easy handling of many connections or events, making it suitable for real-time applications (e.g., chats, notifications).
-
Decoupling:
- Components remain loosely coupled. The event publisher and the event listeners don't need to know about each other's existence.
-
Asynchronous Handling:
- EDA leverages Node.js's non-blocking I/O for better performance under heavy loads.
-
Flexibility:
- Events can be chained, queued, or broadcast to multiple listeners, providing flexibility in how the system behaves.
-
Web Servers:
-
Node.js HTTP servers are event-driven. Requests and responses are handled through events.
const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, World!'); }); server.listen(3000, () => { console.log('Server is listening on port 3000'); });
-
-
Real-Time Applications:
- WebSockets and frameworks like
Socket.IOuse EDA to send real-time updates to clients.
- WebSockets and frameworks like
-
File System I/O:
- File operations in Node.js are asynchronous and event-driven:
const fs = require('fs'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) throw err; console.log(data); });
- File operations in Node.js are asynchronous and event-driven:
-
Streams:
- Node.js streams (used for reading and writing data) are inherently event-driven. They emit events like
data,end, anderror.
- Node.js streams (used for reading and writing data) are inherently event-driven. They emit events like
- The event loop is the heart of Node.js’s event-driven architecture.
- It continuously checks for new events and executes their corresponding listeners.
- Steps in the Event Loop:
- Executes any synchronous code.
- Processes I/O events (like file reads, HTTP requests).
- Executes queued timers (e.g.,
setTimeout). - Handles events emitted by the
EventEmitter.
Event-Driven Architecture is essential to how Node.js operates. It allows for efficient handling of asynchronous tasks and provides flexibility and scalability. Understanding EDA is crucial for building robust, real-time, and performant Node.js applications.
In Node.js, an event is a signal that indicates something has happened in the application. It could represent a wide range of actions, such as:
- A user request (e.g., an HTTP request arriving at a server).
- Completion of a file read/write operation.
- A WebSocket message received.
- A custom action or notification triggered by the developer.
Node.js is built on an event-driven architecture, meaning the application listens for events and reacts to them using callback functions (also known as event listeners). This model enables Node.js to efficiently handle multiple concurrent operations without blocking the main thread.
The EventEmitter is a core class in Node.js provided by the events module. It is used to manage and trigger custom events in your application.
- It allows one part of your code to emit an event and another part to listen for that event and execute a function in response.
-
Importing the events module:
const EventEmitter = require('events'); const eventEmitter = new EventEmitter();
-
Setting Up a Listener:
eventEmitter.on(eventName, listenerFunction)is used to listen for an event:eventEmitter.on('greet', (name) => { console.log(`Hello, ${name}!`); });
-
Emitting an Event:
eventEmitter.emit(eventName, ...args)is used to trigger the event:eventEmitter.emit('greet', 'Skyy'); // Output: Hello, Skyy!
-
on(event, listener):- Adds a listener for a specific event.
- Example:
eventEmitter.on('sayHi', () => { console.log('Hi there!'); }); eventEmitter.emit('sayHi'); // Output: Hi there!
-
emit(event, ...args):- Triggers the event and calls all registered listeners.
- The arguments can be passed to the listener function.
-
removeListener(event, listener):- Removes a specific listener for an event.
-
once(event, listener):- Registers a listener that will be called only once. After being called, it is automatically removed.
- Example:
eventEmitter.once('oneTime', () => { console.log('This will run only once.'); }); eventEmitter.emit('oneTime'); // Output: This will run only once. eventEmitter.emit('oneTime'); // No output
const http = require('http');
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('requestReceived', (req) => {
console.log(`Request received for URL: ${req.url}`);
});
const server = http.createServer((req, res) => {
eventEmitter.emit('requestReceived', req);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});In this example:
- Every time a request is made to the server, the
requestReceivedevent is emitted. - The listener logs the request URL to the console.
-
Logging:
- Logging system events or errors.
-
Streams:
- File or network streams in Node.js emit events like
data,end, orerror.
- File or network streams in Node.js emit events like
-
Real-Time Communication:
- WebSockets or real-time apps can emit events when messages are sent/received.
-
Custom Notifications:
- Trigger custom actions in response to specific events.
In summary:
- Events in Node.js are signals indicating that something happened.
- EventEmitter is a class that enables managing events and listeners.
- By leveraging EventEmitter, Node.js applications can efficiently implement event-driven behavior for handling asynchronous operations and building real-time systems.
WebSockets are a full-duplex communication protocol that enables real-time, bidirectional communication between a client (usually a browser) and a server over a single persistent connection.
Unlike the traditional HTTP request-response model, where the client must continuously send requests to the server to receive updates (polling), WebSockets allow both the client and server to send and receive messages at any time without the need to establish a new connection.
-
Connection Establishment:
- The client sends an initial HTTP request with an "Upgrade" header to the server, requesting to switch to the WebSocket protocol.
- If the server supports WebSockets, it responds with a "101 Switching Protocols" status, upgrading the connection to WebSocket.
-
Persistent Connection:
- Once the connection is established, it remains open. Both the client and server can send messages independently without waiting for each other.
-
Message Exchange:
- Data is exchanged using lightweight frames. These frames can be either text or binary.
-
Real-Time Communication:
- Ideal for applications that require instantaneous updates, such as chat apps, online games, financial dashboards, etc.
-
Efficiency:
- WebSockets eliminate the need for repeated HTTP requests (polling), reducing latency and bandwidth usage.
-
Low Latency:
- Since the connection remains open, there is minimal delay in transmitting data.
-
Chat Applications:
- Enables real-time messaging without delay.
-
Online Gaming:
- Facilitates fast, real-time interactions between players.
-
Financial Market Data:
- Streams real-time stock prices and financial updates.
-
Collaborative Tools:
- Live document editing and collaborative whiteboards.
-
IoT Devices:
- Transmits data to and from IoT devices efficiently.
Using the ws library (a popular WebSocket library for Node.js):
-
Installing the WebSocket Library:
npm install ws
-
Simple WebSocket Server:
const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 8080 }); server.on('connection', (socket) => { console.log('Client connected'); // Send a message to the client socket.send('Welcome to the WebSocket server!'); // Listen for messages from the client socket.on('message', (message) => { console.log(`Received: ${message}`); }); // Handle disconnection socket.on('close', () => { console.log('Client disconnected'); }); }); console.log('WebSocket server is running on ws://localhost:8080');
-
Simple WebSocket Client (in the browser):
const socket = new WebSocket('ws://localhost:8080'); // Listen for messages from the server socket.onmessage = (event) => { console.log(`Server says: ${event.data}`); }; // Send a message to the server socket.send('Hello, server!');
-
Bi-Directional Communication:
Both client and server can send messages at any time. -
Low Overhead:
WebSockets have a smaller overhead compared to traditional HTTP requests, making them faster and more efficient. -
Persistent Connection:
A single connection is used throughout the session, reducing the time and resources needed to establish multiple connections.
-
Connection Management:
Handling connection drops and retries can be challenging. -
Scalability:
WebSocket servers may need additional infrastructure to manage thousands of concurrent connections. -
Security:
WebSockets need to be secured using thewss://protocol (WebSocket over TLS/SSL) to protect against attacks like eavesdropping or man-in-the-middle attacks.
WebSockets provide a powerful way to implement real-time communication between clients and servers. By enabling bi-directional, persistent connections, WebSockets are ideal for use cases like chat apps, gaming, financial dashboards, and more.
The os module in Node.js is a built-in module that provides an interface for interacting with the underlying operating system (OS). It enables us to access system information such as CPU details, memory usage, network interfaces, and more.
To use the os module, we simply import it:
const os = require('os');Here are some of the most useful methods of the os module:
- Purpose: Returns the CPU architecture of the system.
- Example:
console.log(`Architecture: ${os.arch()}`); // Example output: 'x64'
- Purpose: Returns the operating system platform.
- Example:
console.log(`Platform: ${os.platform()}`); // Example output: 'linux', 'darwin' (macOS), 'win32' (Windows)
- Purpose: Returns the name of the operating system.
- Example:
console.log(`OS Type: ${os.type()}`); // Example output: 'Linux', 'Windows_NT', or 'Darwin'
- Purpose: Returns the system uptime in seconds.
- Example:
console.log(`System Uptime: ${os.uptime()} seconds`);
- Purpose: Returns the total memory of the system in bytes.
- Example:
console.log(`Total Memory: ${os.totalmem() / (1024 * 1024)} MB`);
- Purpose: Returns the available free memory of the system in bytes.
- Example:
console.log(`Free Memory: ${os.freemem() / (1024 * 1024)} MB`);
-
Purpose: Returns an array of objects containing information about each CPU/core installed on the system.
-
Example:
console.log('CPU Info:', os.cpus());
- Each object in the array has properties like:
model– CPU modelspeed– CPU speed in MHztimes– Time spent in different CPU states (user, nice, sys, idle, irq)
- Each object in the array has properties like:
-
Purpose: Returns an object containing the network interfaces that are assigned to the system.
-
Example:
console.log('Network Interfaces:', os.networkInterfaces());
- The output is a list of network interfaces with information such as the IP address, MAC address, and family (IPv4 or IPv6).
- Purpose: Returns the hostname of the system.
- Example:
console.log(`Hostname: ${os.hostname()}`);
- Purpose: Returns the home directory of the current user.
- Example:
console.log(`Home Directory: ${os.homedir()}`);
- Purpose: Returns the default directory for temporary files.
- Example:
console.log(`Temporary Directory: ${os.tmpdir()}`);
- Purpose: Returns the operating system release.
- Example:
console.log(`OS Release: ${os.release()}`);
- Purpose: Provides operating system-specific constants for errors, signals, and process priorities.
- Example:
console.log('OS Constants:', os.constants);
Here’s a simple example that displays detailed system information:
const os = require('os');
console.log('💻System Information:');
console.log(`OS Type: ${os.type()}`);
console.log(`Platform: ${os.platform()}`);
console.log(`Architecture: ${os.arch()}`);
console.log(`Total Memory: ${os.totalmem() / (1024 * 1024)} MB`);
console.log(`Free Memory: ${os.freemem() / (1024 * 1024)} MB`);
console.log(`Uptime: ${os.uptime()} seconds`);
console.log(`Host Name: ${os.hostname()}`);
console.log(`Home Directory: ${os.homedir()}`);
console.log('CPU Info:', os.cpus());
/*
💻 System Information:
OS Type: Windows_NT
Platform: win32
Architecture: x64
Total Memory: 7932.69921875 MB
Free Memory: 1473.9765625 MB
Uptime: 146614.546 seconds
Host Name: DESKTOP-PK42N06
Home Directory: C:\Users\ASUS
CPU Info: [
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: {
user: 3735265,
nice: 0,
sys: 3380890,
idle: 42416531,
irq: 513812
}
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: {
user: 3418546,
nice: 0,
sys: 2252000,
idle: 43862093,
irq: 226359
}
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: {
user: 4744437,
nice: 0,
sys: 2828718,
idle: 41959484,
irq: 273140
}
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: {
user: 3949281,
nice: 0,
sys: 2269296,
idle: 43314062,
irq: 194703
}
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: {
user: 2027640,
nice: 0,
sys: 1166156,
idle: 46338843,
irq: 114687
}
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: { user: 1299890, nice: 0, sys: 653312, idle: 47579437, irq: 61406 }
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: { user: 1684421, nice: 0, sys: 904156, idle: 46944062, irq: 97875 }
},
{
model: 'Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz',
speed: 1190,
times: { user: 1300500, nice: 0, sys: 618828, idle: 47613312, irq: 60500 }
}
]
*/-
System Monitoring Tools:
Used to build tools that monitor system performance and status. -
Environment-Based Logic:
Determine the platform or OS type to run platform-specific code. -
Diagnostics and Logging:
Collect system information for logging or troubleshooting. -
Network Configuration:
Access network interface details for connectivity monitoring or configuration.
The os module in Node.js is a powerful utility for accessing and interacting with system information. Whether we are building system monitoring tools, logging utilities, or platform-specific scripts, the os module provides everything we need to understand the environment our Node.js application is running in.