Node.js Interview Questions (Free Preview)
Free sample of 16 from 45 questions available
Node.js Fundamentals
What is Node.js and how does it differ from traditional web servers like Apache or Nginx?
Node.js is a JavaScript runtime environment built on Chrome's V8 JavaScript engine that allows developers to execute JavaScript code server-side. It was created by Ryan Dahl in 2009 to enable JavaScript development outside of web browsers.
Key Characteristics of Node.js:
- Runtime Environment: Node.js is not a web server itself, but a platform for building server applications
- JavaScript Everywhere: Enables full-stack JavaScript development
- Event-Driven Architecture: Built around an event-driven, non-blocking I/O model
- Single-Threaded Event Loop: Uses one main thread with asynchronous operations
Differences from Traditional Web Servers:
| Aspect | Node.js | Apache/Nginx |
|---|---|---|
| Architecture | Application runtime and development platform | Web server software |
| Threading Model | Single-threaded event loop with async I/O | Multi-threaded (Apache) / Event-driven (Nginx) |
| Primary Use | Building web applications and APIs | Serving static content and reverse proxying |
| Language | JavaScript | Configuration-based |
| Request Handling | Non-blocking, asynchronous | Blocking (Apache) / Non-blocking (Nginx) |
Traditional web servers like Apache and Nginx are primarily designed to serve static content, handle HTTP requests, and act as reverse proxies. They require additional technologies (PHP, Python, Ruby) for dynamic content generation.
References:
↑ Back to topWhat are the key differences between the CommonJS and ES Modules module systems in Node.js?
Node.js supports two module systems: CommonJS (traditional) and ES Modules (modern standard).
CommonJS (require/module.exports)
// math.js - CommonJS export
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };
// app.js - CommonJS import
const { add, multiply } = require('./math');
const result = add(5, 3);
ES Modules (import/export)
// math.js - ES Module export
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Default export
export default function subtract(a, b) {
return a - b;
}
// app.js - ES Module import
import subtract, { add, multiply } from './math.js';
const result = add(5, 3);
Key Differences:
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require() / module.exports |
import / export |
| Loading | Synchronous | Asynchronous |
| Timing | Runtime | Compile-time |
| Tree Shaking | Not supported | Supported |
| Top-level await | Not supported | Supported |
| File Extension | .js (default) |
.mjs or .js with "type": "module" |
| Circular Dependencies | Handled dynamically | Static analysis |
Enabling ES Modules in Node.js:
- Using .mjs extension:
// file.mjs
import fs from 'fs';
- Using package.json:
{
"type": "module",
"name": "my-app"
}
Migration Considerations:
- Interoperability: Can import CommonJS from ES modules, but not vice versa directly
- Dynamic Imports: Use
import()for conditional loading in ES modules - File Extensions: Must include
.jsextension in ES module imports
References:
↑ Back to topAsynchronous Programming and Event Loop
What are Promises, and how do they improve the developer experience compared to callbacks?
Promises are objects representing the eventual completion or failure of an asynchronous operation. They provide a cleaner, more manageable way to handle asynchronous code compared to traditional callbacks.
Promise States:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: Operation completed successfully
- Rejected: Operation failed
Improvements over callbacks:
- Better Error Handling: Centralized error handling with
.catch() - Chaining: Sequential operations with
.then() - Readability: Flatter code structure
- Promise.all() and Promise.race(): Handle multiple async operations
// Callback approach (callback hell)
fs.readFile('file1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', (err, data2) => {
if (err) throw err;
fs.readFile('file3.txt', (err, data3) => {
if (err) throw err;
console.log('All files read');
});
});
});
// Promise approach
const readFilePromise = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
readFilePromise('file1.txt')
.then(data1 => readFilePromise('file2.txt'))
.then(data2 => readFilePromise('file3.txt'))
.then(data3 => console.log('All files read'))
.catch(err => console.error('Error:', err));
References:
↑ Back to topHow does async/await simplify asynchronous code? Provide an example scenario.
Async/await is syntactic sugar built on top of Promises that makes asynchronous code look and behave more like synchronous code. It significantly improves code readability and maintainability.
Key Benefits:
- Synchronous-like syntax: Write async code that reads like sync code
- Better error handling: Use try/catch blocks instead of
.catch() - Easier debugging: Stack traces are more meaningful
- Cleaner conditional logic: Avoid complex Promise chains
Example Scenario: User Registration Process
// Promise-based approach
function registerUser(userData) {
return validateUserData(userData)
.then(validatedData => {
return checkUserExists(validatedData.email);
})
.then(userExists => {
if (userExists) {
throw new Error('User already exists');
}
return hashPassword(userData.password);
})
.then(hashedPassword => {
return saveUserToDatabase({
...userData,
password: hashedPassword
});
})
.then(savedUser => {
return sendWelcomeEmail(savedUser.email);
})
.then(() => {
return { success: true, message: 'User registered successfully' };
})
.catch(error => {
return { success: false, error: error.message };
});
}
// Async/await approach
async function registerUser(userData) {
try {
const validatedData = await validateUserData(userData);
const userExists = await checkUserExists(validatedData.email);
if (userExists) {
throw new Error('User already exists');
}
const hashedPassword = await hashPassword(userData.password);
const savedUser = await saveUserToDatabase({
...userData,
password: hashedPassword
});
await sendWelcomeEmail(savedUser.email);
return { success: true, message: 'User registered successfully' };
} catch (error) {
return { success: false, error: error.message };
}
}
// Usage
async function handleRegistration() {
const userData = {
email: 'user@example.com',
password: 'secretPassword',
name: 'John Doe'
};
const result = await registerUser(userData);
console.log(result);
}
References:
↑ Back to topWhat is the event loop and how does it manage asynchronous operations?
The event loop is the core mechanism that enables Node.js to perform non-blocking I/O operations despite being single-threaded. It's responsible for executing code, collecting and processing events, and executing queued sub-tasks.
Event Loop Phases:
graph TD
A[Timer Phase] --> B[Pending Callbacks Phase]
B --> C[Idle, Prepare Phase]
C --> D[Poll Phase]
D --> E[Check Phase]
E --> F[Close Callbacks Phase]
F --> A
Phase Breakdown:
- Timer Phase: Executes callbacks scheduled by
setTimeout()andsetInterval() - Pending Callbacks Phase: Executes I/O callbacks deferred to the next loop iteration
- Idle, Prepare Phase: Internal use only
- Poll Phase: Fetches new I/O events; executes I/O related callbacks
- Check Phase: Executes
setImmediate()callbacks - Close Callbacks Phase: Executes close event callbacks (e.g.,
socket.on('close'))
How it manages async operations:
console.log('1: Start');
setTimeout(() => console.log('2: setTimeout'), 0);
setImmediate(() => console.log('3: setImmediate'));
process.nextTick(() => console.log('4: nextTick'));
Promise.resolve().then(() => console.log('5: Promise'));
console.log('6: End');
// Output order:
// 1: Start
// 6: End
// 4: nextTick
// 5: Promise
// 3: setImmediate
// 2: setTimeout
Priority Queue:
process.nextTick()(highest priority)- Promise callbacks (microtasks)
setImmediate()setTimeout()/setInterval()
References:
↑ Back to topNPM and Package Management
What is npm and why is it vital for Node.js development?
npm (Node Package Manager) is the default package manager for Node.js and the world's largest software registry. It serves multiple critical functions:
Core Functions:
- Package Installation: Downloads and installs JavaScript packages from the npm registry
- Dependency Management: Automatically handles package dependencies and their versions
- Script Runner: Executes custom scripts defined in package.json
- Version Control: Manages package versions and updates
- Publishing Platform: Allows developers to share their own packages
Why npm is Vital:
- Ecosystem Access: Provides access to over 1.3 million packages, enabling rapid development
- Dependency Resolution: Automatically resolves complex dependency trees
- Code Reusability: Eliminates the need to write common functionality from scratch
- Community Standards: Establishes consistent practices across the Node.js ecosystem
- Build Automation: Integrates with build tools and deployment pipelines
// Example: Installing and using a package
npm install axios
// In your code
const axios = require('axios');
const response = await axios.get('https://api.example.com/data');
References:
↑ Back to topWhat is the purpose of the package.json file?
The package.json file is the heart of any Node.js project, serving as a manifest that describes the project and its dependencies.
Core Purposes:
1. Project Metadata
{
"name": "my-awesome-project",
"version": "1.0.0",
"description": "A comprehensive Node.js application",
"author": "John Doe <john@example.com>",
"license": "MIT",
"keywords": ["nodejs", "express", "api"],
"homepage": "https://github.com/user/project#readme",
"repository": {
"type": "git",
"url": "https://github.com/user/project.git"
}
}
2. Dependency Management
{
"dependencies": {
"express": "^4.18.0",
"mongoose": "^6.0.0"
},
"devDependencies": {
"nodemon": "^2.0.0",
"jest": "^28.0.0",
"eslint": "^8.0.0"
}
}
3. Script Automation
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"build": "webpack --mode=production",
"lint": "eslint src/",
"deploy": "npm run build && npm run test && git push heroku main"
}
}
4. Entry Points
{
"main": "index.js",
"bin": {
"my-cli": "./bin/cli.js"
},
"exports": {
".": "./lib/index.js",
"./utils": "./lib/utils.js"
}
}
5. Configuration
{
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"os": ["linux", "darwin"],
"cpu": ["x64"],
"private": true,
"workspaces": ["packages/*"]
}
Key Benefits:
- Reproducible Builds: Ensures consistent environments
- Automation: Enables script execution via
npm run - Documentation: Serves as project documentation
- Publishing: Required for npm package publication
- Tooling Integration: Integrates with IDEs and build tools
References:
↑ Back to topCan you explain the difference between dependencies and devDependencies?
The distinction between dependencies and devDependencies is crucial for proper package management and application deployment:
Dependencies vs DevDependencies
Dependencies
Purpose: Packages required for the application to run in production
{
"dependencies": {
"express": "^4.18.0",
"mongoose": "^6.0.0",
"axios": "^1.0.0",
"lodash": "^4.17.0",
"dotenv": "^16.0.0"
}
}
Characteristics:
- Essential for application functionality
- Installed in production environments
- Included when others install your package
- Downloaded with
npm installby default
DevDependencies
Purpose: Packages needed only during development and testing
{
"devDependencies": {
"nodemon": "^2.0.0",
"jest": "^28.0.0",
"eslint": "^8.0.0",
"prettier": "^2.7.0",
"webpack": "^5.0.0",
"@types/node": "^18.0.0"
}
}
Characteristics:
- Used for development, testing, and build processes
- Not installed in production (with
npm ci --production) - Not included when others install your package as a dependency
- Installed only with
npm install(not withnpm install --production)
Installation Commands
# Install production dependency
npm install express --save
npm install express # --save is default
# Install development dependency
npm install jest --save-dev
npm install jest -D # shorthand
# Install without saving to package.json
npm install package-name --no-save
# Production installation (excludes devDependencies)
npm install --production
npm ci --production
Real-World Example
{
"name": "e-commerce-api",
"dependencies": {
"express": "^4.18.0", // Web framework - needed in production
"mongoose": "^6.0.0", // Database ORM - needed in production
"bcrypt": "^5.0.0", // Password hashing - needed in production
"jsonwebtoken": "^8.5.0", // JWT tokens - needed in production
"helmet": "^6.0.0" // Security middleware - needed in production
},
"devDependencies": {
"nodemon": "^2.0.0", // Development server - dev only
"jest": "^28.0.0", // Testing framework - dev only
"supertest": "^6.0.0", // API testing - dev only
"eslint": "^8.0.0", // Code linting - dev only
"prettier": "^2.7.0", // Code formatting - dev only
"@types/express": "^4.17.0" // TypeScript types - dev only
}
}
Other Dependency Types
PeerDependencies
{
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
}
- Required by the package but not automatically installed
- Must be installed by the consuming application
- Common in plugins and extensions
OptionalDependencies
{
"optionalDependencies": {
"fsevents": "^2.0.0" // Mac-specific file watching
}
}
- Not required for core functionality
- Installation failure doesn't break the process
- Often platform-specific packages
Best Practices:
- Accurate Classification: Ensure packages are in the correct category
- Bundle Size Optimization: Keep dependencies minimal for production
- Security: Regularly audit both dependency types
- Documentation: Comment on unusual dependency choices
// Example: Checking if in development mode
if (process.env.NODE_ENV === 'development') {
// Only load dev tools in development
const debug = require('debug');
debug('app:startup')('Starting in development mode');
}
Impact on Deployment:
# Development environment
npm install # Installs both dependencies and devDependencies
# Production environment
npm ci --production # Installs only dependencies
# or
NODE_ENV=production npm ci
References:
↑ Back to topExpress.js and Web Frameworks
What are middleware functions in Express and how are they used?
Middleware functions are functions that execute during the request-response cycle in Express.js. They have access to the request object (req), response object (res), and the next middleware function.
Types of Middleware:
1. Application-level middleware
// Executed for every request
app.use((req, res, next) => {
console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
next();
});
// Path-specific middleware
app.use('/api', (req, res, next) => {
console.log('API middleware');
next();
});
2. Router-level middleware
const router = express.Router();
router.use((req, res, next) => {
console.log('Router middleware');
next();
});
router.get('/users', (req, res) => {
res.json({ users: [] });
});
app.use('/api', router);
3. Built-in middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.static('public')); // Serve static files
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
4. Third-party middleware
const cors = require('cors');
const morgan = require('morgan');
app.use(cors()); // Enable CORS
app.use(morgan('combined')); // HTTP request logger
5. Error-handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
Middleware execution flow diagram:
Request → Middleware 1 → Middleware 2 → Route Handler → Response
↑ ↓ ↓ ↓
└─────── next() ──── next() ──── next() (optional)
Key principles:
- Always call
next()unless you're ending the request-response cycle - Order matters - middleware executes in the order it's defined
- Error-handling middleware must have 4 parameters
References:
↑ Back to topWhat is the role of error handling middleware in an Express application?
Error handling middleware in Express serves as a centralized mechanism for catching, processing, and responding to errors that occur during request processing.
Characteristics of Error Handling Middleware:
Error handling middleware functions are defined with four parameters: (err, req, res, next)
// Basic error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
});
});
Comprehensive Error Handling Implementation:
1. Custom Error Classes
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = AppError;
2. Async Error Handler Wrapper
// utils/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
3. Comprehensive Error Handling Middleware
// middleware/errorHandler.js
const AppError = require('../utils/AppError');
const handleCastErrorDB = (err) => {
const message = `Invalid ${err.path}: ${err.value}`;
return new AppError(message, 400);
};
const handleDuplicateFieldsDB = (err) => {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const message = `Duplicate field value: ${value}. Please use another value!`;
return new AppError(message, 400);
};
const handleValidationErrorDB = (err) => {
const errors = Object.values(err.errors).map(el => el.message);
const message = `Invalid input data. ${errors.join('. ')}`;
return new AppError(message, 400);
};
const handleJWTError = () =>
new AppError('Invalid token. Please log in again!', 401);
const handleJWTExpiredError = () =>
new AppError('Your token has expired! Please log in again.', 401);
const sendErrorDev = (err, req, res) => {
return res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
};
const sendErrorProd = (err, req, res) => {
// Operational, trusted error: send message to client
if (err.isOperational) {
return res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
// Programming or other unknown error: don't leak error details
console.error('ERROR 💥', err);
return res.status(500).json({
status: 'error',
message: 'Something went very wrong!'
});
};
module.exports = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
sendErrorDev(err, req, res);
} else {
let error = { ...err };
error.message = err.message;
if (error.name === 'CastError') error = handleCastErrorDB(error);
if (error.code === 11000) error = handleDuplicateFieldsDB(error);
if (error.name === 'ValidationError') error = handleValidationErrorDB(error);
if (error.name === 'JsonWebTokenError') error = handleJWTError();
if (error.name === 'TokenExpiredError') error = handleJWTExpiredError();
sendErrorProd(error, req, res);
}
};
4. Usage in Controllers
// controllers/userController.js
const User = require('../models/User');
const AppError = require('../utils/AppError');
const asyncHandler = require('../utils/asyncHandler');
const getUser = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('No user found with that ID', 404));
}
res.status(200).json({
status: 'success',
data: { user }
});
});
module.exports = { getUser };
5. Global Unhandled Error Catching
// server.js
const app = require('./src/app');
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
console.log('💥 UNCAUGHT EXCEPTION! Shutting down...');
console.log(err.name, err.message);
process.exit(1);
});
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
console.log('💥 UNHANDLED REJECTION! Shutting down...');
console.log(err.name, err.message);
server.close(() => {
process.exit(1);
});
});
Error Handling Flow Diagram:
Request → Middleware → Controller → Service
↓ ↓ ↓ ↓
Error occurs → Error caught → next(err) → Error Middleware
↓
Error processed → Response sent → End
Key Benefits:
- Centralized Error Processing: All errors flow through a single point
- Consistent Error Responses: Uniform error format across the application
- Environment-Specific Handling: Different error details for development vs production
- Operational vs Programming Errors: Distinction between expected and unexpected errors
- Error Logging: Centralized logging for monitoring and debugging
References:
↑ Back to topDatabases and Data Management
How do you connect a Node.js application to a database (e.g., MongoDB, PostgreSQL)?
Connecting a Node.js application to databases involves using appropriate database drivers or client libraries. The approach varies depending on the database type:
MongoDB Connection
For MongoDB, you can use the native MongoDB driver or Mongoose (ODM):
Using Native MongoDB Driver:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
async function connectToMongoDB() {
try {
await client.connect();
console.log('Connected to MongoDB');
const database = client.db('myapp');
return database;
} catch (error) {
console.error('MongoDB connection error:', error);
}
}
Using Mongoose:
const mongoose = require('mongoose');
async function connectToMongoDB() {
try {
await mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('Connected to MongoDB via Mongoose');
} catch (error) {
console.error('Mongoose connection error:', error);
}
}
PostgreSQL Connection
For PostgreSQL, use the pg library:
Using pg (node-postgres):
const { Pool } = require('pg');
const pool = new Pool({
user: 'username',
host: 'localhost',
database: 'myapp',
password: 'password',
port: 5432,
max: 20,
idleTimeoutMillis: 30000,
});
async function connectToPostgreSQL() {
try {
const client = await pool.connect();
console.log('Connected to PostgreSQL');
return client;
} catch (error) {
console.error('PostgreSQL connection error:', error);
}
}
Best practices for database connections:
- Use connection pooling to manage multiple database connections efficiently
- Store database credentials in environment variables, not in code
- Implement proper error handling and connection retry logic
- Close connections properly to prevent memory leaks
References:
↑ Back to topTesting and Debugging
What tools and libraries are available for testing Node.js applications?
Node.js has a rich ecosystem of testing tools and libraries that cater to different testing needs:
Testing Frameworks
- Jest - The most popular JavaScript testing framework [1], providing an all-in-one solution with built-in test runner, assertion library, mocking capabilities, and code coverage
- Mocha - A mature, feature-rich testing framework [2] that provides the test structure but requires additional libraries for assertions
- Vitest - A modern, fast testing framework [1] that offers Jest-compatible API with better performance and native ESM support
- Node.js Test Runner - The built-in test runner introduced in Node.js 18+ [2]
Assertion Libraries
- Chai - A BDD/TDD assertion library that pairs well with Mocha
- Jest Expect - Built into Jest, providing expressive assertion methods
- Node.js Assert - The built-in assertion module
HTTP/API Testing
- Supertest - Essential for testing HTTP endpoints and Express.js applications [3]
- Nock - HTTP server mocking library for testing external API calls
End-to-End Testing
- Cypress - Modern E2E testing framework [4]
- Playwright - Cross-browser automation framework [5]
- Puppeteer - Chrome DevTools Protocol-based testing
Mocking and Stubbing
- Sinon.js - Standalone library for spies, stubs, and mocks
- Jest Mocks - Built-in mocking capabilities in Jest
Performance Optimization and Scaling
What is clustering in Node.js and how does it improve performance?
Clustering in Node.js is a built-in module that allows you to create child processes (workers) that share server ports, enabling your application to take advantage of multi-core systems.
How Clustering Works:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // Restart worker
});
} else {
// Workers can share any TCP port
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello from worker ' + process.pid);
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
Performance Improvements:
- CPU Utilization: Leverages all available CPU cores instead of just one
- Load Distribution: Requests are automatically distributed among worker processes
- Fault Tolerance: If one worker crashes, others continue serving requests
- Increased Throughput: Can handle more concurrent requests by running multiple instances
Benefits:
- Zero Downtime Deployments: Workers can be restarted individually
- Automatic Load Balancing: Built-in round-robin load balancing
- Process Isolation: Crashes in one worker don't affect others
Considerations:
- Memory Usage: Each worker consumes additional memory
- Shared State: Workers don't share memory, requiring external solutions for shared data
- Not Suitable for: CPU-intensive tasks that would block all workers
References:
↑ Back to topSecurity Best Practices
What are some common security vulnerabilities in Node.js applications?
Node.js applications face several critical security vulnerabilities that developers must address:
Injection Attacks
- SQL Injection: Occurs when untrusted data is sent to SQL queries without proper sanitization
- NoSQL Injection: Similar to SQL injection but targets NoSQL databases like MongoDB
- Command Injection: Happens when user input is passed to system commands without validation
Cross-Site Scripting (XSS)
- Stored XSS: Malicious scripts stored in the database and executed when viewed
- Reflected XSS: Scripts reflected off web servers in error messages or search results
- DOM-based XSS: Client-side scripts that modify the DOM in unsafe ways
Authentication and Session Vulnerabilities
- Broken Authentication: Weak password policies, session fixation, or improper logout
- Session Hijacking: Unauthorized access to user sessions through various attack vectors
- JWT Vulnerabilities: Weak signing keys, algorithm confusion, or improper token validation
Dependency Vulnerabilities
- Outdated Dependencies: Using packages with known security flaws
- Supply Chain Attacks: Malicious code introduced through compromised dependencies
- Prototype Pollution: Modifying Object.prototype affecting the entire application
Server-Side Request Forgery (SSRF)
- Applications making requests to internal resources based on user input
- Can lead to internal network scanning or accessing sensitive endpoints
Insecure Direct Object References
- Exposing internal implementation objects without proper authorization checks
- Allowing users to access resources they shouldn't have permission to view
References:
↑ Back to topHow do you manage authentication and authorization in a Node.js application?
Authentication Strategies
JWT (JSON Web Tokens)
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Login endpoint
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Validate user credentials
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate JWT
const token = jwt.sign(
{ userId: user._id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
});
// JWT verification middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
};
Session-Based Authentication
const session = require('express-session');
const MongoStore = require('connect-mongo');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI
}),
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Session authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Authentication required' });
}
next();
};
Authorization (Role-Based Access Control)
// Role-based authorization middleware
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient privileges' });
}
next();
};
};
// Usage examples
app.get('/admin', authenticateToken, authorize(['admin']), (req, res) => {
res.json({ message: 'Admin only content' });
});
app.get('/user-data', authenticateToken, authorize(['user', 'admin']), (req, res) => {
res.json({ message: 'User data' });
});
Multi-Factor Authentication (MFA)
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Generate MFA secret
const generateMFASecret = async (userId) => {
const secret = speakeasy.generateSecret({
name: `YourApp (${userId})`,
issuer: 'YourApp'
});
// Save secret to database
await User.findByIdAndUpdate(userId, { mfaSecret: secret.base32 });
// Generate QR code
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
return { secret: secret.base32, qrCode: qrCodeUrl };
};
// Verify MFA token
const verifyMFA = (req, res, next) => {
const { token } = req.body;
const user = req.user;
const verified = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: token,
window: 2
});
if (!verified) {
return res.status(401).json({ error: 'Invalid MFA token' });
}
next();
};
References:
↑ Back to topAdvanced Topics and Specialized Knowledge Areas
What are streams in Node.js and how can they be utilized for efficient data processing?
Streams are Node.js objects that let you read data from a source or write data to a destination in a continuous fashion. They are particularly powerful for handling large amounts of data efficiently without loading everything into memory.
Types of Streams:
1. Readable Streams
- Read data from a source (files, HTTP requests, stdin)
- Examples:
fs.createReadStream(),process.stdin
2. Writable Streams
- Write data to a destination (files, HTTP responses, stdout)
- Examples:
fs.createWriteStream(),process.stdout
3. Duplex Streams
- Both readable and writable
- Examples: TCP sockets, crypto streams
4. Transform Streams
- Modify data as it passes through
- Examples: zlib compression, CSV parsers
Efficient Data Processing Use Cases:
const fs = require('fs');
const zlib = require('zlib');
const csv = require('csv-parser');
// File processing pipeline
fs.createReadStream('large-file.csv')
.pipe(csv())
.pipe(new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// Process each row
const processed = {
...chunk,
processedAt: new Date().toISOString()
};
callback(null, JSON.stringify(processed) + '\n');
}
}))
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('processed-data.json.gz'));
Memory Efficiency Benefits:
- Backpressure: Automatic flow control prevents memory overflow
- Chunked Processing: Process data piece by piece
- Pipeline Composition: Chain multiple processing steps efficiently
Performance Diagram:
Traditional Approach:
[Load Entire File] → [Process in Memory] → [Write Result]
Memory Usage: ████████████████████████████████
Stream Approach:
[Read Chunk] → [Process Chunk] → [Write Chunk] → [Next Chunk]
Memory Usage: ████
References:
- Node.js Streams Documentation
- Stream Handbook by James Halliday
- Mastering Node.js Streams - Samer Buna