// counter.js
let count = 0; // private, invisible outside this file
function increment() { count += 1; return count; }
function getCount() { return count; }
module.exports = { increment, getCount };
Topics
40
Advanced Node.js
Architecture & Design Patterns
Async Patterns
Authentication & Authorization
Authentication & Authorization (JWT, OAuth, Passport)
Background Jobs & Queues
Caching
Caching with Redis
Child Processes & Process Management
CLI Tools & Scripting with Node.js
Cloud & DevOps
Clustering & Worker Threads
Core Node.js Modules
Databases
Databases & ORMs (MongoDB/Mongoose, SQL/Sequelize)
Debugging & Diagnostics
Deployment & Process Managers (PM2)
Docker & Containerization for Node.js
Docker & Deployment
Email & Notifications
Environment Variables & Configuration
Error Handling
Event Loop & Non-blocking IO
Events & EventEmitter
Express & Middleware
File System & File Processing
File System (fs) Module
File Uploads & Media Processing
Git & Project Management
Global Objects & the process Object
GraphQL
GraphQL with Node.js
HTTP & HTTPS Modules
HTTP & Web Servers
Logging & Monitoring
Message Queues (RabbitMQ & Kafka)
Microservices Architecture with Node.js
Node.js Fundamentals & Runtime Architecture
Path & OS Modules
Performance Optimization & Profiling
Architecture & Design Patterns
15 questions found
The Module pattern uses each file's own function scope (whether via CommonJS's implicit wrapper function or ESM's module scope) to keep variables and functions private by default, exposing only what's explicitly attached to module.exports or exported via 'export' -- this gives every Node.js file natural encapsulation without needing IIFEs or closures written by hand, unlike plain browser scripts sharing a single global scope.
Real-world example
A payment library exposes only a processPayment() function while keeping its internal retry logic, API keys, and validation helpers completely private to the module, preventing consuming applications from accidentally depending on or mutating implementation details that might change between versions.
Modules (CommonJS/ESM);OOP & Classes
How is the Factory pattern commonly applied in Node.js applications, and what problem does it solve?
IntermediateA factory function or factory class centralizes object creation logic behind a single function, hiding the specific class or configuration details from the calling code -- useful when object construction is conditional (different implementations based on environment or input) or complex enough that scattering 'new ClassName()' calls throughout the codebase would create tight coupling to specific implementations.
function createLogger(env) {
if (env === 'production') return new JsonLogger();
if (env === 'test') return new SilentLogger();
return new ConsoleLogger();
}
const logger = createLogger(process.env.NODE_ENV);
Real-world example
A multi-tenant SaaS application uses a factory function to construct the correct database adapter (PostgresAdapter, MySQLAdapter, or a mock TestAdapter) based on each tenant's configuration, so the rest of the application code interacts only with a common adapter interface without knowing which concrete class it received.
Design Patterns in JavaScript;Dependency Injection & IoC Principles
What is the Singleton pattern, and what are the specific risks of using it in a Node.js application?
IntermediateA Singleton ensures only one instance of a class or module-level object exists and is shared across the entire application -- in Node.js this is trivially achieved because a required module is cached and reused by reference after its first load, so a module that exports a single instantiated object naturally behaves as a singleton. The main risk is that singletons introduce hidden global state, making unit tests harder to isolate (state can leak between tests) and creating implicit coupling between unrelated parts of the codebase that all depend on the same shared instance.
// database.js -- naturally a singleton due to module caching
class Database {
constructor() { this.connection = connectToDb(); }
}
module.exports = new Database(); // same instance returned on every require()
Real-world example
A connection-pool manager is implemented as a module-level singleton so that every part of an application shares the same underlying pool of database connections, but this later complicates testing until the team refactors to inject the pool explicitly rather than importing the singleton directly.
Design Patterns in JavaScript;Modules (CommonJS/ESM)
What is a layered (n-tier) architecture in a Node.js/Express application, and what are its typical layers?
AdvancedA layered architecture separates an application into distinct responsibilities, commonly: the routing/controller layer (handling HTTP requests and responses), the service/business-logic layer (implementing the actual application rules), the data-access/repository layer (talking to the database), and the model layer (data shape definitions) -- each layer depends only on the layer below it, which improves testability (business logic can be tested without an HTTP server or a real database) and makes it easier to swap implementations, like changing databases without touching business logic.
// controller
app.post('/users', (req, res) => userService.createUser(req.body).then(u => res.json(u)));
// service (business logic)
async function createUser(data) {
validateUser(data);
return userRepository.save(data);
}
// repository (data access)
async function save(userData) { return db.collection('users').insertOne(userData); }
Real-world example
A growing Express API refactors from having database queries directly inside route handlers to a proper repository layer, which immediately makes it possible to unit test the business logic against an in-memory fake repository instead of requiring a real database connection for every test.
Express & Middleware;Testing with Jest
Mocha & the Node Test Runner
What is the Repository pattern, and how does it help decouple business logic from a specific database technology?
AdvancedThe Repository pattern defines an abstraction (an interface or a consistent set of functions) for accessing and persisting domain objects, hiding the specific database technology and query syntax behind that abstraction -- business logic calls methods like findById() or save() without knowing whether the underlying implementation uses MongoDB, PostgreSQL, or an in-memory store, which makes both testing (via a fake in-memory repository) and future database migrations significantly easier.
class UserRepository {
async findById(id) { return db.collection('users').findOne({ _id: id }); }
async save(user) { return db.collection('users').insertOne(user); }
}
// In tests, swap in a fake implementation with the same interface
class FakeUserRepository {
constructor() { this.users = new Map(); }
async findById(id) { return this.users.get(id); }
async save(user) { this.users.set(user.id, user); return user; }
}
Real-world example
A team migrating from MongoDB to PostgreSQL is able to complete the migration by rewriting only the internals of their UserRepository class, since every other part of the application only ever called repository.findById() and repository.save() and never issued raw queries directly.
Databases & ORMs (MongoDB/Mongoose
SQL/Sequelize);Testing with Jest
Mocha & the Node Test Runner
What is the Observer pattern, and how does Node.js's EventEmitter class implement it natively?
AdvancedThe Observer pattern lets an object (the subject) maintain a list of dependents (observers) and notify them automatically of state changes, without the subject needing to know any specifics about the observers -- Node.js's built-in EventEmitter class is a direct, first-class implementation of this pattern: any object can extend EventEmitter, emit named events, and any number of listeners can subscribe to those events independently.
const EventEmitter = require('node:events');
class OrderProcessor extends EventEmitter {
processOrder(order) {
// ... process order ...
this.emit('orderCompleted', order);
}
}
const processor = new OrderProcessor();
processor.on('orderCompleted', (order) => sendConfirmationEmail(order));
processor.on('orderCompleted', (order) => updateInventory(order));
Real-world example
An order-processing service emits an 'orderCompleted' event rather than directly calling sendEmail() and updateInventory() from inside the processing function, letting new behaviors (like triggering a loyalty-points update) be added later by simply attaching a new listener, without modifying the core processing logic at all.
Events & EventEmitter;Message Queues (RabbitMQ & Kafka)
What is Domain-Driven Design (DDD), and how do its concepts of entities, value objects, and aggregates apply to a Node.js codebase?
AdvancedDomain-Driven Design structures code around the business domain itself rather than technical layers -- an entity is an object with a distinct identity that persists over time (a User with a stable ID), a value object is defined entirely by its attributes with no identity of its own (an Address or a Money amount, where two instances with identical values are interchangeable), and an aggregate is a cluster of entities and value objects treated as a single consistency boundary, with one designated 'aggregate root' controlling all access and enforcing invariants for the whole cluster.
class Money { // value object: equality is by value, and it's immutable
constructor(amount, currency) { this.amount = amount; this.currency = currency; Object.freeze(this); }
equals(other) { return this.amount === other.amount && this.currency === other.currency; }
}
class Order { // entity: has identity, and acts as the aggregate root
constructor(id) { this.id = id; this.items = []; this.total = new Money(0, 'USD'); }
addItem(item) { this.items.push(item); this.total = this.recalculateTotal(); }
}
Real-world example
An e-commerce backend models Order as an aggregate root that owns its OrderLineItems, ensuring that any modification (like adding an item) always goes through the Order object itself so that invariants like 'total must equal the sum of line items' can never be violated by code modifying a line item directly.
OOP & Classes;Design Patterns in JavaScript
What is a Hexagonal Architecture (Ports and Adapters), and what specific benefit does it provide for a Node.js API?
AdvancedHexagonal Architecture structures an application so the core business logic sits in the center, communicating with the outside world (databases, HTTP frameworks, message queues, third-party APIs) only through defined interfaces called 'ports', with concrete implementations ('adapters') plugged in at the edges -- this means the core business logic has zero direct dependency on Express, MongoDB, or any other specific technology, making it possible to swap the web framework or database with minimal changes to the actual business rules, and to test the core logic in complete isolation.
// Port (interface the core logic depends on)
class NotificationPort { async send(message) { throw new Error('not implemented'); } }
// Adapter (concrete implementation, plugged in at the edge)
class EmailAdapter extends NotificationPort {
async send(message) { return emailClient.send(message); }
}
// Core business logic depends only on the port, not the concrete adapter
class OrderService {
constructor(notificationPort) { this.notifications = notificationPort; }
async completeOrder(order) { await this.notifications.send(`Order ${order.id} completed`); }
}
Real-world example
A startup initially ships with an EmailAdapter for order notifications, then later adds an SmsAdapter and a SlackAdapter, all implementing the same NotificationPort interface, without ever having to touch the core OrderService business logic that triggers the notification.
Dependency Injection & IoC Principles;Testing with Jest
Mocha & the Node Test Runner
What is the Middleware (Chain of Responsibility) pattern as implemented by Express, and how does the 'next()' function drive it?
IntermediateExpress middleware implements the Chain of Responsibility pattern: each middleware function receives the request, response, and a 'next' callback, and can either handle the request completely, modify it and pass control to the next function in the chain by calling next(), or short-circuit the chain by sending a response directly -- this lets cross-cutting concerns (authentication, logging, error handling) be composed as small, independent, reorderable functions rather than one large monolithic handler.
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next(); // pass control to the next middleware in the chain
});
app.use((req, res, next) => {
if (!req.headers.authorization) return res.status(401).end(); // short-circuits the chain
next();
});
Real-world example
An API composes a request-logging middleware, an authentication middleware, and a rate-limiting middleware as three separate, independently testable functions applied in sequence, rather than combining all three concerns into a single large function at the top of every route handler.
Express & Middleware;Error Handling
What is a Circuit Breaker pattern, and why is it important when a Node.js service calls an unreliable downstream dependency?
AdvancedA Circuit Breaker wraps calls to an external dependency and tracks recent failure rates -- when failures exceed a threshold, the breaker 'opens' and immediately rejects further calls (without even attempting them) for a cooldown period, preventing a struggling downstream service from being overwhelmed further and preventing the calling service from wasting resources on calls likely to fail or time out; after the cooldown, it allows a limited number of test requests through in a 'half-open' state to check if the dependency has recovered.
const CircuitBreaker = require('opossum');
const breaker = new CircuitBreaker(callExternalApi, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000,
});
breaker.fallback(() => ({ cached: true, data: getCachedResponse() }));
breaker.fire(requestParams).then(handleResponse);
Real-world example
A checkout service calling a flaky third-party shipping-rate API wraps that call in a circuit breaker with a fallback to a cached flat rate, so that when the shipping API starts timing out under its own load, the checkout flow degrades gracefully instead of piling up slow, doomed requests.
Error Handling;Cloud & DevOps
Showing 1–10 of 15