Singleton is object that can have only a single, unique instance, with a single point of access. Node.js module system provides simple way to implement Singleton using module.exports
. Module will be cached when it is accessed using require()
statement. So our module is merely a cached instance although it behaves like a Singleton.
In this tutorial, we’re gonna look at ways to implement this kind of Singleton in Node.js:
- Singleton Object
- Singleton Class
Node.js Singleton Object example
We will create a Bank
object, it has 3 methods:
– deposit()
increases cash
.
– withdraw()
decreases cash
.
– total()
returns cash
.
Bank.js
let cash = 0; const Bank = { deposit(amount) { cash += amount; return cash; }, withdraw(amount) { if (amount <= cash) { cash -= amount; return true; } else { return false; } }, total() { return cash; } } module.exports = Bank; |
Bank
object behaves like a Singleton because we will use module.exports
and require()
statement (not use new
keyword).
app.js
const fund = require('./Bank'); const atm1 = require('./Bank'); const atm2 = require('./Bank'); fund.deposit(10000) atm1.deposit(20) atm2.withdraw(120) console.log(`total-atm1: ${atm1.total()}`) console.log(`total-atm2: ${atm2.total()}`) fund.deposit(2000) console.log(`total-fund: ${fund.total()}`) |
As you can see, we create 3 Bank
s (fund
, atm1
, atm2
), every Bank
object can deposit or withdraw money. But all of the action work with only one the cash
insides Bank
.
Here is the result (run with node app.js
command):
total-atm1: 9900 total-atm2: 9900 total-fund after funding 2000: 11900 |
Node.js Singleton Class example
Now we will make ScoreBoard
like a Singleton.
ScoreBoard.js
class ScoreBoard { constructor() { this.board = []; } join(name) { this.board.push({ name, scores: 0 }); } leave(name) { this.board = this.board.filter(player => player.name !== name); } update(name, scores) { const playerIdx = this.board.findIndex(player => player.name === name); if (playerIdx > -1) { this.board[playerIdx].scores += scores; } } showScores() { return this.board; } getWinner() { return this.sort()[0]; } sort() { return this.board.sort((p1, p2) => p2.scores - p1.scores); } } module.exports = new ScoreBoard(); |
ScoreBoard
object has a board that contains all Player
s and their scores. Everytime a Player
join any Game
(we will create more than one Game
), he will be added to the board.
Game.js
const ScoreBoard = require('./ScoreBoard'); class Game { constructor(name) { this.name = name; } getName() { return this.name; } join(player) { ScoreBoard.join(player.getName()); } } module.exports = Game; |
You can see that we use require('./ScoreBoard')
in many places, because it’s a Singleton, all Players’ scores
will be stored at only one place – ScoreBoard
object.
Player.js
const ScoreBoard = require('./ScoreBoard'); class Player { constructor(name) { this.name = name; } getName() { return this.name; } win(scores) { ScoreBoard.update(this.name, scores); } lose(scores) { ScoreBoard.update(this.name, -scores); } } module.exports = Player; |
Now we create 2 different Game
objects with different Player
objects to join the game. Each Player
win and lose his scores
in separated games.
Notice that we use new
keyword for creating Game
, so Game
is not a Singleton, this allows us to create many Game
objects.
app.js
const ScoreBoard = require('./ScoreBoard'); const Game = require('./Game'); const Player = require('./Player'); const AAGame = new Game('AA-game'); const BBGame = new Game('BB-game'); const jack = new Player('Jack'); const adam = new Player('Adam'); const katherin = new Player('Katherin'); AAGame.join(jack); AAGame.join(adam); BBGame.join(katherin); jack.win(100); jack.win(100); jack.lose(50); adam.lose(100); adam.win(200); katherin.win(100); katherin.win(200); console.log('AA: ', AAGame.getName()); console.log('BB: ', BBGame.getName()); console.log('scores: ', ScoreBoard.showScores()); console.log(`Winner: ${ScoreBoard.getWinner().name}, scores: ${ScoreBoard.getWinner().scores}`); |
Run the code with command: node app.js
. Here is the result:
AA: AA-game BB: BB-game scores: [ { name: 'Jack', scores: 150 }, { name: 'Adam', scores: 100 }, { name: 'Katherin', scores: 300 } ] Winner: Katherin, scores: 300 |
Some notes
– Using require()
statement is case-insensitive. Inconsistent filenames will cached as separated instances. For example:
const ScoreBoard = require('./ScoreBoard'); const ScoreBoard = require('./SCOREBOARD'); |
– To make Singleton Class, we instantiate the class with
new
keyword before exporting it out of the module:module.exports = new ScoreBoard(); |