Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to implement module pattern in JavaScript

The module pattern uses closures to create private and public members, providing encapsulation and namespace management in JavaScript. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve used module patterns extensively before ES6 modules to organize code in large applications, creating clean APIs with private implementation details for millions of users.

The most maintainable approach uses IIFE (Immediately Invoked Function Expression) or ES6 modules for encapsulation.

Basic Module Pattern

const Calculator = (function() {
  // Private variables
  let result = 0

  // Private function
  function log(message) {
    console.log(`[Calculator] ${message}`)
  }

  // Public API
  return {
    add(x) {
      result += x
      log(`Added ${x}, result: ${result}`)
      return this
    },

    subtract(x) {
      result -= x
      log(`Subtracted ${x}, result: ${result}`)
      return this
    },

    getResult() {
      return result
    },

    reset() {
      result = 0
      log('Reset')
      return this
    }
  }
})()

// Usage
Calculator.add(5).add(3).subtract(2)
console.log(Calculator.getResult()) // 6

// Private members not accessible
console.log(Calculator.result) // undefined
console.log(Calculator.log) // undefined

Revealing Module Pattern

const UserManager = (function() {
  // Private state
  const users = []
  let currentId = 1

  // Private functions
  function generateId() {
    return currentId++
  }

  function findById(id) {
    return users.find(user => user.id === id)
  }

  // Public functions
  function addUser(name, email) {
    const user = {
      id: generateId(),
      name,
      email,
      createdAt: new Date()
    }
    users.push(user)
    return user
  }

  function getUser(id) {
    return findById(id)
  }

  function getAllUsers() {
    return [...users] // Return copy
  }

  function deleteUser(id) {
    const index = users.findIndex(user => user.id === id)
    if (index !== -1) {
      users.splice(index, 1)
      return true
    }
    return false
  }

  // Reveal public API
  return {
    add: addUser,
    get: getUser,
    getAll: getAllUsers,
    delete: deleteUser
  }
})()

// Usage
const user = UserManager.add('John', '[email protected]')
console.log(user) // { id: 1, name: 'John', ... }

console.log(UserManager.getAll()) // [{ id: 1, ... }]

// Private functions not accessible
console.log(UserManager.generateId) // undefined

Module with Configuration

const Logger = (function(config) {
  // Private state
  const settings = Object.assign({
    level: 'info',
    prefix: '[LOG]',
    timestamp: true
  }, config)

  // Private functions
  function formatMessage(level, message) {
    const timestamp = settings.timestamp
      ? `[${new Date().toISOString()}] `
      : ''

    return `${timestamp}${settings.prefix} ${level.toUpperCase()}: ${message}`
  }

  function shouldLog(level) {
    const levels = ['debug', 'info', 'warn', 'error']
    const currentLevel = levels.indexOf(settings.level)
    const messageLevel = levels.indexOf(level)
    return messageLevel >= currentLevel
  }

  // Public API
  return {
    debug(message) {
      if (shouldLog('debug')) {
        console.log(formatMessage('debug', message))
      }
    },

    info(message) {
      if (shouldLog('info')) {
        console.log(formatMessage('info', message))
      }
    },

    warn(message) {
      if (shouldLog('warn')) {
        console.warn(formatMessage('warn', message))
      }
    },

    error(message) {
      if (shouldLog('error')) {
        console.error(formatMessage('error', message))
      }
    },

    setLevel(level) {
      settings.level = level
    }
  }
})({ level: 'debug', prefix: '[APP]' })

// Usage
Logger.debug('Debug message')
Logger.info('Info message')
Logger.setLevel('error')
Logger.info('This won\'t show') // Below error level

Singleton Module

const Database = (function() {
  let instance

  function createInstance() {
    // Private state
    const connections = []
    let connectionId = 1

    return {
      connect(connectionString) {
        const connection = {
          id: connectionId++,
          connectionString,
          connected: true
        }
        connections.push(connection)
        return connection
      },

      disconnect(id) {
        const index = connections.findIndex(c => c.id === id)
        if (index !== -1) {
          connections[index].connected = false
          return true
        }
        return false
      },

      getConnections() {
        return connections.filter(c => c.connected)
      }
    }
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance()
      }
      return instance
    }
  }
})()

// Usage
const db1 = Database.getInstance()
const db2 = Database.getInstance()

console.log(db1 === db2) // true (singleton)

Namespace Module

const MyApp = MyApp || {}

MyApp.Utils = (function() {
  function debounce(func, wait) {
    let timeout
    return function(...args) {
      clearTimeout(timeout)
      timeout = setTimeout(() => func.apply(this, args), wait)
    }
  }

  function throttle(func, limit) {
    let inThrottle
    return function(...args) {
      if (!inThrottle) {
        func.apply(this, args)
        inThrottle = true
        setTimeout(() => inThrottle = false, limit)
      }
    }
  }

  return {
    debounce,
    throttle
  }
})()

MyApp.API = (function() {
  const baseUrl = 'https://api.example.com'

  async function get(endpoint) {
    const response = await fetch(`${baseUrl}${endpoint}`)
    return response.json()
  }

  async function post(endpoint, data) {
    const response = await fetch(`${baseUrl}${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
    return response.json()
  }

  return {
    get,
    post
  }
})()

// Usage
const debouncedSearch = MyApp.Utils.debounce(() => {
  console.log('Searching...')
}, 300)

MyApp.API.get('/users').then(users => console.log(users))

ES6 Module Pattern

// math.js
const PI = 3.14159

function add(a, b) {
  return a + b
}

function multiply(a, b) {
  return a * b
}

// Private function
function logOperation(operation, result) {
  console.log(`${operation}: ${result}`)
}

// Export public API
export {
  PI,
  add,
  multiply
}

// Usage in another file
import { add, multiply, PI } from './math.js'

console.log(add(2, 3)) // 5
console.log(PI) // 3.14159
// logOperation is not accessible

Module with Events

const EventBus = (function() {
  const events = {}

  function on(event, callback) {
    if (!events[event]) {
      events[event] = []
    }
    events[event].push(callback)
  }

  function off(event, callback) {
    if (events[event]) {
      events[event] = events[event].filter(cb => cb !== callback)
    }
  }

  function emit(event, data) {
    if (events[event]) {
      events[event].forEach(callback => callback(data))
    }
  }

  function clear() {
    Object.keys(events).forEach(event => {
      events[event] = []
    })
  }

  return {
    on,
    off,
    emit,
    clear
  }
})()

// Usage
EventBus.on('user:login', (user) => {
  console.log('User logged in:', user.name)
})

EventBus.emit('user:login', { name: 'John' })

Best Practice Note

This is how we implemented modules in CoreUI before ES6 modules became standard. The module pattern provides encapsulation through closures, creating private and public members that organize code and prevent global namespace pollution. While ES6 modules are now the standard, understanding the classic module pattern helps when working with legacy code or implementing design patterns that require private state and controlled access.

For related patterns, check out how to implement singleton pattern in JavaScript and how to implement factory pattern in JavaScript.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author