Node.js Interview Guide: Part 5

Node.js Interview Guide: Part 5

·

6 min read

Practical Questions and Code Examples

Welcome to the third part of our Node.js interview guide! In this post, we'll focus on practical questions you might encounter in a Node.js interview, along with specific code examples to illustrate key concepts.

Q21: How would you implement caching in a Node.js application?

A: Caching in Node.js can be implemented using in-memory caching (like node-cache) or distributed caching systems (like Redis). Here's an example using node-cache:

const NodeCache = require("node-cache");
const myCache = new NodeCache({ stdTTL: 100, checkperiod: 120 });

function getCachedData(key, fetchFunction) {
  const value = myCache.get(key);
  if (value) {
    return Promise.resolve(value);
  }

  return fetchFunction().then((result) => {
    myCache.set(key, result);
    return result;
  });
}

// Usage
function fetchUserFromDB(id) {
  // Simulating DB fetch
  return new Promise(resolve => setTimeout(() => resolve({ id, name: "John Doe" }), 1000));
}

getCachedData("user:1", () => fetchUserFromDB(1))
  .then(user => console.log(user));

This implementation caches the result of expensive operations, significantly reducing response times for subsequent requests.

Q22: Explain the concept of middleware chaining in Express.js with an example.

A: Middleware chaining in Express.js refers to the practice of using multiple middleware functions for a single route. Each middleware can modify the request or response and pass control to the next middleware using next(). Here's an example:

const express = require('express');
const app = express();

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
}

function authMiddleware(req, res, next) {
  if (req.headers.authorization === 'secret-token') {
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
}

app.use(loggingMiddleware);

app.get('/protected', authMiddleware, (req, res) => {
  res.send('Welcome to protected route!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

In this example, the /protected route uses both loggingMiddleware (applied globally) and authMiddleware (applied specifically to this route).

Q23: How would you handle file uploads in Node.js?

A: File uploads in Node.js can be handled using middleware like multer. Here's an example:

const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
  }
});

const upload = multer({ storage: storage });

app.post('/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded.');
  }
  res.send('File uploaded successfully!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

This setup allows for single file uploads to a specified directory, with custom filename generation.

Q24: How would you implement rate limiting in a Node.js API?

A: Rate limiting can be implemented using middleware like express-rate-limit. Here's an example:

const express = require('express');
const rateLimit = require("express-rate-limit");

const app = express();

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

// Apply to all requests
app.use("/api/", apiLimiter);

app.get('/api/data', (req, res) => {
  res.json({ message: "This is rate-limited data" });
});

app.listen(3000, () => console.log('Server running on port 3000'));

This implementation limits each IP to 100 requests every 15 minutes for all routes starting with /api/.

Q25: How would you handle database migrations in Node.js?

A: Database migrations in Node.js can be handled using tools like Knex.js. Here's an example:

// knexfile.js
module.exports = {
  development: {
    client: 'postgresql',
    connection: {
      database: 'my_db',
      user:     'username',
      password: 'password'
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  }
};

// migration file: 20210101000000_create_users_table.js
exports.up = function(knex) {
  return knex.schema.createTable('users', function(table) {
    table.increments('id');
    table.string('name').notNullable();
    table.string('email').notNullable().unique();
    table.timestamps(true, true);
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable('users');
};

// To run migrations
// $ knex migrate:latest

This setup allows for version-controlled database schema changes, making it easier to manage database structure over time.

Q26: Explain the concept of serverless architecture and how Node.js fits into it.

A: Serverless architecture is a cloud computing model where the cloud provider manages the infrastructure. Node.js is well-suited for serverless due to its event-driven nature and quick startup time. Here's an example using AWS Lambda:

// handler.js
module.exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: "Hello from serverless Node.js!",
        input: event,
      },
      null,
      2
    ),
  };
};

// serverless.yml
service: my-express-application
provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: us-east-1
functions:
  app:
    handler: handler.hello
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'

This setup allows you to run Node.js functions in response to HTTP requests without managing servers.

Q27: How would you implement server-side rendering with Node.js?

A: Server-side rendering (SSR) in Node.js can be implemented using frameworks like Next.js for React. Here's a basic example:

// pages/index.js
import fetch from 'isomorphic-unfetch';

const Home = ({ stars }) => (
  <div>
    Next.js has {stars} ⭐️
  </div>
);

Home.getInitialProps = async () => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
};

export default Home;

// To run:
// $ next build
// $ next start

This example fetches data on the server before rendering the React component, improving initial load time and SEO.

Q28: How would you implement real-time updates in a Node.js application?

A: Real-time updates can be implemented using WebSockets via libraries like Socket.io. Here's an example:

const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a 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');
});

This setup allows for real-time, bidirectional communication between the server and connected clients.

Q29: How would you implement internationalization (i18n) in a Node.js application?

A: Internationalization in Node.js can be implemented using libraries like i18n. Here's an example:

const express = require('express');
const i18n = require('i18n');

const app = express();

i18n.configure({
  locales: ['en', 'fr'],
  directory: __dirname + '/locales',
  defaultLocale: 'en',
  cookie: 'lang',
});

app.use(i18n.init);

app.get('/', (req, res) => {
  res.send(res.__('Hello'));
});

app.listen(3000, () => console.log('Server running on port 3000'));

// locales/en.json
{
  "Hello": "Hello"
}

// locales/fr.json
{
  "Hello": "Bonjour"
}

This setup allows for easy translation of text based on the user's preferred language.

Q30: How would you implement graceful shutdown in a Node.js application?

A: Graceful shutdown involves handling termination signals, closing server connections, finishing ongoing requests, and cleaning up resources. Here's an example:

const express = require('express');
const app = express();

const server = app.listen(3000, () => console.log('Server running on port 3000'));

process.on('SIGTERM', () => {
  console.info('SIGTERM signal received.');
  console.log('Closing http server.');
  server.close(() => {
    console.log('Http server closed.');
    // Close database connections, etc.
    process.exit(0);
  });
});

// Simulate a long request
app.get('/long-request', (req, res) => {
  setTimeout(() => {
    res.send('Long request finished');
  }, 5000);
});

This implementation ensures that the server finishes processing ongoing requests before shutting down, preventing data loss or hanging connections.

These practical examples cover a range of important Node.js concepts that you might encounter in an interview or real-world development. Remember to not just memorize the code, but understand the underlying principles and use cases for each technique.