Modern examples of JavaScript module patterns: 3 practical examples
When people talk about examples of JavaScript module patterns: 3 practical examples, ES modules are the one pattern you absolutely cannot ignore anymore. They’re natively supported in all modern browsers and Node.js, and they’re the foundation for most build tools.
At a high level:
- Each file is a module.
- You use
exportto expose code. - You use
importto consume it.
Here’s a simple ES module that wraps a REST API client:
// apiClient.js
const BASE_URL = 'https://api.example.com';
async function request(path, options = {}) {
const response = await fetch(`\({BASE_URL}}\(path}`, {
headers: { 'Content-Type': 'application/json' },
...options,
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
export async function getUser(id) {
return request(`/users/${id}`);
}
export async function updateUser(id, data) {
return request(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
export default {
getUser,
updateUser,
};
And then you consume it like this:
// profilePage.js
import apiClient, { getUser } from './apiClient.js';
async function loadProfile(userId) {
const user = await getUser(userId);
console.log('Loaded user:', user);
}
loadProfile('123');
This is a straightforward example of an ES module pattern: private implementation details (like BASE_URL and the request helper) are hidden inside the module, while the public API is defined by what you export.
Real-world ES module examples include more than just API clients
If you’re looking for the best examples of ES module usage, think about cross-cutting concerns you want to centralize:
Example 1: Feature flag module
// featureFlags.js
const flags = new Map();
export function setFlag(name, value) {
flags.set(name, Boolean(value));
}
export function isEnabled(name) {
return flags.get(name) === true;
}
export function listFlags() {
return Object.fromEntries(flags.entries());
}
Any part of your app can import isEnabled to toggle behavior. This is one of the cleaner examples of JavaScript module patterns that keeps global-ish state organized.
Example 2: Analytics wrapper module
// analytics.js
let provider = null;
export function initAnalytics(externalProvider) {
provider = externalProvider;
}
export function trackEvent(name, payload = {}) {
if (!provider) return;
provider.track(name, {
timestamp: Date.now(),
...payload,
});
}
Every tracking call in your app goes through this module. If you change providers in 2025, you only touch one file.
Why ES modules dominate in 2024–2025
Modern runtimes and tools are aligned around ES modules:
- Node.js has supported ES modules without flags since v14.
- Browsers support
<script type="module">without transpilation. - Bundlers like Vite, Rollup, and Webpack optimize better when they see
import/export.
For up-to-date guidance on JavaScript modules and syntax, the MDN Web Docs are still the gold standard reference: https://developer.mozilla.org.
When you think about examples of JavaScript module patterns: 3 practical examples, ES modules should be your default pattern for new code.
2. Revealing Module Pattern: Classic pattern, still useful
Before ES modules were standardized, developers leaned heavily on the revealing module pattern to simulate private and public members. Even in 2024, it’s still handy when you:
- Need a self-contained module without a build step.
- Are working with legacy code or older platforms.
- Want to encapsulate behavior in a single IIFE (Immediately Invoked Function Expression).
Here’s a clear example of the revealing module pattern:
// cartModule.js
const CartModule = (function () {
const items = new Map(); // private
function addItem(id, qty = 1) {
const current = items.get(id) || 0;
items.set(id, current + qty);
}
function removeItem(id) {
items.delete(id);
}
function getTotalCount() {
let total = 0;
for (const qty of items.values()) {
total += qty;
}
return total;
}
function listItems() {
return Array.from(items.entries()).map(([id, qty]) => ({ id, qty }));
}
// reveal public API
return {
addItem,
removeItem,
getTotalCount,
listItems,
};
})();
// usage
CartModule.addItem('sku-123', 2);
console.log(CartModule.getTotalCount());
items is private; only the returned methods are visible. That’s the heart of this pattern.
More revealing module pattern examples include UI and state
To meet the goal of examples of JavaScript module patterns: 3 practical examples, let’s look at a couple more concrete use cases.
Example 3: DOM-based notification module
// notificationModule.js
const NotificationModule = (function () {
let container = null;
function ensureContainer() {
if (!container) {
container = document.createElement('div');
container.id = 'notifications';
document.body.appendChild(container);
}
}
function show(message, type = 'info') {
ensureContainer();
const el = document.createElement('div');
el.className = `notification notification--${type}`;
el.textContent = message;
container.appendChild(el);
setTimeout(() => el.remove(), 3000);
}
return { show };
})();
// usage
NotificationModule.show('Saved successfully', 'success');
This is a nice, isolated example of a module that manages its own DOM container without leaking implementation details.
Example 4: In-memory cache module
// cacheModule.js
const CacheModule = (function () {
const store = new Map();
function set(key, value, ttlMs = 0) {
const expiresAt = ttlMs ? Date.now() + ttlMs : null;
store.set(key, { value, expiresAt });
}
function get(key) {
const entry = store.get(key);
if (!entry) return undefined;
if (entry.expiresAt && entry.expiresAt < Date.now()) {
store.delete(key);
return undefined;
}
return entry.value;
}
function clear() {
store.clear();
}
return { set, get, clear };
})();
Again, you see a pattern: private data + public API returned from an IIFE. Among the best examples of this pattern are modules where you really want to guard internal state.
When to still use the revealing module pattern in 2025
In greenfield applications, ES modules win. But this pattern still shows up in:
- Older CMS themes where you only control a single script tag.
- Embeddable widgets that must avoid polluting the global scope.
- Legacy Node.js code that predates
import/export.
The core idea—hide internals, reveal a clean API—is timeless, and it fits neatly into our trio of examples of JavaScript module patterns: 3 practical examples.
3. Hybrid Service Module Pattern: ES modules + factories
The third of our examples of JavaScript module patterns: 3 practical examples is a hybrid pattern you see a lot in React, Vue, and Node.js backends: a module that exports a factory or class, which in turn creates configured service instances.
Think of it as: “Use ES modules for structure, and a factory to manage configuration and lifecycle.”
Here’s a concrete example of a logging service module:
// logger.js
function createLogger({ level = 'info', destination = console } = {}) {
const levels = ['debug', 'info', 'warn', 'error'];
function shouldLog(msgLevel) {
return levels.indexOf(msgLevel) >= levels.indexOf(level);
}
function log(msgLevel, message, meta = {}) {
if (!shouldLog(msgLevel)) return;
destination.log(JSON.stringify({
level: msgLevel,
message,
meta,
timestamp: new Date().toISOString(),
}));
}
return {
debug: (msg, meta) => log('debug', msg, meta),
info: (msg, meta) => log('info', msg, meta),
warn: (msg, meta) => log('warn', msg, meta),
error: (msg, meta) => log('error', msg, meta),
};
}
export { createLogger };
export default createLogger({ level: 'info' });
Usage in different parts of your app:
// server.js
import defaultLogger, { createLogger } from './logger.js';
const requestLogger = createLogger({ level: 'debug' });
defaultLogger.info('Server starting');
requestLogger.debug('Incoming request', { path: '/api/users' });
This hybrid pattern gives you:
- A default instance for quick use.
- A factory for more advanced scenarios (multi-tenant apps, test doubles, different environments).
More hybrid module pattern examples include configuration-heavy services
To round out our examples of JavaScript module patterns: 3 practical examples, here are two more concrete hybrid patterns.
Example 5: HTTP client with pluggable middlewares
// httpClient.js
function createHttpClient({ baseUrl, middlewares = [] }) {
async function request(path, options = {}) {
let config = { url: baseUrl + path, options };
for (const mw of middlewares) {
config = await mw(config) || config;
}
const response = await fetch(config.url, config.options);
return response.json();
}
return {
get: (path) => request(path),
post: (path, body) => request(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}),
};
}
export { createHttpClient };
You can now create differently configured clients:
import { createHttpClient } from './httpClient.js';
const authMiddleware = async (config) => {
const token = localStorage.getItem('token');
return {
...config,
options: {
...config.options,
headers: {
...(config.options.headers || {}),
Authorization: `Bearer ${token}`,
},
},
};
};
const api = createHttpClient({
baseUrl: 'https://api.example.com',
middlewares: [authMiddleware],
});
Example 6: State store module for a small app
// store.js
function createStore(initialState = {}) {
let state = { ...initialState };
const listeners = new Set();
function getState() {
return { ...state };
}
function setState(partial) {
state = { ...state, ...partial };
listeners.forEach((listener) => listener(getState()));
}
function subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
return { getState, setState, subscribe };
}
export const appStore = createStore({ user: null, theme: 'light' });
export { createStore };
You get a singleton appStore for most of your app, and the option to spin up isolated stores for tests or micro-frontends.
These hybrid service modules are some of the best examples of modern patterns that blend ES modules with classic factory ideas.
How to choose between these examples of JavaScript module patterns
We’ve walked through three main patterns and several supporting examples. In practice, your choice usually comes down to:
- ES modules: Use for almost everything in modern code bases, especially when using bundlers or Node.js.
- Revealing module pattern: Use in legacy environments or when you need a self-contained script without a build step.
- Hybrid service modules: Use when you have configuration-heavy services (logging, HTTP, state, analytics) that need multiple differently configured instances.
In large applications—front-end or back-end—you’ll often mix these patterns. For example, an ES module might export a factory that internally uses a revealing-style closure to hide implementation details. That’s perfectly fine; patterns are tools, not rules.
For deeper language background and specification details, the ECMAScript standard is published through ECMA International, and the core language concepts are also covered in many university CS curricula. For example, the Harvard CS50 materials discuss modular program design and abstraction in general terms: https://cs50.harvard.edu.
FAQ: examples of JavaScript module patterns and practical usage
What are some real examples of JavaScript module patterns in production apps?
Real-world examples of JavaScript module patterns show up as:
- API client modules that centralize networking logic.
- Authentication modules that wrap login, logout, and token refresh.
- Analytics modules that standardize event tracking.
- Configuration modules that read environment variables or build-time flags.
- State stores for small apps that don’t need a full framework.
All of these can be implemented as ES modules, revealing modules, or hybrid service modules.
Which example of a JavaScript module pattern should I teach beginners first?
For beginners, ES modules are the best starting point. They match what’s in modern docs, tutorials, and tooling. Once students are comfortable with import and export, you can show a simple revealing module pattern as a way to explain closures and encapsulation. Using one clear example of each pattern is usually enough to get the idea across.
Are CommonJS modules still relevant compared to these examples?
CommonJS (require, module.exports) is still present in older Node.js projects and some npm packages, but new code should favor ES modules. Many bundlers and the Node.js runtime can interoperate between the two, but the patterns in this article—especially ES modules and hybrid service modules—represent where the ecosystem has settled for 2024–2025.
Can I mix different examples of JavaScript module patterns in one project?
Yes, and most large code bases do. A typical React or Node.js project might:
- Use ES modules everywhere by default.
- Have a few legacy revealing modules or IIFEs for old widgets.
- Export service factories (our third pattern) for logging, HTTP, and state.
The key is consistency within each area of your project. Use the same pattern for similar concerns so future maintainers don’t have to mentally switch styles every file.
Where can I learn more about patterns, architecture, and maintainable JavaScript?
For language-level reference and modern examples, MDN is still the best general resource: https://developer.mozilla.org. For broader software design concepts that influence how you think about modules and abstraction, many university CS departments (such as Harvard’s CS programs at https://cs.harvard.edu) publish open course materials that cover modular design, interfaces, and separation of concerns.
These resources, combined with the examples of JavaScript module patterns: 3 practical examples in this article, will give you a solid foundation for organizing real-world JavaScript applications.
Related Topics
Explore More JavaScript Code Snippets
Discover more examples and insights in this category.
View All JavaScript Code Snippets