How to implement factory pattern in JavaScript
The factory pattern creates objects without exposing instantiation logic, providing flexibility to choose which class to instantiate at runtime. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve implemented factory patterns in large-scale applications to manage component creation, handle multiple database adapters, and provide plugin architectures for millions of users.
The most maintainable approach uses factory functions or classes that encapsulate object creation logic.
Basic Factory Function
// Product classes
class Car {
constructor(make, model) {
this.type = 'car'
this.make = make
this.model = model
}
drive() {
console.log(`Driving ${this.make} ${this.model}`)
}
}
class Truck {
constructor(make, model) {
this.type = 'truck'
this.make = make
this.model = model
}
drive() {
console.log(`Driving ${this.make} ${this.model} truck`)
}
}
// Factory function
function vehicleFactory(type, make, model) {
switch (type) {
case 'car':
return new Car(make, model)
case 'truck':
return new Truck(make, model)
default:
throw new Error(`Unknown vehicle type: ${type}`)
}
}
// Usage
const myCar = vehicleFactory('car', 'Toyota', 'Camry')
const myTruck = vehicleFactory('truck', 'Ford', 'F-150')
myCar.drive() // Driving Toyota Camry
myTruck.drive() // Driving Ford F-150 truck
Factory Class
class User {
constructor(name, email, role) {
this.name = name
this.email = email
this.role = role
}
getPermissions() {
return []
}
}
class Admin extends User {
constructor(name, email) {
super(name, email, 'admin')
}
getPermissions() {
return ['read', 'write', 'delete', 'manage']
}
}
class Editor extends User {
constructor(name, email) {
super(name, email, 'editor')
}
getPermissions() {
return ['read', 'write']
}
}
class Viewer extends User {
constructor(name, email) {
super(name, email, 'viewer')
}
getPermissions() {
return ['read']
}
}
// Factory class
class UserFactory {
static create(role, name, email) {
switch (role) {
case 'admin':
return new Admin(name, email)
case 'editor':
return new Editor(name, email)
case 'viewer':
return new Viewer(name, email)
default:
return new User(name, email, role)
}
}
}
// Usage
const admin = UserFactory.create('admin', 'John', '[email protected]')
const editor = UserFactory.create('editor', 'Jane', '[email protected]')
console.log(admin.getPermissions())
// ['read', 'write', 'delete', 'manage']
console.log(editor.getPermissions())
// ['read', 'write']
Abstract Factory
// Database adapters
class MySQLAdapter {
connect() {
return 'Connected to MySQL'
}
query(sql) {
return `MySQL query: ${sql}`
}
}
class PostgreSQLAdapter {
connect() {
return 'Connected to PostgreSQL'
}
query(sql) {
return `PostgreSQL query: ${sql}`
}
}
class MongoDBAdapter {
connect() {
return 'Connected to MongoDB'
}
query(json) {
return `MongoDB query: ${JSON.stringify(json)}`
}
}
// Abstract factory
class DatabaseFactory {
static create(type, config) {
const adapters = {
mysql: MySQLAdapter,
postgresql: PostgreSQLAdapter,
mongodb: MongoDBAdapter
}
const Adapter = adapters[type]
if (!Adapter) {
throw new Error(`Unknown database type: ${type}`)
}
return new Adapter(config)
}
}
// Usage
const mysql = DatabaseFactory.create('mysql', { host: 'localhost' })
console.log(mysql.connect()) // Connected to MySQL
console.log(mysql.query('SELECT * FROM users'))
// MySQL query: SELECT * FROM users
const mongo = DatabaseFactory.create('mongodb', { host: 'localhost' })
console.log(mongo.connect()) // Connected to MongoDB
console.log(mongo.query({ find: 'users' }))
// MongoDB query: {"find":"users"}
Factory with Registration
class ComponentFactory {
constructor() {
this.components = new Map()
}
register(name, component) {
this.components.set(name, component)
}
create(name, props) {
const Component = this.components.get(name)
if (!Component) {
throw new Error(`Component not registered: ${name}`)
}
return new Component(props)
}
}
// Component classes
class Button {
constructor(props) {
this.type = 'button'
this.label = props.label
}
render() {
return `<button>${this.label}</button>`
}
}
class Input {
constructor(props) {
this.type = 'input'
this.placeholder = props.placeholder
}
render() {
return `<input placeholder="${this.placeholder}">`
}
}
// Usage
const factory = new ComponentFactory()
factory.register('button', Button)
factory.register('input', Input)
const btn = factory.create('button', { label: 'Click Me' })
const input = factory.create('input', { placeholder: 'Enter text' })
console.log(btn.render()) // <button>Click Me</button>
console.log(input.render()) // <input placeholder="Enter text">
Factory with Configuration
class Logger {
constructor(config) {
this.level = config.level
this.output = config.output
}
log(message) {
if (this.output === 'console') {
console.log(`[${this.level}] ${message}`)
} else if (this.output === 'file') {
// Write to file
console.log(`Writing to file: [${this.level}] ${message}`)
}
}
}
class LoggerFactory {
static create(environment) {
const configs = {
development: {
level: 'debug',
output: 'console'
},
production: {
level: 'error',
output: 'file'
},
test: {
level: 'silent',
output: 'console'
}
}
const config = configs[environment] || configs.development
return new Logger(config)
}
}
// Usage
const devLogger = LoggerFactory.create('development')
devLogger.log('Debug message') // [debug] Debug message
const prodLogger = LoggerFactory.create('production')
prodLogger.log('Error occurred') // Writing to file: [error] Error occurred
Async Factory
class ApiClient {
constructor(config) {
this.baseUrl = config.baseUrl
this.token = config.token
}
async get(endpoint) {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: { Authorization: `Bearer ${this.token}` }
})
return response.json()
}
}
class ApiClientFactory {
static async create(service) {
// Fetch configuration from server
const config = await fetch(`/api/config/${service}`).then(res => res.json())
return new ApiClient(config)
}
}
// Usage
const client = await ApiClientFactory.create('users')
const users = await client.get('/users')
console.log(users)
Factory with Dependency Injection
class EmailService {
send(to, subject, body) {
console.log(`Sending email to ${to}`)
}
}
class SMSService {
send(phone, message) {
console.log(`Sending SMS to ${phone}`)
}
}
class NotificationFactory {
constructor(emailService, smsService) {
this.services = {
email: emailService,
sms: smsService
}
}
create(type) {
const service = this.services[type]
if (!service) {
throw new Error(`Unknown notification type: ${type}`)
}
return service
}
}
// Usage
const emailService = new EmailService()
const smsService = new SMSService()
const factory = new NotificationFactory(emailService, smsService)
const email = factory.create('email')
email.send('[email protected]', 'Hello', 'Welcome!')
// Sending email to [email protected]
const sms = factory.create('sms')
sms.send('+1234567890', 'Your code is 1234')
// Sending SMS to +1234567890
Plugin Factory
class PluginFactory {
constructor() {
this.plugins = new Map()
}
register(name, plugin) {
this.plugins.set(name, plugin)
}
create(name, options) {
const Plugin = this.plugins.get(name)
if (!Plugin) {
return null // Gracefully handle missing plugins
}
return new Plugin(options)
}
has(name) {
return this.plugins.has(name)
}
getAll() {
return Array.from(this.plugins.keys())
}
}
// Plugin classes
class AnalyticsPlugin {
constructor(options) {
this.trackingId = options.trackingId
}
track(event) {
console.log(`Tracking: ${event} (ID: ${this.trackingId})`)
}
}
class CachePlugin {
constructor(options) {
this.ttl = options.ttl
this.cache = new Map()
}
get(key) {
return this.cache.get(key)
}
set(key, value) {
this.cache.set(key, value)
}
}
// Usage
const factory = new PluginFactory()
factory.register('analytics', AnalyticsPlugin)
factory.register('cache', CachePlugin)
const analytics = factory.create('analytics', { trackingId: 'UA-123456' })
analytics.track('page_view') // Tracking: page_view (ID: UA-123456)
const cache = factory.create('cache', { ttl: 3600 })
cache.set('user:1', { name: 'John' })
console.log(cache.get('user:1')) // { name: 'John' }
console.log(factory.getAll()) // ['analytics', 'cache']
Best Practice Note
This is how we implement factory patterns in CoreUI for flexible component creation. The factory pattern provides a clean abstraction for object creation, allowing runtime decisions about which class to instantiate without coupling code to specific implementations. Always use factories when you need runtime class selection, multiple implementations of an interface, or centralized object creation logic. Consider registration-based factories for plugin systems and dependency injection for testability.
Related Articles
For related patterns, check out how to implement singleton pattern in JavaScript and how to implement builder pattern in JavaScript.



