How to use Singleton Pattern with TypeScript to solve real world problems in web projects makes it possible to Share a single Global Instance throughout the application.
Welcome to the Design Patterns in TypeScript series, this series will introduce some useful Design Patterns in web development using TypeScript.
Design Patterns are very important for web developers and we can code better by mastering them. In this article, I will use TypeScript to introduce the Singleton Pattern .
Singleton Pattern
Singleton Pattern is a common pattern and we usually only need one of several objects, such as public buffers, windown objects in Browsers, etc. Singleton Pattern is used to ensure that there is only one Instance of a Class and to provide a single global access point for it .
Let’s see how to use TypeScript to implement the Singleton Pattern.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> singleton <span class="token operator">:</span> Singleton <span class="token punctuation">;</span> <span class="token comment">// ①</span> <span class="token keyword">private</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// ②</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token function">getInstance</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">:</span> Singleton <span class="token punctuation">{</span> <span class="token comment">// ③</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> Singleton <span class="token punctuation">.</span> singleton <span class="token punctuation">)</span> <span class="token punctuation">{</span> Singleton <span class="token punctuation">.</span> singleton <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> Singleton <span class="token punctuation">.</span> singleton <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
When defining the Singleton
class, there are three important steps:
- Define a
private static Properties
to hold the Instance object; - Define a
private constructor
; - Provide a
public static getInstance()
to get an Instance object.
In VSCode, for private static Properties, VSCode’s IntelliSense
will automatically filter out these Properties
:
For private constructor
, when we create an Instance via new Singleton()
, TypeScript compiler will prompt the following error message:
1 2 | Constructor of class 'Singleton' is private and only accessible within the class declaration.ts(2673) |
With the Singleton
class, try to check if its instances are really unique:
1 2 3 4 | <span class="token keyword">let</span> instance1 <span class="token operator">=</span> Singleton <span class="token punctuation">.</span> <span class="token function">getInstance</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">let</span> instance2 <span class="token operator">=</span> Singleton <span class="token punctuation">.</span> <span class="token function">getInstance</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> instance1 <span class="token operator">===</span> instance2 <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// true</span> |
The easiest way to implement a singleton in JavaScript is to use an object literal :
1 2 3 4 5 6 7 8 9 | <span class="token keyword">const</span> httpClient <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function">get</span> <span class="token punctuation">(</span> url <span class="token punctuation">,</span> config <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send get request</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token parameter">url <span class="token punctuation">,</span> config</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send post request</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
If you need to contain additional private properties
or methods
, you can use the following ways to create a singleton:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">const</span> httpClient <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// private method</span> <span class="token keyword">function</span> <span class="token function">sendRequest</span> <span class="token punctuation">(</span> <span class="token parameter">url <span class="token punctuation">,</span> config</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send request</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token function">get</span> <span class="token punctuation">(</span> url <span class="token punctuation">,</span> config <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">sendRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">,</span> config <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token parameter">url <span class="token punctuation">,</span> config</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">sendRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">,</span> config <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
If you are using ES6, you can declare a singleton using ES Modules very easily:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span> <span class="token function">get</span> <span class="token punctuation">(</span> url <span class="token punctuation">,</span> config <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send get request</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token parameter">url <span class="token punctuation">,</span> config</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send post request</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
Axios is widely used in various web projects and the provided axios
object is also a Singleton object.
1 2 3 4 5 6 | <span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">"axios"</span> <span class="token punctuation">;</span> axios <span class="token punctuation">.</span> <span class="token function">get</span> <span class="token punctuation">(</span> <span class="token string">'/users'</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">then</span> <span class="token punctuation">(</span> <span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> res <span class="token punctuation">.</span> data <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
The example above axios
uses the default configure object, of course Axios also allows us to use the axios.create
function to create a new instance of axios with a custom configuration.
1 2 3 4 5 6 | <span class="token keyword">const</span> instance <span class="token operator">=</span> axios <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> baseURL <span class="token operator">:</span> <span class="token string">'https://mediuem.com/'</span> <span class="token punctuation">,</span> timeout <span class="token operator">:</span> <span class="token number">1000</span> <span class="token punctuation">,</span> headers <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string">'X-Custom-Header'</span> <span class="token operator">:</span> <span class="token string">'Bytefer'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Use cases of Singleton Pattern:
- Objects take too long or resources to create but are often reused over and over again.
- Since Singleton Pattern has only one Instance Instance, memory usage is reduced especially when an object needs to be created and destroyed frequently => can’t optimize performance during creation or destruction, that is luc Singleton Pattern brings out its full power.
However, Singleton Pattern is not always useful, sometimes using it will also be an Anti-Pattern.
For example: If you code Nodejs + Postgres, you are familiar with this pg library, right? Connecting takes time and also takes resources. Wow, then the formula and then apply the formula.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">import</span> <span class="token punctuation">{</span> Pool <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'pg'</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> pool <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Pool</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token parameter">query <span class="token operator">:</span> any</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> rows <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> pool <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> query <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> rows <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
pool
is now a Singleton Instance ok to use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">import</span> <span class="token punctuation">{</span> query <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../'</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">doSomething</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'BEGIN'</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> queryText <span class="token operator">=</span> <span class="token string">'INSERT INTO users(name) VALUES($1) RETURNING id'</span> <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">query</span> <span class="token punctuation">(</span> queryText <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token string">'brianc'</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> insertPhotoText <span class="token operator">=</span> <span class="token string">'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'</span> <span class="token keyword">const</span> insertPhotoValues <span class="token operator">=</span> <span class="token punctuation">[</span> res <span class="token punctuation">.</span> rows <span class="token punctuation">[</span> <span class="token number">0</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> id <span class="token punctuation">,</span> <span class="token string">'s3.bucket.foo'</span> <span class="token punctuation">]</span> <span class="token keyword">await</span> <span class="token function">query</span> <span class="token punctuation">(</span> insertPhotoText <span class="token punctuation">,</span> insertPhotoValues <span class="token punctuation">)</span> <span class="token keyword">await</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'COMMIT'</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span> e <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'ROLLBACK'</span> <span class="token punctuation">)</span> <span class="token keyword">throw</span> e <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> <span class="token function">doSomething</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> |
Opps!!! not good at all in this case then using 1 Instance is counterproductive -> bug
According to node-postgres doc: To perform a transaction with node-postgres , you just need to execute BEGIN/COMMIT/ROLLBACK
queries through an Instance which is fine. Note however: You must use the same Instance client
for all statements in a transaction
. PostgreSQL isolates a transaction
to client
. This means that if you initiate or use transaction
using the pool.query
method, you will get **LỖI**
. It is strongly recommended not to use transaction
with the pool.query
method.
The correct way would be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <span class="token keyword">const</span> <span class="token punctuation">{</span> Pool <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'pg'</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> pool <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Pool</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token comment">// note: we don't try/catch this because if connecting throws an exception</span> <span class="token comment">// we don't need to dispose of the client (it will be undefined)</span> <span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token keyword">await</span> pool <span class="token punctuation">.</span> <span class="token function">connect</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> client <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'BEGIN'</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> queryText <span class="token operator">=</span> <span class="token string">'INSERT INTO users(name) VALUES($1) RETURNING id'</span> <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> client <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> queryText <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token string">'brianc'</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">const</span> insertPhotoText <span class="token operator">=</span> <span class="token string">'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'</span> <span class="token keyword">const</span> insertPhotoValues <span class="token operator">=</span> <span class="token punctuation">[</span> res <span class="token punctuation">.</span> rows <span class="token punctuation">[</span> <span class="token number">0</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> id <span class="token punctuation">,</span> <span class="token string">'s3.bucket.foo'</span> <span class="token punctuation">]</span> <span class="token keyword">await</span> client <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> insertPhotoText <span class="token punctuation">,</span> insertPhotoValues <span class="token punctuation">)</span> <span class="token keyword">await</span> client <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'COMMIT'</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span> e <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> client <span class="token punctuation">.</span> <span class="token function">query</span> <span class="token punctuation">(</span> <span class="token string">'ROLLBACK'</span> <span class="token punctuation">)</span> <span class="token keyword">throw</span> e <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> client <span class="token punctuation">.</span> <span class="token function">release</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
=> When you use a certain Design Pattern, even the formula is the same as the textbook, sometimes it’s a very normal thing. Vietnamese teachers also have a classic sentence: Math is very easy, you just need to apply the formula. It’s true that you just need to apply the formula… but leave the exam room early to submit.
Next time I will also write an article about Anti-Pattern and will dig into this issue. Remember to follow me to read.
Roundup
As always, I hope you enjoyed this article and learned something new.
Thank you and see you in the next posts!
If you find this blog interesting, please give me a like and subscribe to support me. Thank you.