<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bruno Paulino</title>
    <description>The latest articles on DEV Community by Bruno Paulino (@bpaulino0).</description>
    <link>https://dev.to/bpaulino0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F263418%2F464eea65-cf43-4d77-8b62-7498bc830dfe.jpg</url>
      <title>DEV Community: Bruno Paulino</title>
      <link>https://dev.to/bpaulino0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bpaulino0"/>
    <language>en</language>
    <item>
      <title>Ask away, like you career depends on it</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Mon, 23 Oct 2023 20:57:18 +0000</pubDate>
      <link>https://dev.to/bpaulino0/ask-away-like-you-career-depends-on-it-26l5</link>
      <guid>https://dev.to/bpaulino0/ask-away-like-you-career-depends-on-it-26l5</guid>
      <description>&lt;p&gt;It has been a while since I wanted to write this but I've been putting this on the side, until this week when a more junior engineer at my current job approached me with some questions around a work-related topic. I noticed that he was a bit afraid of asking, perhaps concerned that he would come across as unprepared or stupid. Turns out, his question was really interesting and sparked a great conversation with surprisingly new learnings for him and myself.&lt;/p&gt;

&lt;p&gt;This really motivated me to write this piece because I have once been like this and was constantly ashamed of asking questions, fearing to sound stupid or dumb, which eventually led me to lose a few opportunities.&lt;/p&gt;

&lt;p&gt;But there was a point where I stopped worrying about this and the result of it was really surprising: It changed my life and career in several ways I could never imagine and today I want to share a few situations where I simply asked, already expecting the worst outcome, but it turned out to work much better than I could ever believe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the chance to study without losing my job
&lt;/h2&gt;

&lt;p&gt;Before going to university and becoming a full-time software engineer, I was a police officer. This was a particularly challenging job because I was part of a military force in my home state, which as you can probably guess, it's a job&lt;br&gt;
that involves a lot of rules, discipline and hierarchy. You don't simply go around asking people, you need to follow the chain of command and crossing this line can have consequences.&lt;/p&gt;

&lt;p&gt;After a while, I figured this job wasn't really what I wanted to do, so I went to university. There was one little challenge: The classes had many conflicts with my work schedule, so I would have to figure out how I could work and study at the same time.&lt;/p&gt;

&lt;p&gt;I was a little worried that it would be troubling to find a work schedule that could support my studies, so I was already thinking that I would have to give up university given that I couldn't afford to lose my job. Either way, I simply&lt;br&gt;
went to my boss and asked if he could support me in figuring this out. Surprisingly, he was quite happy that I was going to university and went out of his way to help me out. He figured out a way to change my work schedule where I could work a few nights and weekends and I wouldn't have to miss classes much.&lt;/p&gt;

&lt;p&gt;It all worked out and I managed to study just fine. I missed one class here and there, but I wasn't expecting to attend at all. That was a huge win already.&lt;/p&gt;

&lt;p&gt;Everything was going well until nearing the end of my studies, where I managed to get a scholarship with all expenses paid to go to the US.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the chance to study abroad without losing my job
&lt;/h2&gt;

&lt;p&gt;I was starting to feel defeated. There was no way for me to go, spend almost 2 years in the US without losing my job. I thought "That is it, I am gonna lose this opportunity. I cannot afford to support my family without my job".&lt;/p&gt;

&lt;p&gt;Although I knew the chances of keeping my job while away were really slim, I had to ask my boss again. I thought "What have I got to lose? I did not have this opportunity in the first place. Worst case, I graduate here and eventually get another job".&lt;/p&gt;

&lt;p&gt;So I went and talked to my boss. Surprisingly, he was one of the happiest guys around when he heard the news. He was very honest and told me he had no clue whether I could go and keep my job, but he would do everything on his power to help me out. He asked me for a few days so he could figure something out.&lt;/p&gt;

&lt;p&gt;A week had gone by. It was a weekend evening when he called me and said "Hey Bruno, I've figured something out. Let's try it next week". After talking to him I was really surprised, I had no idea what that could be, but I was willing to try anything.&lt;/p&gt;

&lt;p&gt;Next week, once I was back to the office, he explained to me that there was some sort of particular law which allowed the police chief to authorize any particular officer to go anywhere and take a course that could benefit the police.&lt;/p&gt;

&lt;p&gt;I HAD NO CLUE THIS EVEN EXISTED! Neither did my boss, until he talked to his colleagues about my situation and a good friend of his, who turns out was a really good lawyer, explained it to him. Long-story short, we went through all the paperwork internally and eventually I got the approval to go, enjoy my&lt;br&gt;
hard-earned scholarship in the US while I kept my job, including part of my salary.&lt;/p&gt;

&lt;p&gt;The following picture is me and a couple of friends making a Brazilian BBQ in one of many sunny days in Florida:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbor5fmbocl8g0u38erqd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbor5fmbocl8g0u38erqd.jpg" alt="me and a couple of friends making a Brazilian BBQ in one of many sunny&amp;lt;br&amp;gt;
days in Florida" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything was going well until I was nearing the end of my studies in the US. I was given the opportunity to find an internship and stay for another 3 months. If I couldn't find it, I would have to head back to my country in 2 weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the chance to work abroad and bring along 10 other colleagues
&lt;/h2&gt;

&lt;p&gt;And the time to find an internship came. I was looking for jobs everywhere in the little town I was based in (Melbourne, Florida).&lt;/p&gt;

&lt;p&gt;If you are not familiar with this area in Florida, you have probably heard about the "Space Coast", which is this area in the USA where rockets go around, where NASA is based at, together with many companies working with aerospace tech.&lt;/p&gt;

&lt;p&gt;This was super exciting to me during my whole time there, but when it came to find an internship, I quickly found that many of these companies have contracts with the American Department of Defense and, due to security reasons, if you are not an American citizen, they won't even consider you as a suitable candidate. This is even written in the job ad as a requirement.&lt;/p&gt;

&lt;p&gt;So my search for an internship immediately became quite difficult. My American friends were getting job interviews left and right, while I was only getting rejections. I thought "That is it, I'm going back to Brazil without some international experience. But that is fine, I got really far, this is already good enough".&lt;/p&gt;

&lt;p&gt;I was once again at the state of feeling defeated. I felt I've not tried hard enough to get an internship. So I started reaching out to other foreign colleagues that got internships in the past. Until one of them gave me a hint about a jet research lab that took him in the past summer. I looked it up and to my surprise, this was a Brazilian company (Embraer), with a research facility in the same town I was based at.&lt;/p&gt;

&lt;p&gt;Looking at their website and LinkedIn page, I couldn't find any available opportunity. So I started checking the folks that work there. I managed to find one of the HR department heads on LinkedIn. I couldn't contact her directly given that we were not connected on the platform, so I just tried to guess her work email address.&lt;/p&gt;

&lt;p&gt;I simply tried:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subject:&lt;/strong&gt; Looking for an Internship Opportunity&lt;br&gt;
&lt;strong&gt;to:&lt;/strong&gt; &lt;a href="mailto:firstname.lastname@embraer.com"&gt;firstname.lastname@embraer.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;{Email body left out for brevity}&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I sent her an email. I didn't receive any error delivery message, so I assumed it worked and her email existed. This already felt like a small win. But even then, my chances of getting a reply were really slim. Worse yet, even if I get a&lt;br&gt;
reply, what are the chances of getting an actual internship opportunity? Very likely zero. But it was worth asking it anyways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three days later, a unexpected surprise
&lt;/h3&gt;

&lt;p&gt;Three days have gone by since I sent her that email. Around that time, I've already mostly given up my internship search and was already preparing to travel back home. Then suddenly, I had this one unread message in my inbox. The message looked something like the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Re:&lt;/strong&gt; Looking for an Internship Opportunity&lt;/p&gt;

&lt;p&gt;Hi Bruno, Thank you for reaching out. I'm really glad you wrote me regarding that. I am trying to contact someone at Florida Tech for a couple of weeks now, but I couldn't get a hold of anyone from the administration.&lt;/p&gt;

&lt;p&gt;We actually have 10 available spots for the Summer Internship Program at Embraer and we are looking for students like you. Could you send me your resume and if you have more colleagues looking for internship opportunities in IT, please let me know.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After reading this email, I couldn't believe it... I not only found a potential opportunity for me, but somehow I could also help ten other colleagues?&lt;/p&gt;

&lt;p&gt;I am pretty sure that you, the reader, might be thinking that this is some sort of joke and to be honest, I couldn't believe it myself when I read this email.&lt;/p&gt;

&lt;p&gt;Fast-forward 2 weeks later, I managed to get one of the spots for that internship program, and believe it or not, I also managed to bring along 10 other friends that were in a similar situation. All of them got hired.&lt;/p&gt;

&lt;p&gt;This internship was one of the coolest experiences in my professional career and I could not have been happier about its outcome. I made many friends from several different countries, I learned a sh*t ton of new stuff and I even got paid for it.&lt;/p&gt;

&lt;p&gt;Here is a picture of me, a few of my colleagues that joined me after that lucky email I sent, and one of our great managers, getting the chance to experience the Embraer Legacy aircraft, the same aircraft which the queen of England used to fly around back then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxufu63d7swi4r2dkxl4i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxufu63d7swi4r2dkxl4i.jpg" alt="Bruno inside a luxury aircraft with the Embraer team" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What can you learn from this?
&lt;/h2&gt;

&lt;p&gt;I think there was a good lesson I learned from these experiences. I had several opportunities during my life that I could have completely missed out if I didn't ask for help back then. And in other situations, where I had no clue whether something could work, by simply asking around, I could carve out the opportunity I needed, like that internship.&lt;/p&gt;

&lt;p&gt;In retrospect, I feel quite lucky. I was at the right place, at the right time and thankfully had the right people by my side. Things could have turned out much differently, but by the simple fact that I asked around, I managed to increase the likelihood of these lucky moments to come to reality.&lt;/p&gt;

&lt;p&gt;So if you are ever wondering whether you should ask, just go, ask. You should, of course, do your homework, research around what you are looking for and try to help yourself first. If you are reaching out to a mentor, a friend or your boss, try to offer a few ideas on how this person can help you with right away.&lt;/p&gt;

&lt;p&gt;But even then, if you are clueless, ask anyways. The worst outcome is likely that things will remain the way they are. And in case things don't work out, you at least tried and can already think about other plans.&lt;/p&gt;

&lt;p&gt;Most people have good intentions and are very likely able to help you. Sometimes you hit a jerk here and there, but you know, they are not the common place. They are also missing the opportunity to be part of your story, so don't hold questions to yourself.&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>career</category>
      <category>learning</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Distributed locks in Node.js</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Wed, 26 Jul 2023 05:04:15 +0000</pubDate>
      <link>https://dev.to/bpaulino0/distributed-locks-in-nodejs-h16</link>
      <guid>https://dev.to/bpaulino0/distributed-locks-in-nodejs-h16</guid>
      <description>&lt;p&gt;Often times, you need to build a feature that requires coordinated access to a certain resource. A common use-case is around queues where jobs can be enqueued to be processed in the background.&lt;/p&gt;

&lt;p&gt;These jobs can either be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Picked up by a fleet of workers and processed in parallel. The order they start and finish doesn't matter.&lt;/li&gt;
&lt;li&gt;Picked up and processed serially. The order they are processed matters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first case is generally simple enough. You generally don't have to concern yourself with race conditions. Jobs can be processed successfully (or failed and retried) in any order they come in.&lt;/p&gt;

&lt;p&gt;But what happens when you must guarantee that their processing order be respected? If you have a single server and a single worker thread, then it's simple. Jobs will be processed serially and their ordering will be respected.&lt;/p&gt;

&lt;p&gt;And what happens when you have a fleet of workers deployed across different servers? You will need to "elect" one of your workers to be the one processing these jobs. Better yet, you must make sure that if that worker dies or gets removed from your servers pool, another healthy worker must pick-up the jobs and continue processing them in some sort of handover fashion. This is where a distributed lock algorithm is handy.&lt;/p&gt;

&lt;p&gt;Here is our demo running where we simulate two servers competing to acquire the lock and how the lock handover works when a server goes down:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6wdpki7w7mqn5dcziga.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6wdpki7w7mqn5dcziga.gif" alt="two terminal windows showing two instances of our app running and acquiring the lock" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are a savvy Node.js engineer and just want to see the code, &lt;a href="https://github.com/brunojppb/node-distributed-lock"&gt;here is the Github repo ready for you.&lt;/a&gt; Feel free to leave a Github star so folks can find this distributed lock template more easily.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Locks on a single node
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Lock_(computer_science)"&gt;A lock&lt;/a&gt; is a common mechanism that helps the programmer to restrict access (mutual exclusion) to certain resources, allowing you to control which thread can read or write to shared data.&lt;/p&gt;

&lt;p&gt;In a single-thread, event-loop based runtime like Node.js, you generally don't need locks,  but in multi-threaded environments like in &lt;a href="https://www.rust-lang.org/"&gt;Rust&lt;/a&gt;, you usually need to use the primitives for atomic operations provided by the standard library like &lt;a href="https://doc.rust-lang.org/std/sync/struct.Mutex.html"&gt;std::sync::Mutex&lt;/a&gt;, which guarantees that a given code path is only accessed by one thread at a time.&lt;/p&gt;

&lt;p&gt;Primitives like this are super useful, but our use-case goes beyond one single server. Remember that we are crossing the boundaries of a server here and we must make sure that other workers do not process our jobs in case one worker is already processing them.&lt;/p&gt;

&lt;p&gt;We need to extend the concept of a lock and make it distributed, where other servers can also see who is holding the lock and act accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making our lock distributed with Redis
&lt;/h2&gt;

&lt;p&gt;Given that we must share the lock state across servers, we need some sort of distributed data sharing mechanism that can synchronize this data across our fleet of workers. We could somehow connect our workers and send messages among them, but that would be quite complex to pull it off. Another alternative is to use a shared data store like a database that can make the lock state instantly available across our servers. Redis fits perfectly for that purpose.&lt;/p&gt;

&lt;p&gt;Redis is a fantastic technology. If you are not familiar with it, &lt;a href="https://github.com/redis/redis"&gt;Redis&lt;/a&gt; is an in-memory database. It is incredibly fast and can act as a shared cache across your cluster of servers.&lt;/p&gt;

&lt;p&gt;Redis provides APIs for atomic operations, which will support us in guaranteeing that only a single worker can acquire a lock at any given time. We will see that in action soon, but this blogpost largely follows the pattern suggested by Redis itself in its &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/"&gt;patterns manual here&lt;/a&gt; called &lt;strong&gt;Redlock&lt;/strong&gt; for cases where we have a single instance of Redis.&lt;/p&gt;

&lt;p&gt;To facilitate the understanding on how the distributed lock works, have a look in the following diagram. We have a pool of four workers which are Node.js servers responsible for processing our hypothetical serial queue. These four servers will concurrently try to acquire the lock by setting a key-value pair in Redis. The first server that manages to do it, "wins" and will hold the lock. With the lock at hand, this server should be able to start processing jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82dgpm4psv29u3f8sq85.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82dgpm4psv29u3f8sq85.jpg" alt="Lock 1" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, in case this server needs to be shutdown (e.g. during a deployment), the lock should be released, giving the chance to one of the other servers to acquire it and continue to process the background jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtt8r4lvihrabi249lg6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtt8r4lvihrabi249lg6.jpg" alt="Lock 2" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-safe with expiring keys
&lt;/h2&gt;

&lt;p&gt;So far, our example has been focusing on releasing the lock when a server needs to shutdown. But what happens during a hardware failure? What if your app panics and crashes out of nowhere? This situation could lead you to a &lt;a href="https://en.wikipedia.org/wiki/Deadlock"&gt;deadlock&lt;/a&gt; where no other server from your server pool will be able to acquire the lock to keep processing jobs.&lt;/p&gt;

&lt;p&gt;Redis provides a very useful feature where whenever you set a key-value pair, you can also define how long this value can last on its memory. Redis will clean it up automatically after the given time passes.&lt;/p&gt;

&lt;p&gt;We will be seeing how to set a key with an expiry time very soon, but here is how it will look like at a high-level:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqphje48n2rt2akzttbio.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqphje48n2rt2akzttbio.jpg" alt="Lock 3" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Renewing the lock from time to time
&lt;/h3&gt;

&lt;p&gt;Given that our lock, represented by the entry we store in Redis, expires automatically after a certain time, we must make sure that our worker that holds the lock can renew the lock from time to time to make sure that it's still the one holding the lock.&lt;/p&gt;

&lt;p&gt;To perform that, Redis also provides an API to specifically change the expiry time of a given key. The &lt;a href="https://redis.io/commands/expire/"&gt;EXPIRE&lt;/a&gt; command will help us to accomplish that.&lt;/p&gt;

&lt;p&gt;But enough theory, let's dip into the code and see that in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kicking off with a Demo
&lt;/h2&gt;

&lt;p&gt;We start by demonstrating this implementation in action so you get a feeling of how it works and then we will have a look at the code, step-by-step.&lt;/p&gt;

&lt;p&gt;To kickstart this project, I've used &lt;a href="https://remix.run/"&gt;Remix&lt;/a&gt; with the &lt;a href="https://remix.run/docs/en/main/other-api/adapter"&gt;Express adapter&lt;/a&gt; so I can&lt;br&gt;
have a super quick project already setup with TypeScript. The code is available &lt;a href="https://github.com/brunojppb/node-distributed-lock"&gt;here&lt;/a&gt;, so just clone it, &lt;code&gt;npm install&lt;/code&gt; to kick things off.&lt;/p&gt;

&lt;p&gt;But before running our Node.js app, we need Redis available as a dependency so our app can connect to it. The simplest way to install Redis is to use &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;. Our project already comes with a handy &lt;code&gt;docker-compose&lt;/code&gt; file that bootstraps Redis for you.&lt;/p&gt;

&lt;p&gt;So make sure that Docker is running and just execute the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to run Redis in the background, which will be a perfect fit for our app. Now, copy the &lt;code&gt;.env.example&lt;/code&gt; file and rename it to &lt;code&gt;.env&lt;/code&gt;. This should be enough for starting our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you watch your server logs, you should see a few logs showing that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your node tried to acquire the lock&lt;/li&gt;
&lt;li&gt;The lock was successfully acquired&lt;/li&gt;
&lt;li&gt;It's doing some (simulated) work like processing background jobs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to acquire lock { workerId: '01H63B5EAKQZTCDAT8AAVQ9WAG' }
Lock acquired. Starting work { workerId: '01H63B5EAKQZTCDAT8AAVQ9WAG' }
Doing some heavy work... { workerId: '01H63B5EAKQZTCDAT8AAVQ9WAG' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open a new terminal window and fire up a new process from our Node.js app on a different port with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3001 npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simulates having another server of your app running and trying to acquire the lock. You will only see that your new process will keep trying to acquire the lock every 5 seconds. You will see logs like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to acquire lock { workerId: '01H63B9M322QAP09D6EJ19SGWC' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, shutdown your app on your first terminal window, the one holding the lock. Have a look at the second terminal window now. It should pick up the lock and start processing the background jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying to acquire lock { workerId: '01H63B9M322QAP09D6EJ19SGWC' }
Lock acquired. Starting work { workerId: '01H63B9M322QAP09D6EJ19SGWC' }
Doing some heavy work... { workerId: '01H63B9M322QAP09D6EJ19SGWC' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what we want to accomplish. We want to make sure that our background jobs are never left behind. When a worker goes down, another worker picks it up and continue processing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice that we always log the worker ID, so it's easy to identify which worker&lt;br&gt;
node has the lock and which ones are just waiting and trying to acquire it.&lt;br&gt;
The Node ID is a fundamental part of this lock implementation given that we&lt;br&gt;
use it to identify which node is actively holding the lock. The Node ID is&lt;br&gt;
automatically assigned during server startup time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Managing our lock state using a state machine
&lt;/h2&gt;

&lt;p&gt;You are probably asking yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does each node know how and when to acquire the lock or wait to try&lt;br&gt;
acquiring it again?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where state machines come in. They help us to model our state transitions in a very elegant way.&lt;/p&gt;

&lt;p&gt;I personally enjoy using &lt;a href="https://github.com/statelyai/xstate"&gt;xstate&lt;/a&gt;, a finite state machine library that helps us to model how our state looks like and the transition between them, including failure modes.&lt;/p&gt;

&lt;p&gt;The folks from &lt;a href="https://stately.ai/"&gt;Stately&lt;/a&gt;, the maintainers of xstate, even provide a Web UI for modeling and visualizing our state machine. Here is how it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupt6nnzhpx3r92jref00.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupt6nnzhpx3r92jref00.jpg" alt="Lock xstate machine" width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also play with &lt;a href="https://stately.ai/viz/7ad688b3-4910-41d1-9fe7-70b2a426d5c4"&gt;this interactive demo here&lt;/a&gt;. On this UI, you can simulate all the state transitions and visually see how the state machine works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kicking off our state machine during server startup
&lt;/h2&gt;

&lt;p&gt;The main goal of this state machine is to coordinate the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once our server starts up, we must signal to the state machine that it's time to attempt to acquire the lock:

&lt;ul&gt;
&lt;li&gt;If the lock is acquired successfully, our worker can start processing the background jobs. In the meantime, our worker also needs to renew the lock to make sure that it can continue processing background jobs.&lt;/li&gt;
&lt;li&gt;In case it can't acquire it, either because of another worker that has already acquired it or because Redis is unavailable, our worker needs to sit on an idle state for a few seconds and then later on attempt to acquire the lock again.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't worry if you are not familiar with state machines. The xstate &lt;a href="https://xstate.js.org/docs/guides/introduction-to-state-machines-and-statecharts/#states"&gt;introductory article&lt;/a&gt; does a fantastic job in introducing you the concept and guides you through creating your first state chart, but let's walk through the code to see how the entire process works and I will be referencing our demo project with links here so it's easy for the reader to track it back to the code.&lt;/p&gt;

&lt;p&gt;Once we start listening to HTTP requests, &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/server.ts#L36-L39"&gt;here&lt;/a&gt; we can start our worker with a call to &lt;code&gt;startLockWorker&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Express server started at port &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;startLockWorker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's head to the worker implementation of &lt;code&gt;startLockWorker&lt;/code&gt; &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/worker/index.ts#L64-L67"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startLockWorker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRY_ACQUIRE_LOCK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have an instance of a &lt;code&gt;service&lt;/code&gt;. While you can read more about them &lt;a href="https://stately.ai/blog/2021-04-30-should-this-be-an-action-or-a-service#services"&gt;here&lt;/a&gt;, Services wrap our state machines and allow us to observe how the state moves&lt;br&gt;
forward in time, allowing us to hook up callbacks to it and monitor how our state changes over time.&lt;/p&gt;

&lt;p&gt;We first &lt;code&gt;start&lt;/code&gt; our service so it listens to events and we immediately send it a &lt;code&gt;TRY_ACQUIRE_LOCK&lt;/code&gt;. This event will kick-off our state machine, which internally will trigger its internal services to try to acquire the lock. We will have a&lt;br&gt;
look at our actual state machine code soon.&lt;/p&gt;

&lt;p&gt;If you look at where we create this &lt;code&gt;service&lt;/code&gt; instance, you will see  &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/worker/index.ts#L53-L62"&gt;the following call&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interpret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;createLockMachine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;acquireWorkerLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;releaseWorkerLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;renewWorkerLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;consumeResource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stopConsumingResource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is effectively where we create an instance of our state machine when calling &lt;code&gt;createLockMachine&lt;/code&gt;. This call takes an object with several parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;workerId&lt;/strong&gt;: Gives us context on which worker this state machine belongs to. If you are familiar with React state management, &lt;code&gt;context&lt;/code&gt; in xstate is similar to React state. When transitioning between xstate "states", you usually modify its context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;acquireLock&lt;/strong&gt;: An async callback that tries to acquire the lock. It resolves when the lock is acquired successfully or rejects the promise by throwing an error when it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;releaseLock&lt;/strong&gt;: An async callback that tries to release the lock. It resolves when the lock is released successfully or rejects the promise by throwing an error when it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;renewLock&lt;/strong&gt;: An async callback that tries to renew the lock when the worker holds it. It resolves when the lock is renewed successfully or rejects the promise by throwing an error when it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startWork&lt;/strong&gt;: An async callback that starts doing the actual background job processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stopWork&lt;/strong&gt;: An async callback that stops any background job processing. This is important for cases where a worker doesn't manage to renew the lock so it needs to stop and wait.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These callbacks are our services interface which allow us to make our state machine reusable. We can reuse the same state machine to manage different distributed locks across our application by just creating different instances of it and passing the service callbacks that do the actual business logic.&lt;/p&gt;

&lt;p&gt;Now let's have a look at &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/worker/machine.ts#L51-L188"&gt;our state machine&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLockMachine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;CreateMachineArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;predictableActionArguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lock-worker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cleanup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;TRY_ACQUIRE_LOCK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acquiring_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stopWork&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;releasing_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;releasing_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;acquiring_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acquireLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;working&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waiting_to_acquire_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;working&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;startWork&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renew_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;renew_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renewLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;working&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pause_work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;pause_work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stopWork&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waiting_to_acquire_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waiting_to_acquire_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;releasing_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;releaseLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="na"&gt;waiting_to_acquire_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acquiring_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Trying to acquire lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Renewing lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Releasing lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lock acquired. Starting work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stop work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, don't be overwhelmed. This is how xstate lets us declare our state machine. It's mostly a JavaScript object with a specific structure.&lt;/p&gt;

&lt;p&gt;The main thing you want to watch out for is how our machine transition from one state to another. Let's have a look at what happens when our machine enters the &lt;code&gt;acquiring_lock&lt;/code&gt; state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;acquiring_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acquireLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;working&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waiting_to_acquire_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it enters this state, it &lt;code&gt;invoke&lt;/code&gt;s the source (src) service called &lt;code&gt;acquireLock&lt;/code&gt;. In our case, service calls are async functions that when resolved successfully will use the &lt;code&gt;onDone&lt;/code&gt; transition and will move to the &lt;code&gt;target&lt;/code&gt; state, which in this case is &lt;code&gt;working&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the promise returned by this callback rejects, it will enter the &lt;code&gt;onError&lt;/code&gt; block and will transition to the &lt;code&gt;waiting_to_acquire_lock&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;Let's have a look at the &lt;code&gt;working&lt;/code&gt; state &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/main/app/worker/machine.ts#L105-L114"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;working&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;startWork&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renew_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, notice that it does not have any target states in the &lt;code&gt;invoke&lt;/code&gt; clause. This is by design given that when our worker is performing background job processing, we want it to remain that way for as long as it can. But it does have a &lt;code&gt;src&lt;/code&gt; service called &lt;code&gt;startWork&lt;/code&gt;. This is our service callback that allows our worker to effectively start processing the background jobs.&lt;/p&gt;

&lt;p&gt;Also notice that we have an &lt;code&gt;after&lt;/code&gt; block. This is how xstate lets us declare state transitions that will trigger automatically after a given period of time. In this case, whenever our state machine is on the &lt;code&gt;working&lt;/code&gt; state, after 5&lt;br&gt;
seconds, it will transition to the &lt;code&gt;renew_lock&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;Let's head to the &lt;code&gt;renew_lock&lt;/code&gt; state &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/worker/machine.ts#L116-L126"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;renew_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renewLock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onDone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;working&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pause_work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This state is interesting. It will invoke a service called &lt;code&gt;renewLock&lt;/code&gt; and in case it succeeds, it will transition back to the &lt;code&gt;working&lt;/code&gt; state, which will give a chance to our worker to keep chugging along with our background jobs. For the cases where it fails, our target state is &lt;code&gt;pause_work&lt;/code&gt; which in turn also has a src service that notifies our worker to stop any background processing.&lt;/p&gt;

&lt;p&gt;After this tour, you might be wondering how these string identifiers are turned into function calls? Let's have a look at our &lt;code&gt;services&lt;/code&gt; section in our state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Trying to acquire lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Renewing lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Releasing lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lock acquired. Starting work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;startWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stop work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;stopWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the second argument to the &lt;code&gt;createMachine&lt;/code&gt; call. Notice that the key names here all match with the &lt;code&gt;src&lt;/code&gt; we've seen before. This is where we connect the callbacks we've given to our lock machine function signature and our state&lt;br&gt;
machine. This is the abstraction that allows us to make our state machine flexible and reusable across our app.&lt;/p&gt;
&lt;h3&gt;
  
  
  Redis expiring keys in action
&lt;/h3&gt;

&lt;p&gt;We've gone through our state machine and saw how the lock callbacks are being called, but how are we making sure that Redis is operating the way we expect? Let's have a look at how we &lt;code&gt;set&lt;/code&gt; and update the expiry time of our keys using &lt;a href="https://github.com/redis/ioredis"&gt;ioredis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's head to our &lt;code&gt;acquireLock&lt;/code&gt; function &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/services/lock.ts#L10-L30"&gt;here:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;lockValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;expireAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;lockValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;expireAfterInSeconds&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using native redis commands to set our key. The secret sauce is within the &lt;code&gt;NX&lt;/code&gt; and &lt;code&gt;EX&lt;/code&gt; arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NX&lt;/strong&gt;: set if Not eXists. Returns 'OK' on successful cases, null otherwise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EX&lt;/strong&gt;: set with expiration time. The key will be removed after the elapsed time (given in seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a fundamental part of this implementation. Redis allows us to have keys that automatically expire and get deleted from its memory, which guarantees that we won't face &lt;a href="https://en.wikipedia.org/wiki/Lock_(computer_science)"&gt;lock contention&lt;/a&gt;. This covers the eventual case where a server crashes and don't get the chance to release the lock, which would cause a major issue for all our other workers that would be forever waiting to acquire the lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renewing the lock by extending the expiry time
&lt;/h3&gt;

&lt;p&gt;Whenever our worker holds the lock, it attempts to renew the lock from time to time. This is also covered by a Redis API. let's have a look at our renewLock function &lt;a href="https://github.com/brunojppb/node-distributed-lock/blob/e2ef7d65847fe7ed165dffd4669a9f58415da1f6/app/services/lock.ts#L35-L55"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renewLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;lockValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;expireAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Lock is available, we can attempt to acquire it again.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expireAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lockValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Lock is still held by this worker&lt;/span&gt;
    &lt;span class="c1"&gt;// we can safely renew it by extending its expiry time&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expireAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lock held by another node. Can neither renew or acquire it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the &lt;code&gt;ioredis&lt;/code&gt; API for expiring keys. This API allows to either eliminate keys from Redis immediately if we set the expiry time to zero or to extend the expiry time of an existing key for the given seconds.&lt;/p&gt;

&lt;p&gt;This is exactly what we do here. Whenever our worker holds the lock, we just extend the expiry time. Notice how the &lt;code&gt;workerId&lt;/code&gt; plays a major role here. We use it as the &lt;code&gt;lockValue&lt;/code&gt;. This way we make sure that only the worker holding the lock can safely renew it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;I acknowledge that I mostly glanced over most of the concepts here, but the important thing is that you now have all the building blocks to build a distributed lock in Node.js, including a demo project (&lt;a href="https://github.com/brunojppb/node-distributed-lock"&gt;available here&lt;/a&gt;) that you can use as a reference.&lt;/p&gt;

&lt;p&gt;Though, one step further on this demo is to increase the availability of our Redis instance. Right now, we deal with only a single instance of Redis, but what happens if Redis goes down? To improve this implementation, you should look&lt;br&gt;
into having a Redis cluster with multiple master nodes. That is where you would actually implement the &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/#the-redlock-algorithm"&gt;Redlock algorithm&lt;/a&gt; on its full capacity.&lt;/p&gt;

</description>
      <category>node</category>
      <category>distributedsystems</category>
      <category>typescript</category>
      <category>redis</category>
    </item>
    <item>
      <title>Modern Webapps with React, Phoenix, Elixir and TypeScript</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Fri, 21 Jan 2022 14:22:00 +0000</pubDate>
      <link>https://dev.to/bpaulino0/modern-webapps-with-react-phoenix-elixir-and-typescript-4g8d</link>
      <guid>https://dev.to/bpaulino0/modern-webapps-with-react-phoenix-elixir-and-typescript-4g8d</guid>
      <description>&lt;p&gt;I've started working on a side project this year and the tech stack I have chosen was the &lt;a href="https://elixir-lang.org/" rel="noopener noreferrer"&gt;Elixir lang&lt;/a&gt; due to its functional design and fault tolerance (Thanks to the &lt;a href="https://www.erlang.org/" rel="noopener noreferrer"&gt;Erlang VM&lt;/a&gt;) so the &lt;a href="https://www.phoenixframework.org/" rel="noopener noreferrer"&gt;Phoenix framework&lt;/a&gt; was a natural choice for me.&lt;/p&gt;

&lt;p&gt;While Phoenix provides a very interesting programming model called &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html" rel="noopener noreferrer"&gt;LiveView&lt;/a&gt;, I wanted to stick with the frontend stack I'm most familiar with which is &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;. Besides using it heavily in my day job, I also really appreciate the ecosystem around it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are a savvy Elixir engineer and just want to see the code,&lt;br&gt;
&lt;a href="https://github.com/brunojppb/React-Phoenix-TS" rel="noopener noreferrer"&gt;here is the Github repo ready for you.&lt;/a&gt; Feel free to leave a Github star so folks can find this template more easily.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wanted to come up with a solid Phoenix project where I can get all the benefits from Elixir and Phoenix itself, but also be flexible enough by not coupling my React frontend with Phoenix. My requirements were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be able to use &lt;a href="https://webpack.js.org/concepts/hot-module-replacement/" rel="noopener noreferrer"&gt;Hot Module Replacement&lt;/a&gt; during frontend development.&lt;/li&gt;
&lt;li&gt;Run the React frontend in a separate process from the Phoenix app&lt;/li&gt;
&lt;li&gt;During development, changes on the React frontend do not trigger the elixir compiler&lt;/li&gt;
&lt;li&gt;During development, changes on the Phoenix app do not trigger frontend recompilation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/pt-BR/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS&lt;/a&gt;. I don't want to think about it. It's a no-brainer if we bundle all our apps together under the same domain.&lt;/li&gt;
&lt;li&gt;In production, serve the React frontend under the &lt;code&gt;/app/*&lt;/code&gt; path from Phoenix&lt;/li&gt;
&lt;li&gt;In production, all other routes should be server-rendered, so we can still benefit from serve-side rendering for specific cases like better SEO and dynamic landing pages with a smart &lt;a href="https://developers.cloudflare.com/cache/about/cache-control" rel="noopener noreferrer"&gt;caching strategy via Cloudflare&lt;/a&gt; using &lt;code&gt;stale-while-revalidate&lt;/code&gt; headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the clear requirements defined above, I managed to make them all work by combining Phoenix and &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;. So let's get our hands dirty, write some code and make this project work! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide assumes that you are already familiar with Elixir, Phoenix and a frontend framework like React, so we skip a few basic concepts and jump straight in. Although, I will be linking some important resources to guide you in case you are just starting with this stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating our Phoenix project
&lt;/h2&gt;

&lt;p&gt;First of, make sure you have the following dependencies installed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Elixir: &lt;a href="https://elixir-lang.org/install.html" rel="noopener noreferrer"&gt;installation guide here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phoenix: &lt;a href="https://hexdocs.pm/phoenix/installation.html" rel="noopener noreferrer"&gt;installation guide here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NodeJS 16 or above: &lt;a href="https://github.com/nvm-sh/nvm#installing-and-updating" rel="noopener noreferrer"&gt;installation guide here using NVM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL: &lt;a href="https://www.postgresql.org/download/" rel="noopener noreferrer"&gt;Download here&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's head to our terminal and create our Phoenix app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.new phoenix_react 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your project is react, &lt;code&gt;cd&lt;/code&gt; into it and fire up the Phoenix server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;phoenix_react
&lt;span class="c"&gt;# Make sure the Postgres database is available for Ecto&lt;/span&gt;
mix ecto.create
&lt;span class="c"&gt;# Start the dev server&lt;/span&gt;
mix phx.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to access your Phoenix app at &lt;code&gt;localhost:4000&lt;/code&gt; and see a page like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fphoenix_web_app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fphoenix_web_app.png" alt="Phoenix App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome! We have got our Phoenix app up and running. Let's bootstrap our React app in an independent directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating our React with TypeScript project
&lt;/h2&gt;

&lt;p&gt;For our React frontend, I've chosen &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; to handle all the tooling for me. It has got all the sane defaults I need for a TypeScript project with React, plus it uses &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;ESBuild&lt;/a&gt; which gives us blazing fast feedback during development.&lt;/p&gt;

&lt;p&gt;To kick things off, leave the Phoenix server running and open up a new terminal window. Still within the Phoenix directory in your terminal, let's use the Vite CLI to create our React project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init vite@latest frontend &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should create our React project under the &lt;code&gt;frontend&lt;/code&gt; directory. Let's install all dependencies and start our Vite dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now head to your browser at &lt;code&gt;localhost:3000&lt;/code&gt;, you should see our React app up and running!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Freact_ts_vite_app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Freact_ts_vite_app.png" alt="React App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding routes to our React app
&lt;/h2&gt;

&lt;p&gt;There is a major difference between Phoenix routes and React routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phoenix routes are mapped to a request to the server, which results in a new template
rendering which results in the whole browser to reload.&lt;/li&gt;
&lt;li&gt;React routes are client-side only, which means that navigating from &lt;code&gt;/app/settings&lt;/code&gt;
to &lt;code&gt;/app/profile&lt;/code&gt; in our React app doesn't mean a new request to the server. It might just mount a new component instantly which might not need server data at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the strategy here is to leverage &lt;a href="https://reactrouter.com/" rel="noopener noreferrer"&gt;React Router&lt;/a&gt; on our React app for any route that is under &lt;code&gt;/app&lt;/code&gt; and whenever the client makes the first request to our app, let's say they are visiting &lt;code&gt;example.com/app&lt;/code&gt; for the first time, Phoenix will handle this initial request and serve the initial HTML together with our React app payload, so the React app can be mounted and take care of the routing from there.&lt;/p&gt;

&lt;p&gt;To make sure that client-side route changes are working, let's add a very basic routing component so we can test if our react app is working. Let's start by installing React Router in our React app. Stop the dev server and execute the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-router-dom@6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open up your favorite text editor and edit our React app file at &lt;code&gt;phoenix_react/frontend/src/App.tsx&lt;/code&gt; with the following components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BrowserRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * During development we can still access the base path at `/`
   * And this hook will make sure that we land on the base `/app`
   * path which will mount our App as usual.
   * In production, Phoenix makes sure that the `/app` route is
   * always mounted within the first request.
   * */&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BrowserRouter&lt;/span&gt; &lt;span class="na"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings Page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SettingsPage&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BrowserRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SettingsPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings Page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My profile&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Music&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;React TS Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome to the homepage&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to visit &lt;code&gt;localhost:3000/app&lt;/code&gt; and see a screen similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Freact_router_app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Freact_router_app.png" alt="React app with routes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try to click around the &lt;code&gt;Home&lt;/code&gt; and &lt;code&gt;Settings Page&lt;/code&gt; links at the top. Notice that it transitions between pages instantly. If you check your Phoenix console, you notice that no requests have been fired to your backend. So far so good.&lt;/p&gt;

&lt;p&gt;Also notice that we now access our React app via the &lt;code&gt;/app&lt;/code&gt; route. This is important and plays a major role when we bundle our application for production and serve it from Phoenix. We are using a small hook to check whether our app was mounted to the &lt;code&gt;/&lt;/code&gt; path and redirect to the base path. This is only relevant for development. In production, Phoenix will make sure that the user is always in the &lt;code&gt;/app&lt;/code&gt; when using our React app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving our React frontend from Phoenix
&lt;/h2&gt;

&lt;p&gt;So far, Phoenix has no clue about our React app. We need to come up with a way to tell Phoenix how to serve our React app once it's bundled and ready to be served as a SPA. For that to work, we can do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build our React app for production with Vite&lt;/li&gt;
&lt;li&gt;Copy our production build to the &lt;code&gt;priv/static&lt;/code&gt; folder so we can use &lt;a href="https://hexdocs.pm/plug/Plug.Static.html" rel="noopener noreferrer"&gt;Plug.Static&lt;/a&gt; to serve our static assets&lt;/li&gt;
&lt;li&gt;Make Phoenix aware about the &lt;code&gt;/app&lt;/code&gt; route so our generated &lt;code&gt;index.html&lt;/code&gt; from vite
can be statically served, which will trigger our React resources to be loaded.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a custom mix task to do the job
&lt;/h3&gt;

&lt;p&gt;To manage point 1 and 2 from the previous section, we can create a custom &lt;a href="https://hexdocs.pm/mix/1.12/Mix.Task.html" rel="noopener noreferrer"&gt;mix task&lt;/a&gt; that can execute all the TypeScript bundling via NPM and coping files around to make our React app ready to be served by Phoenix.&lt;/p&gt;

&lt;p&gt;Our custom mix task will make sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All of our frontend dependencies are installed&lt;/li&gt;
&lt;li&gt;build our frontend for production distribution&lt;/li&gt;
&lt;li&gt;Move the production files to &lt;code&gt;priv/static/webapp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;/priv/static/webapp&lt;/code&gt; path will be picked up by Phoenix later on, but make sure that you add it to your &lt;code&gt;.gitignore&lt;/code&gt; file. We don't want to commit our frontend production bundles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's go ahead and create &lt;code&gt;lib/mix/tasks/webapp.ex&lt;/code&gt; with the following Elixir code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Webapp&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
    React frontend compilation and bundling for production.
  """&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;
  &lt;span class="kn"&gt;require&lt;/span&gt; &lt;span class="no"&gt;Logger&lt;/span&gt;
  &lt;span class="c1"&gt;# Path for the frontend static assets that are being served&lt;/span&gt;
  &lt;span class="c1"&gt;# from our Phoenix router when accessing /app/* for the first time&lt;/span&gt;
  &lt;span class="nv"&gt;@public_path&lt;/span&gt; &lt;span class="s2"&gt;"./priv/static/webapp"&lt;/span&gt;

  &lt;span class="nv"&gt;@shortdoc&lt;/span&gt; &lt;span class="s2"&gt;"Compile and bundle React frontend for production"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"📦 - Installing NPM packages"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--quiet"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;cd:&lt;/span&gt; &lt;span class="s2"&gt;"./frontend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"⚙️  - Compiling React frontend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;cd:&lt;/span&gt; &lt;span class="s2"&gt;"./frontend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"🚛 - Moving dist folder to Phoenix at &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;@public_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# First clean up any stale files from previous builds if any&lt;/span&gt;
    &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-rf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@public_path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-R"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"./frontend/dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@public_path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"⚛️  - React frontend ready."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;a href="https://hexdocs.pm/elixir/1.12/System.html" rel="noopener noreferrer"&gt;System&lt;/a&gt; module, we can interact directly with our host system, so we can issue shell commands when invoking our custom mix task.&lt;/p&gt;

&lt;p&gt;Let's try it out. Stop your Phoenix server and execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix webapp

&lt;span class="c"&gt;# You should see an outout similar to the following:&lt;/span&gt;
15:48:13.605 &lt;span class="o"&gt;[&lt;/span&gt;info]  📦 - Installing NPM packages
15:48:15.034 &lt;span class="o"&gt;[&lt;/span&gt;info]  ⚙️  - Compiling React frontend
15:48:19.611 &lt;span class="o"&gt;[&lt;/span&gt;info]  🚛 - Moving dist folder to ./priv/static/webapp
15:48:19.618 &lt;span class="o"&gt;[&lt;/span&gt;info]  ⚛️  - React frontend ready.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our frontend is ready to be served by Phoenix now. But there is one little change we have to make to our Vite configuration so our Frontend static assets can be delivered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the webapp base path discoverable
&lt;/h2&gt;

&lt;p&gt;By default, Phoenix serves static content from the &lt;code&gt;priv/static&lt;/code&gt; directory using the base route &lt;code&gt;/&lt;/code&gt;. For instance, if we have a JPG file at &lt;code&gt;priv/static/assets/picture.jpg&lt;/code&gt;, Phoenix will make this resource available at &lt;code&gt;/assets/picture.jpg&lt;/code&gt; to the public.&lt;/p&gt;

&lt;p&gt;We want that to happen, but for our web app, static resources will be under the &lt;code&gt;/webapp/&lt;/code&gt; path. Luckily, this is extremely simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite base path for production
&lt;/h3&gt;

&lt;p&gt;Since we want to serve our Web app from &lt;code&gt;priv/static/webapp&lt;/code&gt;, we have to make sure that during our production build, Vite should append the &lt;code&gt;/webapp/&lt;/code&gt; base path to all our resources. This is paramount for our app to work.&lt;/p&gt;

&lt;p&gt;Vite provides a specific configuration entry for that. Let's go ahead and edit our &lt;code&gt;frontend/vite.config.ts&lt;/code&gt; file with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="c1"&gt;// using the `webapp` base path for production builds&lt;/span&gt;
  &lt;span class="c1"&gt;// So we can leverage Phoenix static assets plug to deliver&lt;/span&gt;
  &lt;span class="c1"&gt;// our React app directly from our final Elixir app,&lt;/span&gt;
  &lt;span class="c1"&gt;// Serving all files from the `priv/static/webapp` folder.&lt;/span&gt;
  &lt;span class="c1"&gt;// NOTE: Remember to move the frontend build files to the&lt;/span&gt;
  &lt;span class="c1"&gt;// `priv` folder during the application build process in CI&lt;/span&gt;
  &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webapp/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now execute our custom mix task again from within our Phoenix project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix webapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is done, take a look at the &lt;code&gt;priv/static/webapp/index.html&lt;/code&gt; contents. We should see an HTML similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/webapp/assets/favicon.17e50649.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Vite App&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/webapp/assets/index.fb986a90.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"modulepreload"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/webapp/assets/vendor.6b432119.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/webapp/assets/index.458f9883.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that all URLs there have the &lt;code&gt;/webapp/&lt;/code&gt; base path prepended. That is very neat. Our Frontend is ready to be served by Phoenix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serving static assets via Plug
&lt;/h3&gt;

&lt;p&gt;Phoenix is still unaware of our &lt;code&gt;webapp&lt;/code&gt; static folder. We must add that to our endpoint configuration so our &lt;code&gt;Plug.Static&lt;/code&gt; can serve it. Head to &lt;code&gt;lib/phoenix_react_web/endpoint.ex&lt;/code&gt; at line 23. Add the &lt;code&gt;webapp&lt;/code&gt; to the string list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;at:&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;from:&lt;/span&gt; &lt;span class="ss"&gt;:phoenix_react&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;gzip:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="sx"&gt;~w(assets fonts images webapp favicon.ico robots.txt)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that tiny change, Phoenix is now able to serve the static assets generated by Vite.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serving the initial HTML page via Phoenix
&lt;/h3&gt;

&lt;p&gt;We now have a fully functional frontend and our Phoenix backend is able to deliver its static assets like JavaScript and CSS files. But to make it really feel native to our&lt;br&gt;
platform, we must be able to visit &lt;code&gt;example.com/app&lt;/code&gt; or any other route under &lt;code&gt;/app&lt;/code&gt; and our React app must be able to mount all its components based on the given route.&lt;/p&gt;

&lt;p&gt;For that to work, we must deliver the initial &lt;code&gt;index.html&lt;/code&gt; that was generated by Vite whenever someone visits &lt;code&gt;/app/*&lt;/code&gt;. We need a custom Phoenix controller. Let's build that now.&lt;/p&gt;

&lt;p&gt;Create a new controller at &lt;code&gt;lib/phoenix_react_web/controllers/webapp_controller.ex&lt;/code&gt; with the following module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;PhoenixReactWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WebappController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;PhoenixReactWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;send_resp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_react_app&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Serve the index.html file as-is and let React&lt;/span&gt;
  &lt;span class="c1"&gt;# take care of the rendering and client-side rounting.&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# Potential improvement: Cache the file contents here&lt;/span&gt;
  &lt;span class="c1"&gt;# in an ETS table so we don't read from the disk for every request.&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;render_react_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:phoenix_react&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"priv/static/webapp/index.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a controller that can serve our &lt;code&gt;index.html&lt;/code&gt; file, but we need to configure a route that will hit this newly created &lt;code&gt;index&lt;/code&gt; function. Let's add the following scope to our Phoenix router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PhoenixReactWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WebappController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/*path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WebappController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! Let's try this out. Make sure that your Vite dev server is stopped and start your Phoenix server with &lt;code&gt;mix phx.server&lt;/code&gt; and go to &lt;code&gt;localhost:4000/app&lt;/code&gt;. You should see the exact same result that we had when our Vite dev server was running!&lt;/p&gt;

&lt;p&gt;Try to click through the header links. It should be all client-side routing. The ultimate test is to type in the url &lt;code&gt;localhost:4000/app/settings&lt;/code&gt;, hit enter and see what happens.&lt;/p&gt;

&lt;p&gt;Notice that the &lt;code&gt;/app/settings&lt;/code&gt; page will be displayed as we expected. Behind the scenes, Phoenix kept delivering the &lt;code&gt;index.html&lt;/code&gt; file and the React Router made sure that the right components were mounted. Sweet! Our Phoenix and React apps are ready to roll!&lt;/p&gt;

&lt;h3&gt;
  
  
  API requests and CORS
&lt;/h3&gt;

&lt;p&gt;If you have been developing frontend apps that talk to an external API, I'm quite confident that you have faced a bunch of CORS issues. For those that are not familiar with, whenever you open up an app at &lt;code&gt;myapp.com&lt;/code&gt; and that same app needs to call an API at &lt;code&gt;myapi.com&lt;/code&gt; the browser prevents that by default.&lt;/p&gt;

&lt;p&gt;Actually, the browser will issue an &lt;code&gt;OPTIONS&lt;/code&gt; request to check if &lt;code&gt;myapi.com&lt;/code&gt; allows requests coming from &lt;code&gt;myapp.com&lt;/code&gt; to be answered. This is a very interesting security mechanism and I'm glad it's there. If you want to learn more about it, &lt;a href="https://twitter.com/jaffathecake" rel="noopener noreferrer"&gt;Jake Archibald&lt;/a&gt; wrote &lt;a href="https://jakearchibald.com/2021/cors/" rel="noopener noreferrer"&gt;an awesome blogpost&lt;/a&gt; about it with all the information you need to know.&lt;/p&gt;

&lt;h4&gt;
  
  
  Skipping the whole CORS trouble
&lt;/h4&gt;

&lt;p&gt;Whenever we are developing an app that it's all hosted under the same domain, things are way easier and simpler. If our &lt;code&gt;myapp.com&lt;/code&gt; makes a request to &lt;code&gt;myapp.com/api/users&lt;/code&gt; the browser won't even think about checking that because it knows that &lt;code&gt;myapp.com&lt;/code&gt; is under the same domain, so it's pretty sure that you allow requests to come and go from your own domain.&lt;/p&gt;

&lt;p&gt;During development, we are running our Phoenix app at port &lt;code&gt;4000&lt;/code&gt; and our React app at port &lt;code&gt;3000&lt;/code&gt;, we need to find a way for requests made by our React app to &lt;code&gt;localhost:3000/api/users&lt;/code&gt; to be captured by some sort of proxy and forwarded to our Phoenix backend at port &lt;code&gt;4000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Luckily, Vite saves the day again by providing us with the server proxy configuration. Head over to the &lt;code&gt;frontend/vite.config.ts&lt;/code&gt; and add the &lt;code&gt;server&lt;/code&gt; entry to your config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="c1"&gt;// Forward all requests made by our React frontend to `localhost:3000/api`&lt;/span&gt;
  &lt;span class="c1"&gt;// to our Phoenix backend running at `localhost:4000`.&lt;/span&gt;
  &lt;span class="c1"&gt;// This is only necessary during development.&lt;/span&gt;
  &lt;span class="c1"&gt;// In production, our Phoenix and React apps are served from the same&lt;/span&gt;
  &lt;span class="c1"&gt;// domain and port, which makes this configuration unecessary.&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// using the `webapp` base path for production builds&lt;/span&gt;
  &lt;span class="c1"&gt;// So we can leverage Phoenix static assets plug to deliver&lt;/span&gt;
  &lt;span class="c1"&gt;// our React app directly from our final Elixir app,&lt;/span&gt;
  &lt;span class="c1"&gt;// Serving all files from the `priv/static/webapp` folder.&lt;/span&gt;
  &lt;span class="c1"&gt;// NOTE: Remember to move the frontend build files to the&lt;/span&gt;
  &lt;span class="c1"&gt;// `priv` folder during the application build process in CI&lt;/span&gt;
  &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webapp/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on, if you are making requests with &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt; for instance, you can safely make a request in your React component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RequestComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt; 
        &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; 
      &lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request to &lt;code&gt;/api/todos&lt;/code&gt; should be forwarded to your Phoenix backend and as long as you have a route and a controller to respond to that, API requests will be served just fine.&lt;/p&gt;

&lt;p&gt;Authentication via http-only Cookies will also just work without any extra setup since everything is under the same domain. (&lt;code&gt;localhost&lt;/code&gt; during development and &lt;code&gt;myapp.com&lt;/code&gt; in production)&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Elixir Release
&lt;/h2&gt;

&lt;p&gt;We have got everything setup now and the cherry on top is to generate the Elixir release with our production Phoenix app.&lt;/p&gt;

&lt;p&gt;The major advantage of an Elixir Release is that it creates a single package including the Erlang VM, Elixir and all of your code and dependencies. The generated package can be placed into any machine without any preconfigured dependency. It works similarly like Go binaries that you just download and execute.&lt;/p&gt;

&lt;p&gt;But before we generate our release, since we are testing the build locally, we need to change the port configuration since our runtime configuration is binding to &lt;strong&gt;443&lt;/strong&gt; by default. Let's quickly change that at &lt;code&gt;config/runtime.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:phoenix_react&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PhoenixReactWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# here use the `port` variable so we can control that with environment variables&lt;/span&gt;
  &lt;span class="ss"&gt;url:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;# Enable the web server&lt;/span&gt;
  &lt;span class="ss"&gt;server:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;http:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;ip:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;port:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;secret_key_base:&lt;/span&gt; &lt;span class="n"&gt;secret_key_base&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that out of the way, execute the following commands to generate the release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate a secret for our Phoenix app&lt;/span&gt;
mix phx.gen.secret
&lt;span class="c"&gt;# It will output a very long string. Something like this:&lt;/span&gt;
B41pUFgfTJeEUpt+6TwSkbrxlAb9uibgIemaYbm1Oq+XdZ3Q96LcaW9sarbGfMhy

&lt;span class="c"&gt;# Now export this secret as a environment variable:&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;B41pUFgfTJeEUpt+6TwSkbrxlAb9uibgIemaYbm1Oq+XdZ3Q96LcaW9sarbGfMhy

&lt;span class="c"&gt;# Export the database URL&lt;/span&gt;
&lt;span class="c"&gt;# Probably very different in production for you.&lt;/span&gt;
&lt;span class="c"&gt;# I'm just using the local postgreSQL dev instance for this demo&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ecto://postgres:postgres@localhost/phoenix_react_dev

&lt;span class="c"&gt;# Get production dependencies&lt;/span&gt;
mix deps.get &lt;span class="nt"&gt;--only&lt;/span&gt; prod

&lt;span class="c"&gt;# Compile the project for production&lt;/span&gt;
&lt;span class="nv"&gt;MIX_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod mix compile

&lt;span class="c"&gt;# Generate static assets in case you&lt;/span&gt;
&lt;span class="c"&gt;# are using Phoenix default assets pipelines&lt;/span&gt;
&lt;span class="c"&gt;# For serve-side rendered pages&lt;/span&gt;
&lt;span class="nv"&gt;MIX_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod mix assets.deploy

&lt;span class="c"&gt;# Generate our React frontend using&lt;/span&gt;
&lt;span class="c"&gt;# our custom mix task&lt;/span&gt;
mix webapp

&lt;span class="c"&gt;# Genereate the convenience scripts to assist&lt;/span&gt;
&lt;span class="c"&gt;# Phoenix applicaiton deployments like running ecto migrations&lt;/span&gt;
mix phx.gen.release

&lt;span class="c"&gt;# Now we are ready to generate the Elixir Release&lt;/span&gt;
&lt;span class="nv"&gt;MIX_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod mix release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have our production release ready. Let's fire it up with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PHX_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost _build/prod/rel/phoenix_react/bin/phoenix_react start

&lt;span class="c"&gt;# You should an output similar to the following&lt;/span&gt;
19:52:53.813 &lt;span class="o"&gt;[&lt;/span&gt;info] Running PhoenixReactWeb.Endpoint with cowboy 2.9.0 at :::4000 &lt;span class="o"&gt;(&lt;/span&gt;http&lt;span class="o"&gt;)&lt;/span&gt;
19:52:53.814 &lt;span class="o"&gt;[&lt;/span&gt;info] Access PhoenixReactWeb.Endpoint at http://localhost:4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now our Phoenix app is running in production mode. Now head to your browser and open &lt;code&gt;localhost:4000/app&lt;/code&gt;. You should see our React app being rendered!&lt;/p&gt;

&lt;p&gt;We have finally succeeded with our Phoenix + React + TypeScript setup. It provides us with a great developer experience while simplifying our production builds by bundling our Phoenix app together with our React app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;While that might have been a tiny bit complex to setup, I believe it is still worth it to keep your SPA decoupled from your backend. Here is a list with a few bonus point of this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single repo to work with which simplifies development, specially with a bigger team&lt;/li&gt;
&lt;li&gt;Simpler CI/CD pipelines on the same repository&lt;/li&gt;
&lt;li&gt;Free to swap out Vite in the future in case we decide to go with a different build tool&lt;/li&gt;
&lt;li&gt;In the extreme case of changing our backend from Phoenix to something else, our React frontend is still fully independent and can basically be copy-pasted into a new setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally believe that the development and deployment of our applications should be simple and while having React as a dependency does increase complexity into our app, the trade-off of building web apps with it pays off in my case. Although, if you have simple CRUD apps, sticking with vanilla Phoenix templates and LiveView might be more than enough.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the repo with all the changes we made on this post &lt;a href="https://github.com/brunojppb/React-Phoenix-TS" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>How to use Redis Cluster for caching</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Thu, 02 Sep 2021 10:29:57 +0000</pubDate>
      <link>https://dev.to/bpaulino0/how-to-use-redis-cluster-for-caching-4ojf</link>
      <guid>https://dev.to/bpaulino0/how-to-use-redis-cluster-for-caching-4ojf</guid>
      <description>&lt;p&gt;In this post, we will explore how we can use Redis as a cache layer for our application and as we explore it further, we will see how a Redis Cluster can provide us more scalability and reliability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; If you are already familiar with Redis and it's just looking for a way to spin-up a fully configured Redis Cluster using Docker, &lt;a href="https://github.com/brunojppb/redis-cluster-demo"&gt;here is the Github repo.&lt;/a&gt; Just clone this repo, go to your terminal, run &lt;code&gt;docker-compose up&lt;/code&gt; and you should be good to go.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is Redis?
&lt;/h2&gt;

&lt;p&gt;Redis is a key-value store. In rough terms, it works just like a database, but it keeps its data in memory, which means that reads and writes are orders of magnitude faster compared to relational databases like &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL.&lt;/a&gt; It is important to mention that Redis does not replace a relational database. It has its own use-cases and we will explore some of them in this post.&lt;/p&gt;

&lt;p&gt;For more information about Redis, have a look at their website &lt;a href="https://redis.io/"&gt;here.&lt;/a&gt; There you find good documentation and how to install it on your machine. However, we will be building a demo during this post and we will use an interesting setup using &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; and &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt; that will spin up and configure the entire Redis cluster for you. The only thing you need available is Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Redis for caching
&lt;/h2&gt;

&lt;p&gt;Whenever we need fast access to some sort of data, we need to think about ways of keeping this data as close to the application layer as possible. If the amount of data is small enough, It's generally a good idea to keep this data in the local memory so we have instant access. But when we talk about web applications, specially the ones that are stateless and can potentially run in multiple servers, we can't guarantee that the data we need will be present as well as making sure that other servers in your cluster have fast access to this same data.&lt;/p&gt;

&lt;p&gt;That is where databases are handy. We can write this data to a central place and other servers can fetch this data whenever they need. The issue with some databases is that if you really need blazing fast access, some of them won't be able to deliver that at bullet speed. Redis is generally the go-to database whenever you need fast and reliable access to specific bits of data. It also provides us with ways to set expiration policies on that data so they are deleted automatically when they expire.&lt;/p&gt;

&lt;p&gt;Redis is usually a good choice for storing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User sessions&lt;/li&gt;
&lt;li&gt;Authentication tokens&lt;/li&gt;
&lt;li&gt;Rate-limit counters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redis is by no means limited to the use-cases above, but they fit well when you need fast data access, most often on every request coming through your servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the point of using a cluster?
&lt;/h2&gt;

&lt;p&gt;It is usually common to start with a single server instance, perhaps connected to a database server which can take you a long way. But once you need to scale you application across different countries and sometimes different continents, it probably means that your application needs to be available 24h a day, 7 days a week. And robustness and reliability needs to be embedded in your application.&lt;/p&gt;

&lt;p&gt;You need to start to think about what happens when one of your database servers go down, either because of an issue in the network or because of a faulty hardware. If you have only a single instance you will be dead in the water. If you have backups, it's going to take sometime until you can spin up a new instance, configure it all up to your standards, restore the backup and put it back in business.&lt;/p&gt;

&lt;p&gt;If your application is mission critical, you cannot afford to be offline for a few hours. Some applications cannot even be offline for a few minutes in the entire year. This is where a Cluster with replicas can save your skin when a problem like that happens.&lt;/p&gt;

&lt;p&gt;A Redis Cluster makes sure that your data is automatically shared across multiple Redis instances, which will give you a higher level of reliability and availability. In case one of those instances experience any kind of failure, the other nodes can still serve content normally for your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spinning up a Redis cluster
&lt;/h2&gt;

&lt;p&gt;I've recently migrated a large web application from using a single Redis instance to a cluster with &lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/Shards.html"&gt;multiple shards&lt;/a&gt;, including multiple replicas. While we are using AWS infrastructure that provides us the entire cluster configuration, I couldn't simply trust that everything would work in production. I had to make sure that we could support a Redis cluster during development, so I've created a setup that spawns several Redis containers and connect with each other automatically to form a cluster.&lt;/p&gt;

&lt;p&gt;To connect to Redis from your application, you will need a library that can perform that for you (Otherwise you have to reinvent the wheel). While I've been using &lt;a href="https://github.com/luin/ioredis"&gt;IORedis&lt;/a&gt; for a nodeJS application in this demo, if you have been using a different language, you will have to look for different connectors like &lt;a href="https://lettuce.io/"&gt;Lettuce for Java&lt;/a&gt; or perhaps &lt;a href="https://github.com/go-redis/redis"&gt;go-redis for Go&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The entire setup is ready for you in this &lt;a href="https://github.com/brunojppb/redis-cluster-demo"&gt;Github repository here&lt;/a&gt;, so you don't have to worry about creating anything from scratch. You can clone it and give it a spin while we will be walking through the files from this repo along the rest of this blogpost.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Creating a Dockerfile
&lt;/h3&gt;

&lt;p&gt;While we will be using the standard Redis image available from Dockerhub to spin up several Redis containers, we still need a way to connect them. That is where we will be building a special container that can issue commands to Redis in a way that it can form a cluster.&lt;/p&gt;

&lt;p&gt;at &lt;a href="https://github.com/brunojppb/redis-cluster-demo/blob/main/redis/Dockerfile"&gt;&lt;code&gt;redis/Dockerfile&lt;/code&gt;&lt;/a&gt; we have the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; redis:latest&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./entrypoint.sh /entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;755 /entrypoint.sh

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be using this Dockerfile to build our custom Docker image based on Redis. The secret sauce here is actually in at &lt;a href="https://github.com/brunojppb/redis-cluster-demo/blob/main/redis/entrypoint.sh"&gt;&lt;code&gt;redis/entrypoint.sh&lt;/code&gt;&lt;/a&gt;. Let's have a look at this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Using the redis-cli tool available as default in the Redis base image&lt;/span&gt;
&lt;span class="c"&gt;# we need to create the cluster so they can coordinate with each other&lt;/span&gt;
&lt;span class="c"&gt;# which key slots they need to hold per shard&lt;/span&gt;

&lt;span class="c"&gt;# wait a little so we give some time for the Redis containers&lt;/span&gt;
&lt;span class="c"&gt;# to spin up and be available on the network&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;5
&lt;span class="c"&gt;# redis-cli doesn't support hostnames, we must match the&lt;/span&gt;
&lt;span class="c"&gt;# container IP addresses from our docker-compose configuration.&lt;/span&gt;
&lt;span class="c"&gt;# `--cluster-replicas 1` Will make sure that every master &lt;/span&gt;
&lt;span class="c"&gt;# node will have its replica node.&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"yes"&lt;/span&gt; | redis-cli &lt;span class="nt"&gt;--cluster&lt;/span&gt; create &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.2:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.3:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.4:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.5:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.6:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  173.18.0.7:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-replicas&lt;/span&gt; 1

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🚀 Redis cluster ready."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using the &lt;code&gt;redis-cli&lt;/code&gt; to issue commands. This command is creating a cluster and pointing to the specific Redis instances that will be reachable when we start this script. we are using hard-coded IP addresses here that will be provided by our &lt;code&gt;docker-compose.yml&lt;/code&gt; file later on.&lt;/p&gt;

&lt;p&gt;This cluster is composed by 3 shards. Each shard has a master node that is responsible for all the writes, but also a Replica node that holds a copy of the data. A Redis Cluster shard can have up to 500 replicas (at least in AWS). A Replica node has the power to take over and become the Master node if the current Master becomes unavailable.&lt;/p&gt;

&lt;p&gt;Now notice that inside of our &lt;code&gt;redis&lt;/code&gt; folder we also have a file called &lt;code&gt;redis.conf&lt;/code&gt;. This file will be copied to each Redis container later on so they can instruct the Redis instance to work as part of a cluster. Let's have a look at its contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Custom config file to enable cluster mode
# on all Redis instances started via Docker
&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt;-&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="c"&gt;# The cluster file is created and managed by Redis
# We just need to declare it here
&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;-&lt;span class="n"&gt;config&lt;/span&gt;-&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt;-&lt;span class="n"&gt;node&lt;/span&gt;-&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;span class="n"&gt;appendonly&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is not much going on there. The important part is &lt;code&gt;cluster-enabled yes&lt;/code&gt; which enables our Redis instance to act as part of the cluster. We now need a way to spin up several Redis containers and make sure that they talk to each other. At the root folder of our project we have the &lt;code&gt;docker-compose.yml&lt;/code&gt;. Let's have a look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis_1_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis_2_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis_3_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis_4_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis_5_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis_6_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="c1"&gt;# This volume is specific for the demo Express application&lt;/span&gt;
  &lt;span class="c1"&gt;# built in this repo. You probably won't need that on your own setup.&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;express_app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;express_app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_CLUSTER_URLS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis_1:6379,redis_2:6379,redis_3:6379,redis_4:6379,redis_5:6379,redis_6:6379'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_6&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cluster_initiator&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4000:4000"&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.10&lt;/span&gt;

  &lt;span class="c1"&gt;# Here we have six Redis containers with Cluster mode enabled,&lt;/span&gt;
  &lt;span class="c1"&gt;# three of them will work as master nodes and each one of&lt;/span&gt;
  &lt;span class="c1"&gt;# will have a replica, so in case of failures, the replica becomes the master.&lt;/span&gt;
  &lt;span class="c1"&gt;# They are configured by the `cluster_initiator` container.&lt;/span&gt;
  &lt;span class="na"&gt;redis_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_1&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_1_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.2&lt;/span&gt;

  &lt;span class="na"&gt;redis_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_2&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_2_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.3&lt;/span&gt;

  &lt;span class="na"&gt;redis_3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_3&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_3_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.4&lt;/span&gt;

  &lt;span class="na"&gt;redis_4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_4&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_4_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.5&lt;/span&gt;

  &lt;span class="na"&gt;redis_5&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_5&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_5_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.6&lt;/span&gt;

  &lt;span class="na"&gt;redis_6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_6&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_6_data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis/redis.conf:/usr/local/etc/redis/redis.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/local/etc/redis/redis.conf"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.7&lt;/span&gt;

  &lt;span class="c1"&gt;# Ephemeral container to create the Redis cluster connections.&lt;/span&gt;
  &lt;span class="c1"&gt;# Once the setup is done, this container shuts down&lt;/span&gt;
  &lt;span class="c1"&gt;# and the cluster can be used by the service app container&lt;/span&gt;
  &lt;span class="na"&gt;cluster_initiator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster_initiator&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_6&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.8&lt;/span&gt;

  &lt;span class="c1"&gt;# Web UI to browse through our Redis data across all nodes&lt;/span&gt;
  &lt;span class="na"&gt;redis_commander&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rediscommander/redis-commander:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis_web&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local:redis_1:6379,local:redis_2:6379,local:redis_3:6379"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:8081"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_6&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cluster_initiator&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.9&lt;/span&gt;

&lt;span class="c1"&gt;# Rename the default network so we can easily identify it&lt;/span&gt;
&lt;span class="c1"&gt;# Across all containers&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis_cluster_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;173.18.0.0/16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a long one, but here is what this &lt;code&gt;docker-compose.yml&lt;/code&gt; does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a container with our Express application (just for the sake of this demo)&lt;/li&gt;
&lt;li&gt;Creates several instances of Redis

&lt;ul&gt;
&lt;li&gt;Configure their IP addresses to match the ones used in our &lt;code&gt;entrypoint.sh&lt;/code&gt; script&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;redis.conf&lt;/code&gt; file so they can act as a cluster&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Creates a cluster initiator container that is only necessary for executing our &lt;code&gt;entrypoint.sh&lt;/code&gt; script and make the cluster connection&lt;/li&gt;
&lt;li&gt;Creates a container with the &lt;a href="https://github.com/joeferner/redis-commander"&gt;Redis Commander UI&lt;/a&gt; which is a nice Web UI for browsing what is stored in our Redis Cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we went through this, let's try this out. Go to your terminal and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is ready, you should be able to open your browser and visit &lt;code&gt;localhost:4000&lt;/code&gt;. There you have a demo web application I've built where you can enter a key/value pair and save it to Redis and also search for a specific key you have entered before so it can fetch it from Redis and show you the contents on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenloxnt05oxl3qcu6ror.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenloxnt05oxl3qcu6ror.png" alt="Redis Cluster Demo Web App" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are wondering how the connection is setup on the JavaScript side, let's have a look at our &lt;code&gt;src/service/redisClient.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Get an existing Redis client instance. Build one if necessary
 * @return {Cluster|null} redis client
 * */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildRedisClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// cluster URLs should be passed in with the following format:&lt;/span&gt;
    &lt;span class="c1"&gt;// REDIS_CLUSTER_URLS=10.0.0.1:6379,10.0.0.2:6379,10.0.0.3:6379&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_CLUSTER_URLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;redisOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;enableAutoPipelining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Redis Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Redis emits this error when an something &lt;/span&gt;
    &lt;span class="c1"&gt;// occurs when connecting to a node when using Redis in Cluster mode&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Redis error in node &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not create a Redis cluster client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buildRedisClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part is very simple. It reads the cluster URLs from the environment and creates an instance of &lt;code&gt;Redis.Cluster&lt;/code&gt; using the RedisIO library. From there on we can start issue commands like &lt;code&gt;redis.set&lt;/code&gt;, &lt;code&gt;redis.get&lt;/code&gt; or &lt;code&gt;redis.exists&lt;/code&gt; across our application. Here is how we do that in the demo Express app within this repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildRedisClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./service/redisClient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildRedisClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Have a look at src/index.js for a complete implementation&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/save-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home/index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dataSaved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home/index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would like to explore the data stored in the cluster, go to &lt;code&gt;localhost:5000&lt;/code&gt; and browse through the Redis Commander UI. There you should be able see all the Master nodes and explore all keys and values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy59a99a83xrzl533h4ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy59a99a83xrzl533h4ou.png" alt="Redis Commander UI" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will notice that some keys are stored in one Master node and other keys are stored in other nodes. This is the data distribution done by Redis, which provides you load balancing across the cluster.&lt;/p&gt;

&lt;p&gt;I hope this Docker setup can help your development workflow the same way it did for me and my team recently. Feel free to &lt;a href="https://twitter.com/bpaulino0"&gt;DM me via Twitter&lt;/a&gt; if you have any questions.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>docker</category>
      <category>redis</category>
      <category>caching</category>
    </item>
    <item>
      <title>Retrying API Calls with Exponential Backoff in Javascript</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Mon, 01 Mar 2021 22:30:28 +0000</pubDate>
      <link>https://dev.to/bpaulino0/retrying-api-calls-with-exponential-backoff-4bpc</link>
      <guid>https://dev.to/bpaulino0/retrying-api-calls-with-exponential-backoff-4bpc</guid>
      <description>&lt;p&gt;Have you ever implemented an integration with a third-party service where you have to call their API endpoints several times a day? Depending on the number of times you call this API, some of those calls will inevitably fail.&lt;/p&gt;

&lt;p&gt;One solution to mitigate this problem is to implement a &lt;code&gt;retry&lt;/code&gt; algorithm. Here is a sequence diagram showing how this algorithm could look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fapi-without-exponential-backoff-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fapi-without-exponential-backoff-diagram.svg" alt="Exponential backoff diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that once our API call fails, our app immediately tries to call it again. That could be extremely fast and there is nothing wrong with that, but that isn't very effective. Why?&lt;/p&gt;

&lt;h2&gt;
  
  
  Being polite with Exponential Backoff
&lt;/h2&gt;

&lt;p&gt;Lets assume the restaurants API we were trying to call on the chart above is having some trouble. Maybe it's overloaded or is completely down. Retrying to call it immediately after a failed attempt will do no good. It will actually make the situation worse: The restaurants API will be hammered harder and won't have time to recover.&lt;/p&gt;

&lt;p&gt;To countermeasure that, we can wait a little before retries. We can actually do better than that. What if on every failed attempt, we exponentially increase the waiting time for the next attempt? Bingo, This is what &lt;a href="https://en.wikipedia.org/wiki/Exponential_backoff" rel="noopener noreferrer"&gt;Exponential Backoff&lt;/a&gt; is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Our app tries to call the Restaurants API.&lt;/li&gt;
&lt;li&gt;The API call fails.&lt;/li&gt;
&lt;li&gt;Our app waits for &lt;code&gt;200 millisecods&lt;/code&gt; before calling it again.&lt;/li&gt;
&lt;li&gt;Our app retries to call the Restaurants API again.&lt;/li&gt;
&lt;li&gt;The API call fails again.&lt;/li&gt;
&lt;li&gt;Our app waits for &lt;code&gt;400 millisecods&lt;/code&gt; before calling it again.&lt;/li&gt;
&lt;li&gt;Our app retries to call the Restaurants API again.&lt;/li&gt;
&lt;li&gt;The API call completes successfully.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is how the diagram would look like when we implement Exponential Backoff:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fapi-with-exponential-backoff-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbpaulino.com%2Fassets%2Fimages%2Fposts%2Fapi-with-exponential-backoff-diagram.svg" alt="Exponential backoff diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How can we do that in Javascript?
&lt;/h2&gt;

&lt;p&gt;The implementation of the algorithm above is actually quite straightforward in Javascript. The implementation below works in Node.js and also in modern browsers, with zero dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Wait for the given milliseconds
 * @param {number} milliseconds The given time to wait
 * @returns {Promise} A fulfiled promise after the given time has passed
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Execute a promise and retry with exponential backoff
 * based on the maximum retry attempts it can perform
 * @param {Promise} promise promise to be executed
 * @param {function} onRetry callback executed on every retry
 * @param {number} maxRetries The maximum number of retries to be attempted
 * @returns {Promise} The result of the given promise passed in
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Notice that we declare an inner function here&lt;/span&gt;
  &lt;span class="c1"&gt;// so we can encapsulate the retries and don't expose&lt;/span&gt;
  &lt;span class="c1"&gt;// it to the caller. This is also a recursive function&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retryWithBackoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Make sure we don't wait on the first attempt&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Here is where the magic happens.&lt;/span&gt;
        &lt;span class="c1"&gt;// on every retry, we exponentially increase the time to wait.&lt;/span&gt;
        &lt;span class="c1"&gt;// Here is how it looks for a `maxRetries` = 4&lt;/span&gt;
        &lt;span class="c1"&gt;// (2 ** 1) * 100 = 200 ms&lt;/span&gt;
        &lt;span class="c1"&gt;// (2 ** 2) * 100 = 400 ms&lt;/span&gt;
        &lt;span class="c1"&gt;// (2 ** 3) * 100 = 800 ms&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeToWait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`waiting for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timeToWait&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeToWait&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// only retry if we didn't reach the limit&lt;/span&gt;
      &lt;span class="c1"&gt;// otherwise, let the caller handle the error&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;retryWithBackoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Max retries reached. Bubbling the error up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;retryWithBackoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is how you can quickly test this implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Fake an API Call that fails for the first 3 attempts
 * and resolves on its fourth attempt.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateFailableAPICall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Simulated error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*** Testing our Retry with Exponential Backoff */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFailableAPICall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onRetry called...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to try this out, here is a &lt;a href="https://codesandbox.io/s/exponential-backoff-ziy8h?file=/src/index.js" rel="noopener noreferrer"&gt;Codesanbox link&lt;/a&gt; where you can play with it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>How to Create Bulletproof Tickets</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Tue, 08 Dec 2020 15:38:14 +0000</pubDate>
      <link>https://dev.to/bpaulino0/how-to-create-bulletproof-tickets-31hn</link>
      <guid>https://dev.to/bpaulino0/how-to-create-bulletproof-tickets-31hn</guid>
      <description>&lt;p&gt;It's Monday morning, you just had your first coffee in front of the computer and you open your project management app to start planning your week with your team. You start reading your backlog of tasks and then you see a ticket with the title:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[BUG] Button doesn't respond&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You open the ticket and for your astonishment, the description is empty, there are no screenshots, no urls, &lt;em&gt;nada&lt;/em&gt;. You can only see that this ticket was created by Joe. You immediately start to think &lt;em&gt;"Ho dear Joe, what did you see? on which page did you see this button? Which button specifically doesn't respond? Was it happening constantly, was it intermittent, was it you who found this issue? Can you reproduce?"&lt;/em&gt; So many questions you want to ask Joe, but so little time.&lt;/p&gt;

&lt;p&gt;If you have never had a similar situation like I mentioned before while working on a project with multiple people, you can consider yourself the luckiest person on the planet. The truth is, you have to put a good deal of thought and time into creating a great ticket. Reporting a bug or defining a requirement well is a kind of art.&lt;/p&gt;

&lt;p&gt;Whenever you are in a position of reporting something in written form, in this case creating a ticket, you have to put yourself in the shoes of the other person that will read and work on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is creating good tickets so important?
&lt;/h2&gt;

&lt;p&gt;At first sight, it seems like a time consuming task, and to be completely honest, it does take time to create a good ticket. But what we often miss is the time we save by avoiding roundtrips of questions that could have been answered during the ticket creation in the first place. Here are some reasons why creating a meaningful ticket is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It saves time:&lt;/strong&gt; People don't have to run around to gather the information that is already there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More understanding:&lt;/strong&gt; A well written ticket helps to give more understanding about the problem that might be hard to track down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solid solutions:&lt;/strong&gt; The more you understand the problem, the better and more reliable the solution will be. And the understanding usually starts by reading the ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less frustration:&lt;/strong&gt; Once the context is well detailed and understood, the people that will work on the problem will have less ambiguous questions to ask which will reduce the cognitive load while reasoning about a solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might be thinking &lt;em&gt;"Okay Bruno, but how the heck do I know whether a ticket is good or not?"&lt;/em&gt;. I'm Glad you asked and this is a super valid question by the way. After dealing with hundreds of tickets along the years, I have got an interesting method that I want to share with you in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Method
&lt;/h2&gt;

&lt;p&gt;There is zero scientific evidence to back me up here, but what I noticed was that I could use some small principles to have a well defined method to create good tickets. So based on what we have seen up until this point, here are the steps we can take to create good tickets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set the stage&lt;/strong&gt;: Explain the context and the impact the problem has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to reproduce&lt;/strong&gt;: If the ticket is related to a bug, here you provide detailed information on how to recreate the problem in a consistent way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer Solutions&lt;/strong&gt;: If you are able to, offer potential solutions for the problem you just found.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the following sections, I will construct a fictional ticket (but based on real world examples of web projects) so you can have a decent example on how you could apply those steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the stage
&lt;/h2&gt;

&lt;p&gt;Let's imagine you have just found a bug on the website from the company you work with. Now you need to report the problem and hopefully someone will fix it as quickly as possible. How do you start explaining the problem to someone that you might not even know?&lt;/p&gt;

&lt;p&gt;To start things off, you need to give as much information as possible about the context of the bug. And what do I mean by context? Here the important thing is to explain in detail what you expect in a normal case and what the bug introduces. But don't be too verbose on it otherwise you leave a lot of room for ambiguity. Here is an example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With the introduction of the new product versions, we changed the URLs our customers see on our public website. This causes several problems with external tools that rely on the previous URLs (e.g. Marketing Campaigns).&lt;/p&gt;

&lt;p&gt;The basic premises of existing product URLs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The customer must have a stable URL for any published product&lt;/li&gt;
&lt;li&gt;It should never change for the same product in the eyes of the customer&lt;/li&gt;
&lt;li&gt;With an immutable URL none of our marketing campaigns running on our Ad platforms would stop working.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the example above, we have given a good overview of the problem and also a good reason for the problem to be solved. This is a good starting point for a discussion about the ticket and also helps to prioritize it during planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Reproduce
&lt;/h2&gt;

&lt;p&gt;Now that we have set up a good context by explaining the problem in detail and why it is important to fix it, it's time to write down the steps to reproduce it. In software development, debugging can be a challenging task and the more clues you have, the easier it gets to find and fix the problem.&lt;br&gt;
One part of the puzzle is to consistently reproduce the problem in a safe environment so it can be monitored, tracked down and fixed. For that to happen, you usually have to define a set of steps that lead to the problem you found. Here is an example following our fictional problem above:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To reproduce the problem, please follow the steps below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open an existing product in the admin interface&lt;/li&gt;
&lt;li&gt;Open the product preview and write down the current URL&lt;/li&gt;
&lt;li&gt;Go back to the admin interface and update the product reference for a new version of the same product&lt;/li&gt;
&lt;li&gt;save the changes&lt;/li&gt;
&lt;li&gt;Open the product preview again on the website&lt;/li&gt;
&lt;li&gt;Compare the new URL with the previous URL you saved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comparison will result in an URL mismatch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the clear steps mentioned above, anyone who gets assigned to work on this ticket will quickly manage to verify the problem and test it as many times as they need.&lt;/p&gt;

&lt;p&gt;Sometimes you won't be able to have a consistent way of reproducing the problem and that is fine too, but try to find as many reproducible steps as possible. This will save a good chunk of time for the development team which leads to a faster bug-fix being deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offer Solutions
&lt;/h2&gt;

&lt;p&gt;The cherry on top, if you have more understanding of the systems you are reporting the bug, is to offer potential solutions for the problem you just found. Developers like challenges, but nobody has infinite time and at the end of the day, we have to produce value for the business, so the quicker a problem can be solved, the better.&lt;/p&gt;

&lt;p&gt;in some cases, you might even know exactly what the problem is and how to fix it, so don't spare words into giving a more detailed and technical suggestion at this stage. Here is how I would suggest a solution for our fictional URL problem we have been creating:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It looks like this problem happens at the e-commerce platform level. Since this is a third-party application which we don't have access to the source code, we have to fix this problem on another layer of our system.&lt;/p&gt;

&lt;p&gt;A potential solution could be to set rules on our reverse proxy so we keep accepting requests from old URLs and map them to the new ones.&lt;br&gt;
In the meantime, I will also report this problem to our e-commerce provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is clear that in the example above, the author has a deeper understanding of what is happening in the internal systems and that is a big advantage. But if you don't have this level of insight, you can still provide good feedback on how to solve the problem. Any potential solution should be considered, especially if that is coming from someone that understands the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I have been following those principles for a long time and it actually became quite natural for me while creating a ticket, but I understand that it takes time to build up this understanding.&lt;/p&gt;

&lt;p&gt;Sometimes your ticket will still leave room for questions and clarifications, and that is fine too. As long as your ticket was well prepared, even those follow-up questions will be more meaningful and complete, leading to less frustration and a more fluid communication path with your team.&lt;/p&gt;

&lt;p&gt;I believe by now you have the superpowers to create bulletproof tickets 🦸🏼‍♀️. Here is the complete ticket we created during this blogpost:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subject&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Product URL breaks after updating product version reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the introduction of the new Product versions, we changed the URLs our customers see on our public website. This causes several problems with external tools that rely on the previous URLs (e.g. Marketing Campaigns).&lt;/p&gt;

&lt;p&gt;The basic premises of existing product URLs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The customer must have a stable URL for any published product&lt;/li&gt;
&lt;li&gt;It should never change for the same product in the eyes of the customer&lt;/li&gt;
&lt;li&gt;With an immutable URL none of our marketing campaigns running on our Ad platforms would stop working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Reproduce&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To reproduce the problem, please follow the steps below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open an existing product in the admin interface&lt;/li&gt;
&lt;li&gt;Open the product preview and write down the current URL&lt;/li&gt;
&lt;li&gt;Go back to the admin interface and update the product reference for a new model&lt;/li&gt;
&lt;li&gt;Save the changes&lt;/li&gt;
&lt;li&gt;Open the product preview on the website&lt;/li&gt;
&lt;li&gt;Compare the new URL with the previous URL you saved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comparison will result in an URL mismatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Potential Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It looks like this problem happens at the e-commerce platform level. Since this is a third-party application which we don't have access to the source code, we have to fix this problem on another layer of our system.&lt;/p&gt;

&lt;p&gt;A potential solution could be to set rules on our reverse proxy so we keep accepting requests from old URLs and map them to the new ones.&lt;br&gt;
In the meantime, I will also report this problem to our e-commerce provider.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>agile</category>
      <category>management</category>
      <category>team</category>
      <category>webdev</category>
    </item>
    <item>
      <title>DevOps and its impact on Developer Productivity</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Sun, 22 Nov 2020 00:12:47 +0000</pubDate>
      <link>https://dev.to/bpaulino0/devops-and-its-impact-on-developer-productivity-cff</link>
      <guid>https://dev.to/bpaulino0/devops-and-its-impact-on-developer-productivity-cff</guid>
      <description>&lt;p&gt;I have recently finished reading &lt;a href="https://www.goodreads.com/book/show/35747076"&gt;Accelerate: building and Scaling High Performing Technology Organizations&lt;/a&gt; to get a more in-depth view into the DevOps landscape and its influence in developer productivity. The book is based on solid research conducted by &lt;a href="https://twitter.com/nicolefv"&gt;Dr. Nicole Forsgren&lt;/a&gt;, &lt;a href="https://twitter.com/jezhumble"&gt;Jez Humble&lt;/a&gt; and &lt;a href="https://twitter.com/RealGeneKim"&gt;Gene Kim.&lt;/a&gt; The book is an eye-opener for the importance of software delivery performance and the value it brings to any organization that takes it seriously.&lt;/p&gt;

&lt;p&gt;This book made me reflect on my experience with DevOps, my own productivity and the productivity of my team. I don’t want to quote every topic mentioned in the book here, but I absolutely recommend you to give it a read. My focus is actually to explore how it relates to my journey adopting DevOps along my career and the benefits it brings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping code to production and developer confidence
&lt;/h2&gt;

&lt;p&gt;When I started my career, I worked at a company where the deployment process was done by a magician developer that held all the powers. Nobody was allowed to deploy code unless this wizard could cast his spells. The second problem we had was that all engineers knew how to run the application on their local machines but once the rubber hits the road, nobody had a clue about setting up dependencies and building the application for production.&lt;/p&gt;

&lt;p&gt;That was a terrible workflow to work with and once during a lucky day, I had no choice but to perform a production deployment by myself. I was feeling scared just thinking about what could go wrong. Having this deployment workflow was a huge blow on the confidence of the engineers in my team, including myself, where not only nobody wanted to be responsible for it, but also not a single person wanted to get involved with it.&lt;/p&gt;

&lt;p&gt;Over the years, I started to learn about DevOps and incrementally adopted it wherever I go so I could avoid the bad experience I had in the past. Since then, I have been reaping the benefits of it over and over again. The productivity gain was immense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with Continuous Integration
&lt;/h2&gt;

&lt;p&gt;To start things off, you want to make your deployment process as predictable and as reproducible as possible. Which means that you have to make sure the work done by a developer can be built, tested and deployed to a safe environment, only then it can be shipped to production. This is called Continuous Integration (CI). Once you define and validate your integration steps for your application stack, be it Java, Node.js or even mobile apps, you must reproduce those steps in code using version control, better yet if you can make this a part of your application repository. As an example, those steps could look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code is committed by an engineer in a feature branch&lt;/li&gt;
&lt;li&gt;A build of the application is triggered. This build is technically production-grade, which means that it has minimal or no difference between development and production, mainly configuration differences (e.g. a sandbox payment gateway where fake payments can be issued), preferably configured via environment variables&lt;/li&gt;
&lt;li&gt;A suite of automated tests will be executed on this build&lt;/li&gt;
&lt;li&gt;Once all tests pass, the code can be reviewed and merged to the production branch (commonly known as main)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The steps above can be performed by any developer in the team. Once you have a continuous integration pipeline in place, the engineers don’t even have to think about it much since they can be triggered automatically by a continuous integration service like &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; or &lt;a href="https://docs.gitlab.com/ee/ci/"&gt;Gitlab CI.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have this process in place, you will have robustness built-in on your development workflow, which will inevitably lead to more confidence with your code, ultimately leading to more productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nail it down with Continuous Deployment
&lt;/h2&gt;

&lt;p&gt;What if I told you that once code is reviewed and merged to your production branch, this code could be automatically released to production with minimal or no impact to your users? Yes, that is possible. This is called Continuous Deployment (CD). The same thought process applied to continuous integration can be transferred to your deployment workflow, where automated pipelines will make sure your code is pushed to your servers clusters.&lt;/p&gt;

&lt;p&gt;Here you can apply the same constraints as before: You want to make sure that every deployment is predictable and reproducible. Any failed steps should leave traces (logs, error messages/code) for further inspection and debug. Here is an example on how it could work for a backend application deployed in a multi-node architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code is merged into the production branch (Build and tests were previously executed, so the confidence level here is already very high)&lt;/li&gt;
&lt;li&gt;Traffic is diverted to specific nodes in your cluster, inactive nodes are updated with the new version of your build. This step is done incrementally for every node, which will result in a &lt;a href="https://spring.io/blog/2016/05/31/zero-downtime-deployment-with-a-database"&gt;zero downtime deployment (or blue/green deployment)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Notification is sent yo your team about a new version deployed. Could be as simple as an email or more sophisticated integrations with your internal chat of choice. (e.g. &lt;a href="https://slack.com/intl/en-at/"&gt;Slack&lt;/a&gt;, &lt;a href="https://mattermost.com"&gt;Matterost&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again, all the steps mentioned above can be executed automatically, with zero manual intervention. Of course, you won’t probably have the full confidence to do it right away, which is totally understandable. But you can start with small steps by automating the process in code and triggering them manually in the beginning. Once the confidence is high enough, this step can be performed automatically for every new code merged.&lt;/p&gt;

&lt;p&gt;Once you enable anyone in your team to perform a deployment with confidence, the productivity of your peers will be noticeably better. As a result, you will be delivering value to your customers much faster, most probably much faster than other companies that don’t adopt such practices, which will give an edge on the competition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;If you are reading about CI and CD for the first time, fear not, you can absolutely do this and adopt such practices. Start with very small actions, I like to call it "start with baby steps". Do you build and test your application manually? Start by automating the build steps in a simple script. Have no automated test suite? Start adding automated tests to critical paths on your app. Those steps will slowly build robustness and resilience into your codebase.&lt;/p&gt;

&lt;p&gt;Once you are confident enough with your first scripts, make it executable from a remote service like Github Actions. That is a good start to enable your team to execute the same steps without being concerned with having the right machine or dependency installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;DevOps is a very broad topic and we only have covered a very shallow surface here, so there is much more to learn and explore. If you are an engineer and you let the "Ops team handle it", I ask you to look at it differently. You don’t have to become a DevOps specialist, but if you manage to include DevOps on your tool belt, you and your team will be much better off.&lt;/p&gt;

&lt;p&gt;There is also a very interesting chapter in the book where specific actions can be taken by managers so software delivery is treated as a "first-class citizen" across your organization. Some actions that I find very interesting and I have personally seen it working:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use failure as a learning opportunity to improve&lt;/li&gt;
&lt;li&gt;Create budgets for continuous learning and training&lt;/li&gt;
&lt;li&gt;Space to explore new ideas and share it with others&lt;/li&gt;
&lt;li&gt;Let your team use their tool of choice (in other words, don’t force them to write Java if they are high-skilled and productive with JavaScript)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Creating custom React hooks to handle components external events</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Sun, 20 Sep 2020 21:16:04 +0000</pubDate>
      <link>https://dev.to/bpaulino0/using-custom-react-hooks-to-handle-components-external-events-1j6n</link>
      <guid>https://dev.to/bpaulino0/using-custom-react-hooks-to-handle-components-external-events-1j6n</guid>
      <description>&lt;p&gt;On a side project this weekend, I had the classic case for a modal implementation. In most of the applications you have to deal with daily, you come to a place where you have to confirm some action or review some changes before pushing the &lt;em&gt;"I am 100% sure about this"&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;This is the perfect case for a modal, a small view that partially covers the screen and presents you with a few options. Most of the time, there will be a button to close the modal away by clicking on the "close" button on the top-right corner. But an even better way to let the user dismiss the modal is to let them click outside of the view in focus, without forcing them to hit the often too small &lt;strong&gt;"x"&lt;/strong&gt; on top.&lt;/p&gt;

&lt;p&gt;Here is the live implementation of our modal component we will build during this post. Try it out on our &lt;a href="https://codesandbox.io/s/small-browser-vosod?file=/src/Modal.js"&gt;Codesandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/react-custom-hooks-see0d"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;For cases like this, you must probably want to watch for clicks or taps outside the main view, in this case, the modal, so you can take the correct action of closing it. But how could you do that in &lt;a href="https://reactjs.org/"&gt;React?&lt;/a&gt; one way would be to implement a global click handler in the component, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modalRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Here you can close your modal.&lt;/span&gt;
        &lt;span class="c1"&gt;// how to close it, that is up to you&lt;/span&gt;
        &lt;span class="c1"&gt;// (e.g. removing components, changing routes)&lt;/span&gt;
        &lt;span class="c1"&gt;// in this case, I am calling a `onClose` function&lt;/span&gt;
        &lt;span class="c1"&gt;// passed down as a prop.&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click happened outside. you can close now.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Pointer events are more device agnostic&lt;/span&gt;
    &lt;span class="c1"&gt;// which are able to handle clicks on Desktops and Taps on mobile devices&lt;/span&gt;
    &lt;span class="c1"&gt;// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/pointerdown_event&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Once our component unmount or update, we must remove the event listener&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Use the modalRef as dependency for the useEffect hook&lt;/span&gt;
    &lt;span class="c1"&gt;// so whenever this reference changes, the listener will update&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"my-modal"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-header"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Super important Action
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-body"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        This is an important message. read it carefully.
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-footer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ok&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this implementation leaves a lot of room for duplication isn't? If we need to handle a similar case on a different component, we will be doomed to repeat the same click away logic. We can do better than that by leveraging the power of custom React hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing logic with Custom React hooks
&lt;/h2&gt;

&lt;p&gt;In my opinion, hooks are one of the most beautiful features in React. You can compose your components in such a way that gives your application superpowers. React itself leverages the power of hooks with &lt;a href="https://reactjs.org/docs/hooks-state.html"&gt;useState&lt;/a&gt;, &lt;a href="https://reactjs.org/docs/hooks-effect.html"&gt;useEffect&lt;/a&gt; and a bunch of others.&lt;/p&gt;

&lt;p&gt;But we are not limited to the hooks React offers, we can create our own hooks, enabling us to share logic in a very functional way across our app. Lets extract that click away logic from our previous modal component into a custom hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useClickAway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClickAway&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Keep a mutable reference to click away callback&lt;/span&gt;
  &lt;span class="c1"&gt;// and change it every time the component using it changes&lt;/span&gt;
  &lt;span class="c1"&gt;// using 'useRef' here will make sure that we have a mutable&lt;/span&gt;
  &lt;span class="c1"&gt;// and single callback lying around.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbackRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onClickAway&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;callbackRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;onClickAway&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onClickAway&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// listen for click events on ref element&lt;/span&gt;
  &lt;span class="c1"&gt;// attaching a handler and calling the callback if necessary&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPointerDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;callbackRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPointerDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPointerDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;lets break our custom hook down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We start by declaring a plain javascript function called &lt;code&gt;useClickAway&lt;/code&gt;. This function takes two arguments. A &lt;code&gt;ref&lt;/code&gt; which is a mutable reference to the component we want to watch for clicks "outside" of its boundaries. And a &lt;code&gt;onClickAway&lt;/code&gt; callback, which will be executed once we detect a click outside.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We created a ref for the &lt;code&gt;useClickAway&lt;/code&gt; callback using the &lt;code&gt;useRef&lt;/code&gt; hook. This will make sure that we have only one reference to our &lt;code&gt;useClickAway&lt;/code&gt; callback that is captured by our &lt;code&gt;useEffect&lt;/code&gt; calls we will use later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On our first useEffect call, we make sure to keep track of the &lt;code&gt;useClickAway&lt;/code&gt; reference. So in case our component updates the &lt;code&gt;useClickAway&lt;/code&gt; reference, we also have to update our internal reference inside our custom hook.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On our second useEffect call, this where rubber hits the road. If you pay close attention, this call is exactly the same as we implemented in our modal component. The only difference is that we are calling our &lt;code&gt;callbackRef&lt;/code&gt; reference for the &lt;code&gt;onClickAway&lt;/code&gt; function instead. This is an extra layer of check to make sure that we are calling the right reference of the callback once a click happens outside of the view.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in place, how can we use that in our modal component? Lets see how the code looks like now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useClickAway&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./useClickAway&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onClose&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modalRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useClickAway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"shadow-overlay"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"my-modal"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-header"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Super important Action&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-body"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          This is an important message. read it carefully.
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-footer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ok&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you notice how clean our modal component looks now? Better yet, we can reuse that same logic across our app just by reusing the &lt;code&gt;useClickAway&lt;/code&gt; hook. Isn't that cool?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/s/small-browser-vosod?file=/src/Modal.js"&gt;Here is the link&lt;/a&gt; to the Codesandbox demo we built on this blogpost. Feel free to copy and use it on your apps.&lt;/p&gt;

</description>
      <category>react</category>
      <category>hooks</category>
      <category>components</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Hardening your server security with Fail2Ban</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Wed, 01 Jul 2020 15:34:58 +0000</pubDate>
      <link>https://dev.to/bpaulino0/hardening-your-server-security-with-fail2ban-3o1m</link>
      <guid>https://dev.to/bpaulino0/hardening-your-server-security-with-fail2ban-3o1m</guid>
      <description>&lt;p&gt;I was recently checking the access logs from some linux servers I maintain and I was very surprised by the ssh login attempts those servers were facing. My servers have password access disabled by default, so only previously registered ssh keys are allowed to login. But even then, the amount of login attempts was disturbing. Around 500 attempts a day. I had to do something about it.&lt;/p&gt;

&lt;p&gt;Talking to my coworkers about it, one of them suggested a tool called &lt;a href="https://www.fail2ban.org/wiki/index.php/Main_Page"&gt;Fail2Ban&lt;/a&gt;. It runs a background service that monitors the log files on your server and based on suspicious activities, like unsuccessful login attempts, it blocks access from those bad actors by updating firewall rules to reject any connection for their IP addresses.&lt;/p&gt;

&lt;p&gt;My first thought was &lt;em&gt;"I should be careful because I could ban myself and lose access to my server permanently"&lt;/em&gt;. So I started the installation process very carefully. Fortunately, the default configuration only blocks the IP access for 10 minutes, so worst case I would have a few minutes to have coffee. I also started the process in a "trashable" server, so if something goes wrong, I could just throw it away and start anew.&lt;/p&gt;

&lt;p&gt;We will customize those settings for a more robust strategy later on here on this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Fail2Ban
&lt;/h2&gt;

&lt;p&gt;Let's get started. The first step is to install Fail2Ban on your server (Ubuntu):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update dependencies&lt;/span&gt;
apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# install fail2ban&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail2Ban comes with a pretty solid default configuration, but since our goal is to customize it to our needs, they recommend us to copy the default configuration file with the &lt;code&gt;.local&lt;/code&gt; extension. The reason for this is that if we update Fail2Ban, the original configuration file will get changed and we will lose our custom configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put those bad guys in jail
&lt;/h2&gt;

&lt;p&gt;The configuration files are located at &lt;code&gt;/etc/fail2ban&lt;/code&gt;, so lets go ahead and create a local copy of those files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy fail2ban default configuration&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
&lt;span class="c"&gt;# Copy fail2ban jail configuration&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail2Ban uses the concept of &lt;code&gt;jails&lt;/code&gt; to monitor specific services like nginx, ssh, apache and so on. Each jail specifies a configuration for a specific application or service running on your server. By default, the &lt;code&gt;sshd&lt;/code&gt; jail is active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Activating Fail2Ban
&lt;/h2&gt;

&lt;p&gt;Now that we have Fail2Ban installed and pre-configured by default, lets start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl start fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as you start Fail2Ban, you might already see some bad guys blocked. First lets check which jails are active with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client status
&lt;span class="c"&gt;# You should see something like this as output:&lt;/span&gt;
Status
|- Number of jail:  2
&lt;span class="sb"&gt;`&lt;/span&gt;- Jail list: sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, so we have Fail2Ban up and running, lets check the &lt;code&gt;sshd&lt;/code&gt; jail with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client status sshd
&lt;span class="c"&gt;# You should see something like this as output&lt;/span&gt;
Status &lt;span class="k"&gt;for &lt;/span&gt;the jail: sshd
|- Filter
|  |- Currently failed: 10
|  |- Total failed: 511
|  &lt;span class="sb"&gt;`&lt;/span&gt;- File list:    /var/log/auth.log
&lt;span class="sb"&gt;`&lt;/span&gt;- Actions
   |- Currently banned: 9
   |- Total banned: 77
   &lt;span class="sb"&gt;`&lt;/span&gt;- Banned IP list:   218.255.86.106 222.186.31.83 85.209.48.228 180.166.184.66 218.92.0.220 109.255.185.65 150.158.178.137 111.231.132.94 66.70.130.149
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, I could immediately see the benefit of Fail2Ban. after a few minutes, I had already several IP addresses banned.&lt;/p&gt;

&lt;p&gt;Lets lookup our IP table to see if those IP addresses match with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt;
&lt;span class="c"&gt;# You should see something like this as output&lt;/span&gt;
Chain f2b-sshd &lt;span class="o"&gt;(&lt;/span&gt;1 references&lt;span class="o"&gt;)&lt;/span&gt;
target     prot opt &lt;span class="nb"&gt;source               &lt;/span&gt;destination         
REJECT     all  &lt;span class="nt"&gt;--&lt;/span&gt;  103.100.211.72       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  &lt;span class="nt"&gt;--&lt;/span&gt;  66.70.130.149        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  &lt;span class="nt"&gt;--&lt;/span&gt;  111.231.132.94       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  &lt;span class="nt"&gt;--&lt;/span&gt;  150.158.178.137      0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  &lt;span class="nt"&gt;--&lt;/span&gt;  109.255.185.65       0.0.0.0/0            reject-with icmp-port-unreachable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is great. Fail2Ban is updating our IP filter rules which will prevent connections from those bad actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing the defaults
&lt;/h2&gt;

&lt;p&gt;The default configuration blocks those IP addresses for 600 seconds (10 minutes). This is a pretty good start, but we can do better. Ideally, if some of those IP addresses are attempting to connect every 10 minutes, we could block them for a greater timespan or even permanently.&lt;/p&gt;

&lt;p&gt;One thing to consider is that if we block IPs permanently, we can potentially increase our IP table lookup time, which means that connecting to our server can become very slow since this list of blocked IPs can grow indefinitely.&lt;/p&gt;

&lt;p&gt;To help us with that, Fail2Ban comes with &lt;code&gt;recidive&lt;/code&gt; which is a jail for its own logs. It works like that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It looks into Fail2Ban own logs for banned IP addresses from other jails.&lt;/li&gt;
&lt;li&gt;If those IP addresses are found in the logs more than 5 times in the current day, it blocks them for 1 week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds like a good strategy. Our IP table won't grow very large (in theory) because in 1 week it will rollback and allow those IP addresses to connect again. If they act in bad faith again, they will be blocked and the cycle repeats.&lt;/p&gt;

&lt;p&gt;So let's go ahead and activate &lt;code&gt;recidive&lt;/code&gt;. Edit the file &lt;code&gt;/etc/fail2ban/jail.local&lt;/code&gt; (I am using nano, but feel free to use a different text editor). Look for the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Jail for more extended banning of persistent abusers
# !!! WARNINGS !!!
# 1. Make sure that your loglevel specified in fail2ban.conf/.local
#    is not at DEBUG level -- which might then cause fail2ban to fall into
#    an infinite loop constantly feeding itself with non-informative lines
# 2. Increase dbpurgeage defined in fail2ban.conf to e.g. 648000 (7.5 days)
#    to maintain entries for failed logins for sufficient amount of time
&lt;/span&gt;[&lt;span class="n"&gt;recidive&lt;/span&gt;]

&lt;span class="n"&gt;logpath&lt;/span&gt;  = /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;fail2ban&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
&lt;span class="n"&gt;banaction&lt;/span&gt; = %(&lt;span class="n"&gt;banaction_allports&lt;/span&gt;)&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;bantime&lt;/span&gt;  = &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;
&lt;span class="n"&gt;findtime&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the configuration is already in place. We only need to activate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# ... comments
&lt;/span&gt;[&lt;span class="n"&gt;recidive&lt;/span&gt;]
&lt;span class="c"&gt;# Include the next line to enable recidive
&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;logpath&lt;/span&gt;  = /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;fail2ban&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
&lt;span class="n"&gt;banaction&lt;/span&gt; = %(&lt;span class="n"&gt;banaction_allports&lt;/span&gt;)&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;bantime&lt;/span&gt;  = &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;
&lt;span class="n"&gt;findtime&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we only need to restart the Fail2Ban service with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's check our &lt;strong&gt;fail2ban&lt;/strong&gt; status to see which jails are running after our update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client status
&lt;span class="c"&gt;# You should see something like this as output&lt;/span&gt;
Status
|- Number of jail:  2
&lt;span class="sb"&gt;`&lt;/span&gt;- Jail list:   recidive, sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that &lt;code&gt;recidive&lt;/code&gt; is active, You can check if some IP addresses were banned for the whole week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client status recidive
&lt;span class="c"&gt;# You should see something like this as output&lt;/span&gt;
Status &lt;span class="k"&gt;for &lt;/span&gt;the jail: recidive
|- Filter
|  |- Currently failed: 25
|  |- Total failed: 90
|  &lt;span class="sb"&gt;`&lt;/span&gt;- File list:    /var/log/fail2ban.log
&lt;span class="sb"&gt;`&lt;/span&gt;- Actions
   |- Currently banned: 10
   |- Total banned: 10
   &lt;span class="sb"&gt;`&lt;/span&gt;- Banned IP list:   129.226.114.97 103.246.240.26 142.93.60.53 167.172.163.162 109.255.185.65 150.158.178.137 66.70.130.149 180.166.184.66 64.225.35.135 218.255.86.106
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have Fail2Ban monitoring our SSH access and fully configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do if I block myself
&lt;/h2&gt;

&lt;p&gt;You should be very careful with your server access from now on. Make sure that you have access from your computer and I highly recommend &lt;a href="https://www.cyberciti.biz/faq/how-to-disable-ssh-password-login-on-linux/"&gt;disabling password access&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can wait the default 10 minutes or you can access from a different IP address (like routing your mobile phone) and remove your IP address from the blocklist with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client &lt;span class="nb"&gt;set &lt;/span&gt;jailname unbanip &lt;span class="o"&gt;[&lt;/span&gt;IP_ADDRESS_HERE]
&lt;span class="c"&gt;# Here is how it could look like for you:&lt;/span&gt;
fail2ban-client &lt;span class="nb"&gt;set &lt;/span&gt;sshd unbanip 103.1.1.103
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;Fail2Ban is quite extensive and supports many different kinds of extensions like your own custom jails. The good thing is that amazing people around the world like to share their experience as well, so if you want to setup Fail2Ban for a different service like nginx, &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04"&gt;Digital Ocean has a great tutorial about it&lt;/a&gt; with a step-by-step guide.&lt;/p&gt;

&lt;p&gt;You can also read more about it on the &lt;a href="https://www.fail2ban.org/wiki/index.php/Main_Page"&gt;Fail2Ban official page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Dockerizing React Apps</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Tue, 03 Mar 2020 06:52:33 +0000</pubDate>
      <link>https://dev.to/bpaulino0/dockerizing-react-apps-591l</link>
      <guid>https://dev.to/bpaulino0/dockerizing-react-apps-591l</guid>
      <description>&lt;p&gt;While creating ReactJS apps, you probably don't have to think too much about how to deploy them. ReactJS applications can be easily bundled in a folder, consisting of plain HTML, CSS and Javascript files. That should be simple enough to upload it to a S3 Bucket, host it on &lt;a href="https://pages.github.com/"&gt;Github Pages&lt;/a&gt; or even integrating great services like &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; or &lt;a href="https://zeit.co/"&gt;Zeit&lt;/a&gt; for fast and automated deployments.  &lt;/p&gt;

&lt;p&gt;But this week, I had the task of deploying a React app created with &lt;a href="https://github.com/facebook/create-react-app"&gt;create-react-app&lt;/a&gt; on a VPS under a subdomain. I didn't want to use stone-age FTP, I wanted to have an automated docker container with my app where I could deploy anywhere without much configuration.  &lt;/p&gt;

&lt;p&gt;I created a demo app with all the configurations detailed on this post. The &lt;a href="https://github.com/brunojppb/dockerized-react-app"&gt;code is available here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing our Dockerfile
&lt;/h2&gt;

&lt;p&gt;We start out by creating a &lt;code&gt;Dockerfile&lt;/code&gt; on our project root folder with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# This image won't be shipped with our final container&lt;/span&gt;
&lt;span class="c"&gt;# we only use it to compile our app.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:12.2.0-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH /app/node_modules/.bin:$PATH&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# production image using nginx and including our&lt;/span&gt;
&lt;span class="c"&gt;# compiled app only. This is called multi-stage builds&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.16.0-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/build /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; /etc/nginx/conf.d/default.conf
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx/nginx.conf /etc/nginx/conf.d&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the snippet of code above, we are using a feature called &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/"&gt;multi-stage builds&lt;/a&gt;. It requires Docker 17.05 or higher, but the benefit of this feature is enormous, which I will explain next. On the first half of the script, we are building a Docker image based on &lt;code&gt;node:12.2.0-alpine&lt;/code&gt; which is a very tiny linux image with node included. Now notice the &lt;code&gt;as build&lt;/code&gt; at the end of the first line. This creates an intermediary image with our dependencies that can be thrown away after build. Soon after that, we install all the dependencies from my React app with &lt;code&gt;npm install&lt;/code&gt; and later we execute &lt;code&gt;npm run build&lt;/code&gt; to compile the React app optimized for production.  &lt;/p&gt;

&lt;p&gt;On the second half of the code, we create a new Docker image based on &lt;code&gt;nginx:1.16.0-alpine&lt;/code&gt; which is also a tiny linux including &lt;a href="https://www.nginx.com/"&gt;nginx&lt;/a&gt;, a high performance web server to serve our React app. We use the command &lt;code&gt;COPY&lt;/code&gt; to extract the content from our previous image called &lt;code&gt;build&lt;/code&gt; and copy it into &lt;code&gt;/usr/share/nginx/html&lt;/code&gt;. Next, we remove the default nginx configuration file and add our custom configuration under &lt;code&gt;nginx/nginx.conf&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# To support react-router, we must configure nginx&lt;/span&gt;
&lt;span class="c1"&gt;# to route the user to the index.html file for all initial requests&lt;/span&gt;
&lt;span class="c1"&gt;# e.g. landing on /users/1 should render index.html&lt;/span&gt;
&lt;span class="c1"&gt;# then React takes care of mouting the correct routes&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt;   &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt;  &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kn"&gt;error_page&lt;/span&gt;   &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;  &lt;span class="n"&gt;/50x.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/50x.html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt;   &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration is very important for apps using &lt;a href="https://reacttraining.com/react-router/web/guides/quick-start"&gt;React Router&lt;/a&gt;. Whenever you share a link to your React app, lets say, a link to &lt;code&gt;/users/1/profile&lt;/code&gt;, this link tells the browser to request this path from the web server. If the web server is not configured properly, our React app won't be able to render the initial &lt;strong&gt;index.html&lt;/strong&gt; file containing our React application.&lt;br&gt;&lt;br&gt;
Using our custom configuration, we tell nginx to route all requests to the root folder &lt;code&gt;/usr/share/nginx/html&lt;/code&gt; which is the directory we previously copied our React app during image build. We should not forget that React apps are Single Page Applications, which means that there is only one page to be rendered on the first request, the rest of the job is taken care by React on the browser.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building our Docker Image
&lt;/h2&gt;

&lt;p&gt;We already have all the required code to build our Docker image. Lets execute the Docker command to build it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make sure to be on the same folder of your React app&lt;/span&gt;
&lt;span class="c"&gt;# replace 'my-react-app' with whatever name you find appropriate&lt;/span&gt;
&lt;span class="c"&gt;# this is the image tag you will push to your Docker registry&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-react-app &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the image is built, lets check the size of the image we just generated with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all the images on your machine&lt;/span&gt;
docker images
&lt;span class="c"&gt;# You should see something like this:&lt;/span&gt;
REPOSITORY     TAG       IMAGE ID        CREATED          SIZE
my-react-app   latest    c35c322d4c37    20 seconds ago   22.5MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, our Docker image is ready to go on to a Docker Registry somewhere. One interesting thing about this image is that the size is only 22.5MB. This is really great for deployment because small images make automated pipelines run much faster during download, image building and upload.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Running our React app with docker-compose
&lt;/h2&gt;

&lt;p&gt;What we need now is a way to run this Docker image. For testing it locally, lets create a file called &lt;code&gt;docker-compose.yml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;my_react_app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8000:80'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.docker.com/compose/"&gt;Docker Compose&lt;/a&gt; will take care of building the image in case it doesn't exist and also bind the port &lt;code&gt;8000&lt;/code&gt; from our local machine to the port &lt;code&gt;80&lt;/code&gt; on the container. &lt;/p&gt;

&lt;p&gt;Lets spin up our container with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open your browser on &lt;code&gt;localhost:8000&lt;/code&gt; and check if our React app is running there. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_1kAHUV8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/react_js_app_docker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_1kAHUV8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/react_js_app_docker.png" alt="React JS App running on Docker" width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Running a React app with Docker might not be the best deployment, but if you need to run docker like in my case, it can be very simple and effective. This opens the door for a lot of automation pipelines you can hook up on the project like &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; or &lt;a href="https://docs.gitlab.com/ee/ci/"&gt;Gitlab CI/CD&lt;/a&gt; to automate your deployment process. You can find &lt;a href="https://github.com/brunojppb/dockerized-react-app"&gt;the code of this post here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>docker</category>
      <category>webapp</category>
      <category>devops</category>
    </item>
    <item>
      <title>Rapid Prototyping with GatsbyJS</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Sat, 29 Feb 2020 13:19:42 +0000</pubDate>
      <link>https://dev.to/bpaulino0/rapid-prototyping-with-gatsbyjs-4244</link>
      <guid>https://dev.to/bpaulino0/rapid-prototyping-with-gatsbyjs-4244</guid>
      <description>&lt;p&gt;On February 17th, I &lt;a href="https://www.meetup.com/ReactVienna/events/268478297/"&gt;gave a talk at the React Vienna Meetup&lt;/a&gt; about this topic and I thought writing a blogpost about could benefit some folks that either couldn't make it or are living somewhere else around the globe. You can find my slides &lt;a href="https://bpaulino.com/rapid-prototyping-gatsby-js/"&gt;here.&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;I would like to start off with a question. What if you want to put together a website with the most modern web tech out there? You probably need to know a bunch of things to start a simple website. Do you want to use modern Javascript syntax? no problem, just use &lt;a href="https://webpack.js.org/"&gt;Webpack&lt;/a&gt; and &lt;a href="https://babeljs.io/"&gt;Babel&lt;/a&gt; to help you transpile your code. Do you want to put your website online? just configure an small box on Digital Ocean, add Nginx and your website is online. Do you want to write blogposts and publish them using a CMS? No problem, just implement your backend, maybe using Postgres as a database. This list goes on and on if you try to start with a modern tool chain.  &lt;/p&gt;

&lt;p&gt;To get this whole setup done, and most importantly, correctly configured, can be hard. It is even worse if you are a beginner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VkUqiEWR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/stackoverflow_hard_time.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VkUqiEWR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/stackoverflow_hard_time.jpg" alt="A beginner having a hard time on Stackoveflow" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Static site generators come to the rescue
&lt;/h2&gt;

&lt;p&gt;So you realize that this is an enormous amount of tools (and knowledge) you need to know upfront to start with a simple website. That is really overwhelming. This is where static site generators really shine. There is usually minimal to none configuration you have to do, there are many templates you can use to start with and most of the configuration thing is done for you. Some cool static site generators are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/"&gt;Jekyll (Ruby)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/"&gt;Hugo (Javascript)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vuejs.org/"&gt;NuxtJS (Vue.js)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gatsbyjs.org/"&gt;GatsbyJS (ReactJS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most interesting one that caught my attention lately was &lt;a href="https://www.gatsbyjs.org/"&gt;GatsbyJS.&lt;/a&gt; I have been working with React for the past 3 years and finding a decent static site generator based on React was very interesting. Right from the start, it solves all the problems I mentioned before. You don't have to configure anything to start. You only need the minimal tools installed like a &lt;a href="https://code.visualstudio.com/"&gt;good text editor&lt;/a&gt; and &lt;a href="https://nodejs.org/en/"&gt;node.js&lt;/a&gt;. There is also no need to have a server for hosting. It generates all the pages statically, which means that when you bundle your website, you have one HTML file for each page of your website, just like the web is supposed to work. And simple enough, you can just upload those files to a static site hosting provider like &lt;a href="https://www.netlify.com/"&gt;netlify&lt;/a&gt; or even use &lt;a href="https://pages.github.com/"&gt;Github Pages&lt;/a&gt; to host it for free.  &lt;/p&gt;

&lt;p&gt;Gatsby has an incredible feature that sets it apart from other generators: Your datasource doesn't have to be statically located within the code, like markdown files. It can be pulled from a remote datasource, like a headless CMS, before generating the pages.&lt;br&gt;&lt;br&gt;
That is a big deal if you want the freedom of publishing blogposts from a nice CMS or you have a team of content creators that are simultaneously creating content. Their don't have to be developers to be able to create a blog post, and Gatsby enables exactly that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Our little experiment
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://woombikes.com/"&gt;woom&lt;/a&gt; we are modernizing our tech stack. One interesting project we had was to rebuild our blog frontend, but in a way that we don't get stuck at a specific platform like we had before (using our ERP). As the main engineer responsible for this project, I decided to give Gatsby a try. I was impressed with the development speed we had using it to implement our blog prototype. We ended up using &lt;a href="https://www.hubspot.com/"&gt;Hubspot&lt;/a&gt; as our CMS since we are using it for other marketing purposes, but the frontend is still portable if we decide to shift to a new CMS. &lt;a href="https://blog.woombikes.com"&gt;You can take a look at our blog here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how it looks like after we got it done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--APCSgWqX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/woom_blog_prototype.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--APCSgWqX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/woom_blog_prototype.png" alt="woom blog using GatsbyJS" width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what are the main advantages of Gatsby anyway? What really made us to look at it differently?&lt;/p&gt;
&lt;h3&gt;
  
  
  It uses React
&lt;/h3&gt;

&lt;p&gt;If you never heard of &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; before, it is the state-of-the-art of frontend libraries. Writing frontend with React requires a different approach, but it definitely pays off. It lets you break your application into reusable components, which makes it infinitely simpler to reason about it and debug.&lt;/p&gt;
&lt;h3&gt;
  
  
  React hooks.
&lt;/h3&gt;

&lt;p&gt;Even though Gatsby generates static pages, you can still benefit from React state management. hooks like &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useContext&lt;/code&gt; are still there to help us have a more dynamic page.&lt;/p&gt;
&lt;h3&gt;
  
  
  GraphQL included
&lt;/h3&gt;

&lt;p&gt;Using the built-in GraphQL API, you can pull data from a remote datasource, process static files from your local repository, like lets say images and transform them to alternative resolutions for different device sizes and so on.&lt;/p&gt;
&lt;h3&gt;
  
  
  Development and Production configuration out-of-the-box.
&lt;/h3&gt;

&lt;p&gt;You don't have to hassle around Webpack configurations, pipelines or anything like this. The basics, which is already what you need to start, is already there, properly configured and ready to row with the single command &lt;code&gt;yarn develop&lt;/code&gt; during development and  &lt;code&gt;yarn build&lt;/code&gt; to deploy a new build.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rich plugin ecosystem
&lt;/h3&gt;

&lt;p&gt;When looking for a framework, you usually have to consider how flexible it can be, so you can benefit from the community, using code extensions and plugins. Gatsby has a very rich plugin ecosystem, and the most important ones, like &lt;a href="https://www.gatsbyjs.org/packages/gatsby-image/"&gt;gatsby-image&lt;/a&gt; for image processing are already there, provided and maintained by Gatsby directly, which is a good sign that will be kept up2date.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lets try to build something
&lt;/h2&gt;

&lt;p&gt;For this &lt;del&gt;talk&lt;/del&gt; blogpost, I created a little demo. This is a website with a blog section. The blogposts come from &lt;a href="https://www.storyblok.com/"&gt;the headless CMS Storyblok.&lt;/a&gt;. You can find the source code of &lt;a href="https://github.com/brunojppb/gatsby-minimal-blog"&gt;this demo here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6pDm-AY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/gatsby_minimal_website.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6pDm-AY2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/gatsby_minimal_website.png" alt="Gatsby minimal website with Blog component" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone the &lt;a href="https://github.com/brunojppb/gatsby-minimal-blog"&gt;demo repo&lt;/a&gt; and execute the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repo&lt;/span&gt;
git clone git@github.com:brunojppb/gatsby-minimal-blog.git
&lt;span class="c"&gt;# Now lets go to our newly created project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;gatsby-minimal-blog
&lt;span class="c"&gt;# install all dependencies&lt;/span&gt;
yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# and fire up the dev server&lt;/span&gt;
yarn develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets go to our browser and type in &lt;code&gt;localhost:8000&lt;/code&gt; to see what happens.&lt;br&gt;
You will see a nice and bare-minimum website, very similar to the screenshot I posted above, but with a link to the blog section of the website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gRwy9v6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/gatsby_minimal_website_home.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gRwy9v6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/posts/gatsby_minimal_website_home.png" alt="Gatsby Minimal website home page" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you take a look at the terminal, you will see that there was an error with a plugin. something like &lt;code&gt;failed createPages - 0.193s&lt;/code&gt;. This is because when we hit &lt;code&gt;yarn develop&lt;/code&gt; Gatsby kicks in and try to fetch our posts from Storyblok. But since you don't have an account there yet, it can't find out your API token to access your blogposts. So go ahead and open &lt;a href="https://app.storyblok.com/#!/"&gt;Storyblok&lt;/a&gt; to create your account and select the demo.&lt;br&gt;&lt;br&gt;
After signing in, you will that there is a &lt;strong&gt;demo space&lt;/strong&gt; created for you. Don't get bogged down with the details, Lets just try to create content there. On the left-hand side, click on &lt;strong&gt;Content&lt;/strong&gt;, inside content, you will see a list of different assets. You have to delete all of them, but leave &lt;strong&gt;Blog Articles&lt;/strong&gt;. Now select &lt;strong&gt;Blog Articles&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
You will see a list of different contents, select all of them and delete it. We will create a new article from scratch.&lt;br&gt;
On the top-right corner, select &lt;strong&gt;"+Entry"&lt;/strong&gt;. You will be asked to enter a title. On the &lt;code&gt;content type&lt;/code&gt; dialog, select &lt;code&gt;article&lt;/code&gt; and hit create. After this screen, you will land on a CMS-like page where you can write on the right-hand side menu and see it live rendering on the left-hand side. Try it out, update the title field and write something into the &lt;strong&gt;long text&lt;/strong&gt; dialog. When you are happy with it, just hit &lt;strong&gt;publish&lt;/strong&gt; on the top-right corner.  &lt;/p&gt;

&lt;p&gt;Now lets get the API Key to access our content. To to settings -&amp;gt; API-Keys. There you have an API token called &lt;code&gt;public&lt;/code&gt;. copy it to your clipboard. Now head back to your text editor and create a file called &lt;code&gt;.env.development&lt;/code&gt; on your project root folder. and inside of it, add your API token in the following pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CMS_API_TOKEN=YOUR_API_TOKEN_HERE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, restart your development server. Go to your browser and click on &lt;code&gt;Visit our blog section&lt;/code&gt;. You should see your recently created article there. You can click on it and to directly to the article page. But what happened there? Lets take a look at the a file called &lt;code&gt;gatsby-node.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.storyblok.com/v1/cdn/stories`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CMS_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;space.version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Create articles list page&lt;/span&gt;
  &lt;span class="nf"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/templates/all_articles.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stories&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// create each individual article page&lt;/span&gt;
  &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/article/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/templates/article.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Don't do this at home :)&lt;/span&gt;
            &lt;span class="c1"&gt;// You need to sanitize this HTML first&lt;/span&gt;
            &lt;span class="na"&gt;long_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;marked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;long_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a lifecycle script executed by Gatsby during build time. What this script does is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch all articles from Storyblok&lt;/li&gt;
&lt;li&gt;create an index page with all the articles so you have a complete list&lt;/li&gt;
&lt;li&gt;create individual pages with each article&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you look into &lt;code&gt;src/templates/all_article.js&lt;/code&gt; you will see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gatsby&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AllArticles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;pageContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"article-list"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/article/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;AllArticles&lt;/strong&gt; component follows a pattern imposed by Gatsby. It must receive a prop called &lt;code&gt;pageContext&lt;/code&gt;. This necessary for Gatsby to inject the content at build-time and render the component to generate the static pages. If you follow the convention, you get all this magic for free.&lt;br&gt;&lt;br&gt;
The same thing happens to the &lt;strong&gt;Article&lt;/strong&gt; component. it expects a &lt;code&gt;pageContext&lt;/code&gt; prop and from there on, you can create your React component in the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;This example was very naive and simplistic, but you can already have a feeling of what Gatsby can do. If you are considering to develop a new website and don't want to waste your time with tooling configuration, give Gatsby a try. It will be probably worth it.&lt;br&gt;&lt;br&gt;
We are now evaluating if makes sense or not to build out Shop frontend with it in a way that supports multi-language. I got some interesting feedback from folks using Gatsby in production during my React Vienna talk that will definitely help to try it out.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
    </item>
    <item>
      <title>Automating your work with Github Actions</title>
      <dc:creator>Bruno Paulino</dc:creator>
      <pubDate>Sat, 02 Nov 2019 14:04:08 +0000</pubDate>
      <link>https://dev.to/bpaulino0/automating-your-work-with-github-actions-h01</link>
      <guid>https://dev.to/bpaulino0/automating-your-work-with-github-actions-h01</guid>
      <description>&lt;p&gt;I have finally joined the &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; beta program and figured why not play with it for a bit and see what I can do. So my first idea was to automate the deployment process of my blog, this one you are currently reading. I am currently using &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; as my static site generator. It works flawless for what I need. I just write whatever I want using &lt;a href="https://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt; and Jekyll digests everything inside my source folder and spits out HTML, CSS and JS files in a &lt;strong&gt;"ready-to-publish"&lt;/strong&gt; folder where I can just upload to the cloud.&lt;/p&gt;

&lt;p&gt;I am currently using &lt;a href="https://pages.github.com/"&gt;Github Pages&lt;/a&gt; to host my blog and it has been working perfectly fine for the past couple years.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how does a Github Action work anyway?
&lt;/h2&gt;

&lt;p&gt;Github Actions is a way to perform tasks automatically for you. To give you an example, I will use my blog workflow.&lt;br&gt;&lt;br&gt;
It all starts when I want to write a new post. I just create a new markdown file, write down whatever is on my head and save it. After this whole process, I need a way to transform my text in a website. Jekyll is doing the heavy-lifting for me, so I just go to my terminal and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This command will generate my entire website and all its dependencies&lt;/span&gt;
jekyll build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After generating all the necessary files, I need to upload it somewhere. In this case, I just have to commit my changes to a specific branch called &lt;strong&gt;gh-pages&lt;/strong&gt; and Github will serve my site on the web. For doing that, I usually perform the following commands in a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This is the folder Jekyll generates with my website. Lets just open it&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;_site
&lt;span class="c"&gt;# Now we need a new git repository here, &lt;/span&gt;
&lt;span class="c"&gt;# so I can commit only the generated files and skip the source files&lt;/span&gt;
git init
git config user.name &lt;span class="s2"&gt;"Bruno Paulino"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"bruno@bpaulino.com"&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# That will create a nice commit message with something like: &lt;/span&gt;
&lt;span class="c"&gt;# New Build - Fri Sep 6 12:32:22 UTC 2019&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"New Build - &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# Now lets push my commit to the gh-pages branch and replace everything there&lt;/span&gt;
&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://brunojppb@github.com/brunojppb.github.io.git
git push &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nv"&gt;$REPO&lt;/span&gt; master:gh-pages
&lt;span class="c"&gt;# Lets do some cleanup here since we don't need the generated files anymore&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-fr&lt;/span&gt; .git
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; _site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is pretty simple right? It is indeed, but how cool would that be if Github could do that for me instead? That is where Github Actions come to give us a hand.  &lt;/p&gt;

&lt;p&gt;It all starts with a folder on your repository called &lt;code&gt;.github/workflows&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
inside of this folder, create a file called &lt;code&gt;deploy-workflow.yml&lt;/code&gt; with the content below. Each line will be explained with a comment:&lt;/p&gt;
&lt;h3&gt;
  
  
  deploy-workflow.yml
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is the name of our workflow.&lt;/span&gt;
&lt;span class="c1"&gt;# Github will show it on its Website UI&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;span class="c1"&gt;# This configures our workflow to be triggered&lt;/span&gt;
&lt;span class="c1"&gt;# only when we push to the master branch&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="c1"&gt;# Here is where we define our jobs. &lt;/span&gt;
&lt;span class="c1"&gt;# Which means the tasks we want Github to execute&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
    &lt;span class="c1"&gt;# Here we specify in whith OS we want it to run&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-18.04&lt;/span&gt;
    &lt;span class="c1"&gt;# Now we define which actions will take place.&lt;/span&gt;
    &lt;span class="c1"&gt;# One after another&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# This is the first action. It will make sure that we have&lt;/span&gt;
      &lt;span class="c1"&gt;# all the necessary files from our repo, including our custom actions&lt;/span&gt;
      &lt;span class="c1"&gt;# This action here is actually from a remote repo available from Githup itself&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt; 
      &lt;span class="c1"&gt;# This is our custom action. Here is where we will define our git commands&lt;/span&gt;
      &lt;span class="c1"&gt;# to push our website updates to the `gh-pages` branch.&lt;/span&gt;
      &lt;span class="c1"&gt;# Notice that we are specifying the path to the action here.&lt;/span&gt;
      &lt;span class="c1"&gt;# We will create those files in a sec&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/build-dist-site&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Now make sure you add this environment variable.&lt;/span&gt;
          &lt;span class="c1"&gt;# This token will allow us to push to github directly&lt;/span&gt;
          &lt;span class="c1"&gt;# without having to type in our password.&lt;/span&gt;
          &lt;span class="c1"&gt;# The GITHUB_TOKEN is available by default&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.GITHUB_TOKEN"&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt; &lt;span class="err"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now lets create our custom action. &lt;a href="https://help.github.com/en/articles/about-actions#types-of-github-actions"&gt;Github Actions are divided in 2 types&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker container&lt;/li&gt;
&lt;li&gt;Javascript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are running our action using a Docker Container. Using Docker, we make sure the environment where our scripts are running will be the same, no matter what happens to the Github environment. So, lets dig deeper and create our &lt;code&gt;actions&lt;/code&gt; folder under &lt;code&gt;.github&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build-dist-site will be the folder for holding&lt;/span&gt;
&lt;span class="c"&gt;# our action configuration (Dockerfile, scripts and Metadata)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/actions/build-dist-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;code&gt;.github/actions/build-dist-site&lt;/code&gt; lets create 3 files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;action.yml&lt;/code&gt;: It will hold the metadata of our action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile:&lt;/code&gt; Will specify our Docker image to run Jekyll in a container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;entrypoint.sh:&lt;/code&gt; Will have our custom scripts to generate and deploy our website update&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Our Docker image will be based on ruby:2-slim&lt;/span&gt;
&lt;span class="c"&gt;# it is a very light docker image.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2-slim&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; author="Bruno Paulino"&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; version="1.0.0"&lt;/span&gt;

&lt;span class="c"&gt;# Lets install all dependencies&lt;/span&gt;
&lt;span class="c"&gt;# including git and Bundler 2.0.2&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; BUNDLER_VERSION 2.0.2&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        bats &lt;span class="se"&gt;\
&lt;/span&gt;        build-essential &lt;span class="se"&gt;\
&lt;/span&gt;        ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;        curl &lt;span class="se"&gt;\
&lt;/span&gt;        libffi6 &lt;span class="se"&gt;\
&lt;/span&gt;        make &lt;span class="se"&gt;\
&lt;/span&gt;        shellcheck &lt;span class="se"&gt;\
&lt;/span&gt;        libffi6 &lt;span class="se"&gt;\
&lt;/span&gt;        git-all &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler:2.0.2 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bundle config &lt;span class="nt"&gt;--global&lt;/span&gt; silence_root_warning 1

&lt;span class="c"&gt;# This is our entrypoint to our custom scripts&lt;/span&gt;
&lt;span class="c"&gt;# more about that in a sec&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; entrypoint.sh /&lt;/span&gt;

&lt;span class="c"&gt;# Use the entrypoint.sh file as the container entrypoint&lt;/span&gt;
&lt;span class="c"&gt;# when Github executes our Docker container&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["sh", "/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our Dockerfile ready, we need to tell Github to use it. That is why we need the &lt;code&gt;action.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  action.yml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ok, here the keys are pretty much self explanatory :)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version'&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Setup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Ruby&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bruno&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Paulino'&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dockerfile'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;action.yml&lt;/code&gt; file tells Github what to do. In this case, just tell it to use Docker and use our &lt;code&gt;Dockerfile&lt;/code&gt; to build the container with it.&lt;/p&gt;

&lt;p&gt;Now we just need our &lt;code&gt;entrypoint.sh&lt;/code&gt; script to execute our website generation and deployment. Lets get our hands dirty with a bit of bash script:&lt;/p&gt;

&lt;h3&gt;
  
  
  entrypoint.sh
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Exit immediately if a pipeline returns a non-zero status.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🚀 Starting deployment action"&lt;/span&gt;

&lt;span class="c"&gt;# Here we are using the variables&lt;/span&gt;
&lt;span class="c"&gt;# - GITHUB_ACTOR: It is already made available for us by Github. It is the username of whom triggered the action&lt;/span&gt;
&lt;span class="c"&gt;# - GITHUB_TOKEN: That one was intentionally injected by us in our workflow file.&lt;/span&gt;
&lt;span class="c"&gt;# Creating the repository URL in this way will allow us to `git push` without providing a password&lt;/span&gt;
&lt;span class="c"&gt;# All thanks to the GITHUB_TOKEN that will grant us access to the repository&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_ACTOR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@github.com/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git"&lt;/span&gt;

&lt;span class="c"&gt;# We need to clone the repo here.&lt;/span&gt;
&lt;span class="c"&gt;# Remember, our Docker container is practically pristine at this point&lt;/span&gt;
git clone &lt;span class="nv"&gt;$REMOTE_REPO&lt;/span&gt; repo
&lt;span class="nb"&gt;cd &lt;/span&gt;repo

&lt;span class="c"&gt;# Install all of our dependencies inside the container&lt;/span&gt;
&lt;span class="c"&gt;# based on the git repository Gemfile&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚡️ Installing project dependencies..."&lt;/span&gt;
bundle &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Build the website using Jekyll&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🏋️ Building website..."&lt;/span&gt;
&lt;span class="nv"&gt;JEKYLL_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;jekyll build
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Jekyll build done"&lt;/span&gt;

&lt;span class="c"&gt;# Now lets go to the generated folder by Jekyll&lt;/span&gt;
&lt;span class="c"&gt;# and perform everything else from there&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;_site

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"☁️ Publishing website"&lt;/span&gt;

&lt;span class="c"&gt;# We don't need the README.md file on this branch&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; README.md

&lt;span class="c"&gt;# Now we init a new git repository inside _site&lt;/span&gt;
&lt;span class="c"&gt;# So we can perform a commit&lt;/span&gt;
git init
git config user.name &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_ACTOR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_ACTOR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@users.noreply.github.com"&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# That will create a nice commit message with something like: &lt;/span&gt;
&lt;span class="c"&gt;# Github Actions - Fri Sep 6 12:32:22 UTC 2019&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Github Actions - &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Build branch ready to go. Pushing to Github..."&lt;/span&gt;
&lt;span class="c"&gt;# Force push this update to our gh-pages&lt;/span&gt;
git push &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nv"&gt;$REMOTE_REPO&lt;/span&gt; master:gh-pages
&lt;span class="c"&gt;# Now everything is ready.&lt;/span&gt;
&lt;span class="c"&gt;# Lets just be a good citizen and clean-up after ourselves&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-fr&lt;/span&gt; .git
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; repo
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🎉 New version deployed 🎊"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤯 That was a lot different from what I started with right? Ok, the reason for that is just Docker. Now we have a more robust implementation of our deployment pipeline where we could even move away from Github to Gitlab and reuse the Dockerfile and entrypoint.sh (with minor changes).&lt;/p&gt;

&lt;p&gt;Now that we are armed with those files, lets commit our changes and push to Github and see what happens. Going to our Github repository page, there you can see a new button called &lt;strong&gt;Actions&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1yqQPdkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_actions_button.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1yqQPdkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_actions_button.jpg" alt="Github Actions button" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets click on it. You will be taken to the &lt;strong&gt;Workflows&lt;/strong&gt; list. There we see our &lt;strong&gt;Deploy&lt;/strong&gt; workflow we just created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hzkuL48f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_workflows_running.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hzkuL48f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_workflows_running.jpg" alt="Github Actions workflows running" width="800" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now inside of our workflow execution context, we can see all of our actions being executed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--miHX6GqT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_execution_pipeline.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--miHX6GqT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_execution_pipeline.jpg" alt="Github Actions execution pipeline" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, now our automation work was fully done. As a cherry on top, you can also add a badge to your README.md file showing the current status of your custom actions like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Where /deploy/ must be replaced with your workflow name&lt;/span&gt;
&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;workflow-badge&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/brunojppb/brunojppb.github.io/workflows/deploy/badge.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will render a nice image by Github on your repository page with the current action status.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jq3Wi1RS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_action_badge.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jq3Wi1RS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bpaulino.com/assets/images/github_action_badge.jpg" alt="Github Actions Badge" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I can enjoy my time spent building and deploying my website doing something else like playing videogames 🎮 or drawing 🎨. Here is the open-source repository of my blog if you want to take a look:&lt;br&gt;
&lt;a href="https://github.com/brunojppb/brunojppb.github.io"&gt;https://github.com/brunojppb/brunojppb.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>pipelines</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
