Skip to main content

Command Palette

Search for a command to run...

Node Interview - Intermediate

Updated
View as Markdown
  1. How do you debug a Node.js application?
    Debugging a Node.js application can be done in several ways, depending on your environment, tools, and the complexity of the application.
    i) Use console.log() Statements

     console.log('User data:', user);
    

    ii) Use the Built-In Debugger
    Node.js has a built-in debugger you can run with

     node inspect app.js
     /*
     c – Continue execution
     n – Step to next line
     s – Step into a function
     o – Step out of a function
     repl – Enter interactive mode
     */
    

    To set a breakpoint in code

     debugger;
    

    iii) Debug with Chrome DevTools
    Start your app with the --inspect flag,

     node --inspect app.js
    

    Open Chrome and go to chrome://inspect
    Click “Open dedicated DevTools for Node”
    You can now set breakpoints, step through code, inspect variables, and more.
    iv) Use Logging Libraries
    Use structured logging tools for better insights: winston, pino, debug

    v) Unit Testing + Debugging

    Write tests using: mocha, jest, ava

    Run tests in watch mode and debug failing ones.
    vi) Monitor with External Tools

    Use tools like,

    PM2 (for production monitoring/debugging)

    New Relic / Datadog (for profiling and performance debugging)

  2. How do you handle errors in Node.js?
    Handling errors in Node.js is critical for building robust, maintainable applications. Node.js provides several mechanisms for error handling, depending on whether the code is synchronous, asynchronous, uses promises, or streams.
    i) General Principles
    a) Always handle errors explicitly.

    b) Use consistent patterns across your application.

    c) Avoid crashing the app for recoverable errors.

    d) Use centralized error handling in web apps (like Express).
    ii) Synchronous Code
    Use try...catch blocks

     try {
       const data = fs.readFileSync('file.txt');
       console.log(data);
     } catch (err) {
       console.error('Error reading file:', err);
     }
    

    iii) Asynchronous Callbacks

    Node uses the error-first callback pattern (err as the first parameter)

     fs.readFile('file.txt', (err, data) => {
       if (err) {
         return console.error('Error reading file:', err);
       }
       console.log(data);
     });
    

    iv) Promises & Async/Await

    Wrap your async code in try...catch

     async function readFile() {
       try {
         const data = await fs.promises.readFile('file.txt', 'utf-8');
         console.log(data);
       } catch (err) {
         console.error('Error:', err.message);
       }
     }
    

    v) Express Error Handling

     // Error-handling middleware
     app.use((err, req, res, next) => {
       console.error(err.stack);
       res.status(500).json({ error: 'Something went wrong!' });
     });
    
     // Example route with error
     app.get('/', (req, res, next) => {
       try {
         throw new Error('Oops!');
       } catch (err) {
         next(err);
       }
     });
    

    vi) Global Error Handlers
    Use as a last resort—don’t rely on them for control flow.

     process.on('uncaughtException', err => {
       console.error('Uncaught Exception:', err);
       process.exit(1);
     });
    
     process.on('unhandledRejection', err => {
       console.error('Unhandled Rejection:', err);
       process.exit(1);
     });
    
  3. How do you handle file uploads in a Node.js application?
    Handling file uploads in a Node.js application typically involves using middleware to parse multipart/form-data, the encoding used by browsers for file uploads. The most common and robust solution is the multer middleware when working with Express.js.
    i) Install Required Packages

     npm install express multer
    

    ii) Set Up multer Middleware

     const express = require('express');
     const multer = require('multer');
     const path = require('path');
    
     const app = express();
    
     // Configure storage
     const storage = multer.diskStorage({
       destination: function (req, file, cb) {
         cb(null, 'uploads/'); // folder to store uploaded files
       },
       filename: function (req, file, cb) {
         const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
         cb(null, uniqueSuffix + path.extname(file.originalname)); // preserve original extension
       }
     });
    
     // Create the upload middleware
     const upload = multer({ storage: storage });
    
     // Route to handle upload
     app.post('/upload', upload.single('myFile'), (req, res) => {
       console.log(req.file); // file info
       res.send('File uploaded successfully!');
     });
    
     app.listen(3000, () => console.log('Server running on port 3000'));
    

    iii) Test with an HTML Form

     <form action="/upload" method="post" enctype="multipart/form-data">
       <input type="file" name="myFile" />
       <button type="submit">Upload</button>
     </form>
    

    iv) Storage Options in multer
    a) diskStorage - Store files locally on disk (as shown above)
    b) memoryStorage - Store files in memory as Buffer objects
    c) stream - Stream files to cloud storage or other APIs

  4. Explain the concept of callback functions in Node.js. Provide an example.
    In Node.js, a callback function is a function passed as an argument to another function and is executed after that function completes. This is a fundamental concept in asynchronous programming, which Node.js heavily relies on—especially for I/O operations like file reading, database access, or HTTP requests.
    i) Why Use Callbacks?
    JavaScript in Node.js is non-blocking and single-threaded, so callbacks allow operations to run in the background and execute code once they're done—without stopping the rest of the program.
    ii) Key Characteristics of Callbacks
    They are passed as arguments to functions.

    They are invoked after an asynchronous operation completes.

    Often follow the error-first pattern: (err, result) => {}
    iii) Reading a File Using a Callback

     const fs = require('fs');
    
     fs.readFile('example.txt', 'utf8', (err, data) => {
       if (err) {
         return console.error('Error reading file:', err);
       }
       console.log('File contents:', data);
     });
    

    iv) Custom Callback Example
    You can define your own functions that accept callbacks,

     function greetUser(name, callback) {
       console.log('Hello, ' + name);
       callback();
     }
    
     greetUser('Alice', () => {
       console.log('This is a callback function!');
     });
    

    v) Callback vs Promises (and Async/Await)
    Callbacks are foundational, but can lead to callback hell if nested too deeply

     doSomething(arg1, (err, result1) => {
       doSomethingElse(result1, (err, result2) => {
         andThenThis(result2, (err, result3) => {
           // ... hard to manage
         });
       });
     });
    
  5. What is the Node.js event loop? How does it work?
    The Node.js Event Loop is the core mechanism that enables non-blocking, asynchronous I/O operations—even though JavaScript is single-threaded. It allows Node.js to perform many operations (like reading files, querying databases, or making HTTP requests) without blocking the main thread.
    At its core, the event loop is a loop that monitors and processes asynchronous events in the background. It keeps the application running as long as there are callbacks to be executed.
    i) How the Event Loop Works (High-Level Steps)
    When Node.js runs your code, it uses the event loop to handle operations:

    a) Initialize: Executes your code (e.g., setting timers, reading files, registering callbacks).

    b) Poll: Waits for incoming events (e.g., I/O, timers) and places them in the callback queue.

    c) Callback Execution: Executes the callback associated with each event.

    d) Repeat: Continues this process until no more work remains.
    ii) Event Loop Phases (Simplified Overview)
    The event loop has several phases that run in order,
    a) Timers - Executes setTimeout() and setInterval() callbacks
    b) Pending Callbacks - Executes I/O callbacks deferred to the next loop
    c) Idle / Prepare - Internal operations
    d) Poll - Waits for new I/O events; executes I/O callbacks
    e) Check - Executes setImmediate() callbacks
    f) Close Callbacks - Executes callbacks like socket.on('close', ...)
    iii) Example to Illustrate Timing

     setTimeout(() => console.log('timeout'), 0);
     setImmediate(() => console.log('immediate'));
     process.nextTick(() => console.log('nextTick'));
     console.log('sync');
     // console.log('sync') runs first (synchronous).
     // process.nextTick() runs before the next phase of the event loop.
     // setTimeout(..., 0) and setImmediate() run in different phases (timers vs check).
    

    iv) Key Benefits
    a) Non-blocking I/O: Efficiently handles thousands of connections.

    b) Lightweight: Fewer threads mean lower overhead.

    c) Great for I/O-bound apps: APIs, chat servers, streaming apps, etc.
    v) Limitations

    a) CPU-bound operations block the event loop.

    b) Heavy computation should be offloaded to worker threads or external services.

  6. How do you read and write files in Node.js?
    Reading and writing files in Node.js is done using the built-in fs (File System) module. You can perform file operations both synchronously (blocking) and asynchronously (non-blocking).
    Use async (non-blocking) for most apps—especially servers and APIs.
    Use sync (blocking) for simple CLI tools, config loading, or during app startup.
    Best Practises
    a) Always handle errors using try...catch or error-first callbacks.

    b) Use the async versions to avoid blocking the event loop.

    c) Use path.join() to construct file paths reliably across platforms.

    d) Use the fs.promises API for cleaner async/await usage.

    Synchronous vs Asynchronous Operations
    a) Synchronous (Blocking)

    Methods end in Sync, e.g., readFileSync, writeFileSync.

    Blocking: halts the entire program until the operation finishes.

    Easier for scripting or startup tasks.
    b) Asynchronous (Non-blocking)

    Do not block the event loop.

    Use a callback or Promise to handle the result.
    i) Reading Files - Asynchronously (non-blocking, recommended)

     const fs = require('fs');
    
     fs.readFile('example.txt', 'utf8', (err, data) => {
       if (err) {
         return console.error('Error reading file:', err);
       }
       console.log('File content:', data);
     });
    

    ii) Reading Files - Synchronously (blocking)

     const data = fs.readFileSync('example.txt', 'utf8');
     console.log('File content:', data);
    

    iii) Reading Files - With Promises / async/await

     const fs = require('fs').promises;
    
     async function readFile() {
       try {
         const data = await fs.readFile('example.txt', 'utf8');
         console.log('File content:', data);
       } catch (err) {
         console.error('Error:', err);
       }
     }
    
     readFile();
    

    iv) Writing Files - Asynchronously (non-blocking, recommended)

     fs.writeFile('output.txt', 'Hello, world!', err => {
       if (err) return console.error('Error writing file:', err);
       console.log('File written successfully!');
     });
    

    v) Writing Files - Synchronously (blocking)

     fs.writeFileSync('output.txt', 'Hello, world!');
     console.log('File written successfully!');
    

    vi) Reading Files - With Promises / async/await

     const fs = require('fs').promises;
    
     async function writeFile() {
       try {
         await fs.writeFile('output.txt', 'Hello, world!');
         console.log('File written successfully!');
       } catch (err) {
         console.error('Error writing file:', err);
       }
     }
    
     writeFile();
    
  7. What is Express.js? How does it simplify building web applications with Node.js?
    Express.js is a minimal and flexible web framework for Node.js that provides powerful features to build web and mobile applications. It simplifies the process of creating server-side logic by abstracting much of the boilerplate code involved in handling HTTP requests, routing, middleware, and more.
    Without Express, writing a web server in Node.js requires manually handling HTTP methods, headers, and routing logic using the http module. Express makes this much simpler and cleaner.
    i) Key Features
    a) Routing - Easily map URLs to handlers (GET, POST, etc.)
    b) Middleware - Process requests (authentication, logging, body parsing, etc.)
    c) Template Engine Support - Supports Pug, EJS, Handlebars for rendering dynamic HTML
    d) Static Files - Serve static assets like CSS, JS, images
    e) Error Handling - Centralized and structured error handling
    f) REST API Support - Ideal for building RESTful APIs
    ii) Basic Example

     // Install Express
     npm install express
    
     // Simple Server
     const express = require('express');
     const app = express();
    
     app.get('/', (req, res) => {
       res.send('Hello, World!');
     });
    
     app.listen(3000, () => {
       console.log('Server listening on port 3000');
     });
    

    iii) Routing Example

     app.get('/about', (req, res) => {
       res.send('About Page');
     });
    
     app.post('/submit', (req, res) => {
       res.send('Form Submitted');
     });
    

    iv) Middleware Example

     app.use(express.json()); // Parses JSON request bodies
    
     app.use((req, res, next) => {
       console.log(`${req.method} ${req.url}`);
       next(); // Pass control to the next handler
     });
    

    v) Error Handling Middleware

     app.use((err, req, res, next) => {
       console.error(err.stack);
       res.status(500).send('Something went wrong!');
     });
    

    vi) Benefits of Express.js

    a) Great community and ecosystem

    b) Easy learning curve for JavaScript/Node developers

    c) Integrates well with databases and other Node.js libraries

    d) Foundation for many popular frameworks (like NestJS, Sails)

  8. How can you validate the input data in Node js Application?
    In a Node.js application, validating input data is crucial for security, data integrity, and preventing bugs or injection attacks (like SQL or XSS). There are several ways to handle validation depending on your stack and needs.
    i) Using a Validation Library (Recommended)

     // Popular Choice: Joi
     npm install joi
    
     // example
     const Joi = require('joi');
    
     // Define schema
     const schema = Joi.object({
       username: Joi.string().alphanum().min(3).max(30).required(),
       age: Joi.number().integer().min(18).required(),
       email: Joi.string().email().required()
     });
    
     // Validate data
     const result = schema.validate({
       username: 'JohnDoe',
       age: 25,
       email: 'john@example.com'
     });
    
     if (result.error) {
       console.error('Validation error:', result.error.details);
     } else {
       console.log('Valid input!');
     }
    

    ii) With Express Middleware
    You can integrate Joi with Express to validate request bodies

     const express = require('express');
     const Joi = require('joi');
     const app = express();
    
     app.use(express.json());
    
     const userSchema = Joi.object({
       name: Joi.string().required(),
       email: Joi.string().email().required()
     });
    
     app.post('/user', (req, res) => {
       const { error } = userSchema.validate(req.body);
       if (error) {
         return res.status(400).json({ message: error.details[0].message });
       }
       res.send('User data is valid');
     });
    

    iii) Using express-validator

     // install express-validator
     npm install express-validator
    
     // example
     const { body, validationResult } = require('express-validator');
    
     app.post('/user',
       body('email').isEmail(),
       body('password').isLength({ min: 6 }),
       (req, res) => {
         const errors = validationResult(req);
         if (!errors.isEmpty()) {
           return res.status(400).json({ errors: errors.array() });
         }
         res.send('Input is valid');
       });
    

    iv) Manual Validation (Not Recommended for Complex Logic)

     if (!req.body.name || typeof req.body.name !== 'string') {
       return res.status(400).send('Name is required and must be a string');
     }
    

    v) Best Practices

    a) Validate all user inputs: body, query, params, headers.

    b) Use a schema-based library (like Joi) for maintainability.

    c) Always return clear error messages to help with debugging (but avoid leaking sensitive info).

    d) Sanitize inputs if needed (express-validator offers sanitizers too).

  9. What is authentication? How is it implemented in Node.js applications?

    Authentication is the process of verifying the identity of a user or system. In a Node.js application, it's used to confirm that a user is who they claim to be—usually by checking credentials like a username/password or a token.
    i) Types of Authentication
    a) Session-Based Authentication (typically with cookies)

    b) Token-Based Authentication (e.g. JSON Web Tokens – JWT)

    c) OAuth / Third-party login (e.g. Google, Facebook)

    d) API Key Authentication

    ii) Common Authentication Implementation in Node.js
    a) Password hashing - bcrypt or argon2
    b) JWT tokens - jsonwebtoken
    c) Middleware - passport or custom
    d) Sessions - express-session
    iii) JWT Authentication – Common Approach
    Install Dependencies

     npm install express jsonwebtoken bcryptjs
    

    User Login Flow

     const express = require('express');
     const jwt = require('jsonwebtoken');
     const bcrypt = require('bcryptjs');
     const app = express();
    
     app.use(express.json());
    
     const users = [{ id: 1, username: 'admin', password: '$2a$10$...' }]; // hashed passwords
    
     app.post('/login', async (req, res) => {
       const { username, password } = req.body;
    
       const user = users.find(u => u.username === username);
       if (!user) return res.status(400).send('User not found');
    
       const valid = await bcrypt.compare(password, user.password);
       if (!valid) return res.status(401).send('Invalid password');
    
       const token = jwt.sign({ id: user.id }, 'secretKey', { expiresIn: '1h' });
       res.json({ token });
     });
    

    Protect Routes with Middleware

     function authenticateToken(req, res, next) {
       const authHeader = req.headers['authorization'];
       const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>
    
       if (!token) return res.sendStatus(401);
    
       jwt.verify(token, 'secretKey', (err, user) => {
         if (err) return res.sendStatus(403); // Invalid token
         req.user = user;
         next();
       });
     }
    
     // Protected route
     app.get('/dashboard', authenticateToken, (req, res) => {
       res.send('Protected dashboard content');
     });
    

    Best Practices -

    a) Hash passwords before storing (never store plain text).

    b) Store secrets securely (e.g. in .env files).

    c) Use HTTPS to protect token transmission.

    d) Implement token expiration and refresh where appropriate.

    e) Validate input data before authenticating.
    iv) Session-Based Auth (with express-session)

     npm install express-session
    
     const session = require('express-session');
    
     app.use(session({
       secret: 'yourSecret',
       resave: false,
       saveUninitialized: true,
       cookie: { secure: false } // use true with HTTPS
     }));
    

    Sessions store user identity on the server (e.g., in memory, Redis), and send a cookie to the browser.

  10. How do you handle CORS (Cross-Origin Resource Sharing) in a Node.js application?
    Handling CORS (Cross-Origin Resource Sharing) in a Node.js application is essential when your front-end and back-end are hosted on different origins (domains, ports, or protocols). CORS is a security feature enforced by browsers that restricts web pages from making requests to a different origin than the one that served the web page.
    CORS allows a server to specify who can access its resources and which methods are allowed. If not handled correctly, the browser will block cross-origin requests from the client.
    i) Install the cors package

    npm install cors
    

    ii) Basic Usage with Express

    const express = require('express');
    const cors = require('cors');
    
    const app = express();
    
    app.use(cors()); // Enable CORS for all origins
    
    app.get('/data', (req, res) => {
      res.json({ message: 'This is CORS-enabled for all origins!' });
    });
    
    app.listen(3000, () => console.log('Server running on port 3000'));
    

    iii) Restrict CORS to Specific Origin

    const corsOptions = {
      origin: 'https://example.com', // only allow this domain
      methods: ['GET', 'POST'],
      allowedHeaders: ['Content-Type', 'Authorization']
    };
    
    app.use(cors(corsOptions));
    

    iv) Enable CORS for a Single Route

    app.get('/public', cors(), (req, res) => {
      res.json({ message: 'CORS-enabled route' });
    });
    

    v) CORS with Credentials (cookies, auth headers)

    const corsOptions = {
      origin: 'http://localhost:3000',
      credentials: true
    };
    
    app.use(cors(corsOptions));
    

    Ensure your client-side request also includes,

    fetch('http://localhost:5000/api', {
      credentials: 'include'
    });
    

    vi) Best Practices

    a) Use cors() middleware for ease and flexibility.

    b) Be specific in production: avoid * for Access-Control-Allow-Origin.

    c) Enable credentials only when needed and configure origin accordingly.

    d) Be cautious with sensitive routes or APIs.

  11. Explain the concept of WebSockets and their Use in Node.js Applications
    WebSockets are a communication protocol that provides full-duplex, bidirectional communication between the client (e.g., browser) and server over a single, long-lived TCP connection.

    Unlike HTTP (which is request-response-based), WebSockets allow real-time communication without repeatedly reestablishing connections or polling.
    Node.js is a great platform for WebSockets due to its non-blocking, event-driven nature.
    Common Libraries -

    a) ws – lightweight WebSocket implementation.
    b) socket.io – higher-level library, abstracts over WebSocket with fallback support.
    i) Key Features of WebSockets

    a) Persistent connection

    b) Low latency

    c) Real-time updates

    d) Uses ws:// or wss:// (secure WebSocket)
    ii) When to Use WebSockets

    a) Real-time chat apps

    b) Live notifications

    c) Real-time games

    d) Collaborative tools (e.g., Google Docs)

    e) Live stock/price updates
    iii) Using ws (WebSocket)
    a) Installation

    npm install ws
    

    b) Server (node js)

    const WebSocket = require('ws');
    
    const wss = new WebSocket.Server({ port: 8080 });
    
    wss.on('connection', ws => {
      console.log('Client connected');
    
      ws.on('message', message => {
        console.log('Received:', message);
        ws.send(`Server echo: ${message}`);
      });
    
      ws.on('close', () => console.log('Client disconnected'));
    });
    

    c) Client (Browser)

    <script>
      const socket = new WebSocket('ws://localhost:8080');
    
      socket.onopen = () => {
        socket.send('Hello Server!');
      };
    
      socket.onmessage = event => {
        console.log('From server:', event.data);
      };
    </script>
    

    iv) Using socket.io (More Feature-Rich)
    a) Installation

    npm install socket.io
    

    b) Server

    const http = require('http').createServer();
    const io = require('socket.io')(http);
    
    io.on('connection', socket => {
      console.log('User connected');
    
      socket.on('chat message', msg => {
        io.emit('chat message', msg);
      });
    
      socket.on('disconnect', () => console.log('User disconnected'));
    });
    
    http.listen(3000, () => console.log('Listening on *:3000'));
    

    c) Client

    <script src="/socket.io/socket.io.js"></script>
    <script>
      const socket = io('http://localhost:3000');
    
      socket.on('chat message', msg => {
        console.log('Message:', msg);
      });
    
      socket.emit('chat message', 'Hello from client');
    </script>
    
  12. What is Event Emitter in Node.js?
    In Node.js, the EventEmitter is a core module that enables an event-driven architecture. It allows objects (like servers, streams, etc.) to emit named events and for other parts of the code to listen and react to those events.

    This is fundamental to how Node.js handles asynchronous operations efficiently.
    i) How It Works
    The EventEmitter class is part of the built-in events module.

    const EventEmitter = require('events');
    const emitter = new EventEmitter();
    

    ii) Basic Example: Emitting and Listening

    // Register a listener
    emitter.on('greet', (name) => {
      console.log(`Hello, ${name}!`);
    });
    
    // Emit the event
    emitter.emit('greet', 'Alice');
    

    iii) Key Methods of EventEmitter
    a) on(event, listener) - Registers a listener (can run multiple times)
    b) once(event, listener) - Runs the listener only once
    c) emit(event, [args]) - Triggers the event, optionally passing data
    d) removeListener() - Removes a specific listener
    e) removeAllListeners() - Removes all listeners for an event
    f) listenerCount(event) - Returns number of listeners for an event
    iv) Common Use Cases in Node.js
    a) HTTP servers (req.on('data'), res.on('finish'))

    b) Streams (readable.on('data'))

    c) File system watcher

    d) Custom app-level events

  13. What are streams and what are types of streams available in Node.js?
    In Node.js, streams are powerful abstract interfaces for working with streaming data—data that isn’t available all at once, but instead arrives in chunks over time (e.g., reading a large file, streaming audio/video, receiving HTTP responses).

    Using streams helps improve performance, memory usage, and scalability in data-heavy applications.
    i) What Are Streams?

    A stream is like a data pipeline where you can read from a source or write to a destination piece by piece, instead of loading everything into memory at once.
    ii) Four Main Types of Streams
    a) Readable - Can be read from
    fs.createReadStream(), HTTP request
    b) Writable - Can be written to
    fs.createWriteStream(), HTTP response
    c) Duplex - Can be read from and written to
    TCP socket (net.Socket)
    d) Transform - Duplex stream that can modify data as it passes through
    zlib.createGzip()
    iii) Example: Readable Stream

    const fs = require('fs');
    const readable = fs.createReadStream('large-file.txt');
    
    readable.on('data', chunk => {
      console.log('Received chunk:', chunk.length);
    });
    
    readable.on('end', () => {
      console.log('Finished reading file.');
    });
    

    iv) Example: Writable Stream

    const fs = require('fs');
    const writable = fs.createWriteStream('output.txt');
    
    writable.write('Hello, ');
    writable.write('world!');
    writable.end(); // Signals end of writing
    

    v) Example: Piping Readable → Writable

    const fs = require('fs');
    
    const readStream = fs.createReadStream('input.txt');
    const writeStream = fs.createWriteStream('output.txt');
    
    readStream.pipe(writeStream); // Transfers content
    

    vi) Example: Transform Stream (e.g., Compression)

    const fs = require('fs');
    const zlib = require('zlib');
    
    const gzip = zlib.createGzip();
    const input = fs.createReadStream('file.txt');
    const output = fs.createWriteStream('file.txt.gz');
    
    input.pipe(gzip).pipe(output);
    

    vii) Benefits of Streams

    a) Memory efficient: Handle large files/data in small chunks.

    b) Faster: Start processing before the full data is available.

    c) Composable: Chain multiple streams together using .pipe().

  14. What is Piping in Node?
    Piping in Node.js is a mechanism used with streams to connect the output of one stream directly into the input of another. This allows you to chain operations and pass data efficiently from one stream to another without manually handling data events.
    Piping lets you transfer data from a readable stream to a writable stream automatically and efficiently.
    Piping is a method to connect streams and transfer data.

    Use .pipe() to link a readable stream to a writable (or transform) stream.

    It’s commonly used for file I/O, compression, HTTP responses, and data processing.
    i) Example: Reading from a File and Writing to Another

    const fs = require('fs');
    
    const readStream = fs.createReadStream('input.txt');
    const writeStream = fs.createWriteStream('output.txt');
    
    // Pipe the read stream into the write stream
    readStream.pipe(writeStream);
    

    This reads input.txt in chunks and writes them to output.txt without loading the entire file into memory.
    ii) Chaining Multiple Pipes
    You can pipe through multiple transform streams to modify data.

    const fs = require('fs');
    const zlib = require('zlib');
    
    const gzip = zlib.createGzip();
    const read = fs.createReadStream('file.txt');
    const write = fs.createWriteStream('file.txt.gz');
    
    read.pipe(gzip).pipe(write); // Compresses the file
    

    iii) Benefits of Piping

    a) Memory Efficient – handles data in chunks.

    b) Readable & Writable – stream data from one place to another.

    c) Simplifies code – avoids manual .on('data') and .write() handling.

    d) Easy to compose – chain multiple streams together (e.g., transform, compress, log).

  15. What is the difference between setImmediate() and setTimeout()?
    The difference between setImmediate() and setTimeout() in Node.js lies in when they execute their callbacks in the event loop. Although both schedule asynchronous operations, their timing behavior is slightly different.
    i) setTimeout(callback, 0)
    Executes the callback after a minimum delay of 0 milliseconds.

    The callback is scheduled in the timers phase of the event loop.

    Actual execution depends on the system timer granularity and current event loop load.

    setTimeout(() => {
      console.log('setTimeout');
    }, 0);
    

    ii) setImmediate(callback)
    Executes the callback immediately after the I/O events are processed.

    The callback is scheduled in the check phase of the event loop.

    Typically runs before setTimeout(..., 0) if both are called in the same phase.

    setImmediate(() => {
      console.log('setImmediate');
    });
    
  16. What is Swagger and How it helps to maintain the ReSTful API in Node js?
    Swagger is a toolset and specification for describing, documenting, testing, and visualizing RESTful APIs. It is now part of the OpenAPI Specification (OAS).

    i) In Node.js, Swagger helps developers

    a) Define API structure in a standardized way.

    b) Automatically generate interactive API documentation.

    c) Validate request/response schemas.

    d) Improve team collaboration and client integration.
    ii) Key Benefits of Swagger in Node.js
    i) API documentation - Generates live, interactive docs (like Swagger UI)
    ii) API validation - Validates input/output based on schema
    iii) Client SDK generation - Auto-generates client SDKs in various languages
    iv) Testing support - Allows testing APIs directly from the docs
    v) Improves communication - Easy for frontend/backend teams or 3rd parties to understand APIs
    iii) Install Required Packages

    npm install swagger-jsdoc swagger-ui-express
    

    iv) Set Up Swagger in app.js or server.js

    const express = require('express');
    const swaggerUi = require('swagger-ui-express');
    const swaggerJsdoc = require('swagger-jsdoc');
    
    const app = express();
    
    const swaggerDefinition = {
      openapi: '3.0.0',
      info: {
        title: 'Example API',
        version: '1.0.0',
        description: 'A simple Express API with Swagger',
      },
      servers: [
        {
          url: 'http://localhost:3000',
        },
      ],
    };
    
    const options = {
      swaggerDefinition,
      apis: ['./routes/*.js'], // Path to the API docs
    };
    
    const swaggerSpec = swaggerJsdoc(options);
    
    app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
    

    v) Document Your Routes with JSDoc-style Comments

    /**
     * @swagger
     * /hello:
     *   get:
     *     summary: Returns a greeting
     *     responses:
     *       200:
     *         description: A simple message
     */
    app.get('/hello', (req, res) => {
      res.send('Hello Swagger!');
    });
    

    vi) Access Swagger UI

    Navigate to, http://localhost:3000/api-docs You’ll see a live, interactive documentation page for your API.

  17. What is Redis and How it helps to cache the API resposne in Node js?
    Redis (Remote Dictionary Server) is a fast, in-memory key-value data store often used as:

    Cache, Database, Message broker

    In Node.js, Redis is commonly used to cache API responses to improve performance and reduce load on databases or third-party APIs.
    i) Why Use Redis for Caching?
    a) Extremely Fast - Keeps data in memory (not disk) for ultra-low latency
    b) Avoids Repetition - Prevents repeated database or API calls
    c) Reduces Latency - Speeds up response time for frequently requested data
    d) TTL Support - Auto-expire cached data using Time-To-Live
    e) Scalable - Works well with distributed systems
    ii) How to Use Redis for Caching in a Node.js API
    a) Install Redis and Redis Client

    Install Redis on your system (or use a service like Redis Cloud).

    Then install the Node.js client,

    npm install redis
    

    b) Connect to Redis

    const redis = require('redis');
    const client = redis.createClient();
    client.connect().then(() => console.log('Redis connected'));
    

    c) Cache API Response Example

    const express = require('express');
    const app = express();
    
    const axios = require('axios');
    
    app.get('/weather/:city', async (req, res) => {
      const { city } = req.params;
    
      const cacheKey = `weather:${city}`;
    
      // 1. Check cache
      const cached = await client.get(cacheKey);
      if (cached) {
        return res.json({ source: 'cache', data: JSON.parse(cached) });
      }
    
      // 2. If not cached, fetch from API
      const apiResponse = await axios.get(`https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=${city}`);
    
      // 3. Store response in Redis for 1 hour
      await client.setEx(cacheKey, 3600, JSON.stringify(apiResponse.data));
    
      res.json({ source: 'API', data: apiResponse.data });
    });
    

    iii) Common Use Cases of Redis Caching in Node.js
    a) API rate limiting

    b) Session storage

    c) Frequently accessed DB queries

    d) Third-party API responses (e.g., weather, stock prices)

    e) Full-page or partial-page caching

  18. How do you manage dependencies in a Node.js project to prevent compatibility issues or security vulnerabilities?
    Managing dependencies in a Node.js project is critical for stability, security, and maintainability. Here's how you can do it effectively,
    i) Use package.json and package-lock.json
    a) package.json defines project dependencies, versions, scripts, and metadata.

    b) package-lock.json locks the exact versions of installed packages to ensure consistent installs across environments.
    Best Practices -

    a) Never delete package-lock.json

    b) Commit it to version control (Git)
    ii) Use Semantic Versioning Wisely

    // In package.json
    "express": "^4.17.1"
    /*
    ^ allows minor and patch upgrades
    ~ allows only patch upgrades
    No prefix = locked version
    */
    

    a) Be cautious with ^ and ~—unexpected updates may introduce breaking changes.

    b) Use npm install <pkg>@version to install specific versions if needed.
    iii) Audit for Security Vulnerabilities

    npm audit
    

    a) Scans dependencies for known vulnerabilities.

    b) Lists severity (Low, Moderate, High, Critical).

    npm audit fix
    

    iv) Regularly Update Dependencies
    Use tools like: npm outdated or npm-check-updates (ncu)

    npx npm-check-updates -u
    npm install
    

    v) Remove Unused or Obsolete Packages

    npm prune
    

    vi) Use a .npmrc File for Config

    // To lock down behavior
    save-exact=true
    // This will save packages without the ^ symbol, e.g., "express": "4.17.1".
    

    vii) Use CI/CD for Testing Compatibility

    a) Run npm ci in CI pipelines for clean, deterministic installs.

    b) Use automated tests to catch issues from dependency updates.
    viii) Consider Using Dependency Management Tools
    a) Dependabot (GitHub) – automatically creates PRs for outdated or vulnerable packages.

    b) Snyk – finds and fixes vulnerabilities.

    c) Renovate – manages version updates.

  19. Explain the OS module in Node.js?
    The os module in Node.js is a built-in module that provides a way to interact with the operating system. It allows you to retrieve system-related information such as the hostname, memory, CPU details, network interfaces, and more.

    You don’t need to install it — it's part of Node.js core.

    const os = require('os');
    

    Example

    const os = require('os');
    
    console.log('Hostname:', os.hostname()); // Returns the hostname of the OS
    console.log('Platform:', os.platform()); // Returns the platform (e.g., 'linux', 'win32')
    console.log('CPU Architecture:', os.arch()); // Returns CPU architecture (e.g., 'x64')
    console.log('Total Memory (MB):', os.totalmem() / 1024 / 1024); // Total system memory (in bytes)
    console.log('Free Memory (MB):', os.freemem() / 1024 / 1024); // Free system memory (in bytes)
    console.log('Uptime (minutes):', os.uptime() / 60); // System uptime in seconds
    console.log('User Info:', os.userInfo()); //     Info about the currently logged-in user
    

    Use Cases in Real Applications

    a) System monitoring dashboards

    b) Logging OS info in backend apps

    c) Performance tuning and diagnostics

    d) Load balancing based on CPU/memory

    e) Temp file storage (via os.tmpdir())

  20. What is a Node Inspector?

    Node Inspector is a debugging tool for Node.js applications that allows you to debug your code using Chrome DevTools or other browser-based interfaces. It provides a visual way to:

    a) Set breakpoints

    b) Step through code

    c) Inspect variables and call stack

    d) Monitor memory usage

    It connects to the V8 Inspector Protocol, which Node.js supports natively.
    i) Why Use Node Inspector?

    a) Visual Debugging - Easier than console.log debugging
    b) Step Execution - Pause, resume, step into/over functions
    c) Variable Inspection - See scope, values, closures in real time
    d) Live Code Editing - Modify code while paused (in Chrome DevTools)
    e) Heap & CPU Profiling - Useful for performance tuning
    ii) How to Use Node Inspector (Built-In Since Node.js v6.3+)
    a) Run your script with the --inspect flag

    node --inspect index.js
    

    b) Open Chrome and Go to

    chrome://inspect
    

    Then click “Open dedicated DevTools for Node”.

    You’ll now see Chrome DevTools connected to your Node.js app.
    c) Set Breakpoints and Debug

    Use the Sources tab to browse your code

    Set breakpoints by clicking line numbers

    Use the Console, Scope, and Call Stack panels for full control