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.