<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title type="text">Jonathas Ribeiro</title>
<generator uri="https://github.com/jekyll/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="https://jonathas.com/feed.xml" />
<link rel="alternate" type="text/html" href="https://jonathas.com" />
<updated>2026-03-09T09:21:07+00:00</updated>
<id>https://jonathas.com/</id>
<author>
  <name>Jonathas Ribeiro</name>
  <uri>https://jonathas.com/</uri>
  <email>contact@jonathas.com</email>
</author>


<entry>
  <title type="html"><![CDATA[How I replaced Dropbox Camera Uploads with an automated photo sync pipeline]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/how-i-replaced-dropbox-camera-uploads-with-an-automated-photo-sync-pipeline/" />
  <id>https://jonathas.com/how-i-replaced-dropbox-camera-uploads-with-an-automated-photo-sync-pipeline</id>
  <published>2026-03-08T16:39:19+00:00</published>
  <updated>2026-03-08T16:39:19+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;I run a weekly photo cleanup routine because I want two things at the same time:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Only curated photos in iCloud&lt;/li&gt;
  &lt;li&gt;Real files on disk for archive and backup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like having my photos as normal files. I can inspect them, back them up to external drives, and know exactly where they live instead of relying entirely on a cloud service.&lt;/p&gt;

&lt;p&gt;For years, Dropbox Camera Uploads handled an important part of this workflow: photos arriving on my MacBook as normal files.&lt;/p&gt;

&lt;p&gt;I use Apple Photos for curation, not as my archive, so that behavior mattered a lot.&lt;/p&gt;

&lt;p&gt;When I started considering alternatives to Dropbox (for example Proton Drive), the gap became obvious. Sync exists, but getting &lt;strong&gt;camera photos automatically as files on disk&lt;/strong&gt; is not guaranteed.&lt;/p&gt;

&lt;p&gt;Once Dropbox Camera Uploads stopped being the center of my workflow, I had to rebuild that behavior myself.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;before&quot;&gt;Before&lt;/h2&gt;

&lt;p&gt;My weekly flow looked like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Let Dropbox Camera Uploads sync iPhone photos and videos to my MacBook.&lt;/li&gt;
  &lt;li&gt;Check duplicates and curate what I wanted to keep in iCloud.&lt;/li&gt;
  &lt;li&gt;Run the &lt;a href=&quot;https://github.com/jonathas/pbc-organizer&quot;&gt;pbc-organizer&lt;/a&gt; script I implemented to rename and move files by EXIF date.&lt;/li&gt;
  &lt;li&gt;Move files into backup folders.&lt;/li&gt;
  &lt;li&gt;Choose photos for my digital frame.&lt;/li&gt;
  &lt;li&gt;Resize and convert them.&lt;/li&gt;
  &lt;li&gt;Send them to the digital frame/tablet using &lt;a href=&quot;https://localsend.org/&quot;&gt;LocalSend&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Run backup scripts to external drives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This workflow worked, but it was repetitive and fragile. The curation decisions were valuable. The glue work was not.&lt;/p&gt;

&lt;p&gt;After I &lt;a href=&quot;https://jonathas.com/how-i-replaced-my-frameo-digital-frame-with-an-android-tablet/&quot;&gt;replaced my old Frameo digital frame with an Android tablet running Fotoo&lt;/a&gt;, the display and system improved, but the workflow was still mostly manual.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-wanted-instead&quot;&gt;What I wanted instead&lt;/h2&gt;

&lt;p&gt;The idea was simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Apple Photos can handle syncing and curation, while automation handles exporting and filesystem organization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built a small pipeline to restore the capability I cared about from Dropbox Camera Uploads:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Photos arriving through sync&lt;/li&gt;
  &lt;li&gt;Photos available as normal files on disk&lt;/li&gt;
  &lt;li&gt;Deterministic backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;osxphotos&lt;/code&gt; for exporting from Apple Photos&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sips&lt;/code&gt; for image conversion and resizing&lt;/li&gt;
  &lt;li&gt;ADB over Wi-Fi for tablet transfer&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; for backups&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-automated-pipeline&quot;&gt;The automated pipeline&lt;/h2&gt;

&lt;p&gt;You can find the scripts in the &lt;a href=&quot;https://github.com/jonathas/photo-sync-pipeline&quot;&gt;photo-sync-pipeline repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I still manually decide what to delete, keep, and send to the digital frame. After that, scripts handle the mechanical steps.&lt;/p&gt;

&lt;p&gt;The pipeline now does the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Export photos and videos since a given date&lt;/li&gt;
  &lt;li&gt;Place them into predictable directories&lt;/li&gt;
  &lt;li&gt;Export the curated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Digital frame&lt;/code&gt; album&lt;/li&gt;
  &lt;li&gt;Normalize file extensions&lt;/li&gt;
  &lt;li&gt;Convert HEIC to JPG&lt;/li&gt;
  &lt;li&gt;Resize and compress frame photos&lt;/li&gt;
  &lt;li&gt;Push them to Android via ADB&lt;/li&gt;
  &lt;li&gt;Trigger a media scan&lt;/li&gt;
  &lt;li&gt;Restart Fotoo&lt;/li&gt;
  &lt;li&gt;Clear the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Digital frame&lt;/code&gt; album&lt;/li&gt;
  &lt;li&gt;Run backup sync jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typical commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export-photos-since 2026-01-25
export-digital-frame
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export-photos-since&lt;/code&gt; effectively replaces the “files on disk” behavior of Dropbox Camera Uploads.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;small-pipeline-diagram&quot;&gt;Small pipeline diagram&lt;/h2&gt;

&lt;p&gt;This is the operational flow I usually follow each week:&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart TD
    A[Open Apple Photos] --&amp;gt; B[Delete duplicates]
    B --&amp;gt; C[Wait for iCloud sync]
    C --&amp;gt; D[Run export-photos-since]
    D --&amp;gt; E[Add selected photos to Digital frame album]
    E --&amp;gt; F[Run export-digital-frame]
    F --&amp;gt; G[Tablet updated]
    F --&amp;gt; H[Backups synced]
&lt;/div&gt;

&lt;p&gt;It is still a curated workflow, not a fully automatic one. That is intentional, since I want the decisions to stay manual and the repetitive steps to be scripted.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;example-exported-filesystem-structure&quot;&gt;Example exported filesystem structure&lt;/h2&gt;

&lt;p&gt;One of the main goals was to keep the exported files easy to inspect and back up. A simplified structure looks like this:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2026/
├── 01 - January                           
│   ├── 2026-01-01                                                                          
│   │   ├── 2026-01-01 09.29.20.heic       
│   │   ├── 2026-01-01 09.29.23.heic                                                        
│   │   ├── 2026-01-01 09.29.24.heic       
│   │   ├── 2026-01-01 09.35.04.heic                                                        
│   │   ├── 2026-01-01 09.35.07.heic      
│   │   ├── 2026-01-01 09.35.38.heic      
│   │   └── 2026-01-01 18.32.27.heic      
│   ├── 2026-01-02                        
│   │   ├── 2026-01-02 18.26.47.mov 
│   │   └── 2026-01-02 18.26.51.jpg        
│   ├── 2026-01-03                  
│   │   ├── 2026-01-03 09.32.47.heic       
│   │   ├── 2026-01-03 09.32.48.heic       
│   │   ├── 2026-01-03 09.32.52.heic       
│   │   ├── 2026-01-03 09.32.54.heic       
│   │   ├── 2026-01-03 09.32.55.heic       
│   │   ├── 2026-01-03 09.32.59.heic       
│   │   ├── 2026-01-03 10.46.40.heic       
│   │   ├── 2026-01-03 10.46.42.heic
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The exact layout can vary, but the important parts are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deterministic paths&lt;/li&gt;
  &lt;li&gt;Files visible in normal directories&lt;/li&gt;
  &lt;li&gt;A separate place for digital frame exports&lt;/li&gt;
  &lt;li&gt;Backup destinations that can be synced repeatedly&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-is-better&quot;&gt;Why this is better&lt;/h2&gt;

&lt;p&gt;The main improvement is reduced mental overhead.&lt;/p&gt;

&lt;p&gt;Before, I had to remember a long sequence of manual steps every week.&lt;/p&gt;

&lt;p&gt;LocalSend was a good intermediate step for transferring photos to the tablet, but ADB made the process fully repeatable.&lt;/p&gt;

&lt;p&gt;Now the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Digital frame&lt;/code&gt; album acts as a signal, not storage:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add photos to the album&lt;/li&gt;
  &lt;li&gt;Run the pipeline&lt;/li&gt;
  &lt;li&gt;The batch is exported, transferred, and cleared&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-setup&quot;&gt;Final setup&lt;/h2&gt;

&lt;p&gt;The system now looks like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Apple Photos + iCloud for sync and curation&lt;/li&gt;
  &lt;li&gt;Filesystem directories as the archive&lt;/li&gt;
  &lt;li&gt;ADB for transferring images to the digital frame&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; for backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single vendor controls the entire workflow.&lt;/p&gt;

&lt;p&gt;That is exactly what I wanted.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/how-i-replaced-dropbox-camera-uploads-with-an-automated-photo-sync-pipeline/&quot;&gt;How I replaced Dropbox Camera Uploads with an automated photo sync pipeline&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on March 08, 2026.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Running two Zigbee networks side by side in Home Assistant]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/running-two-zigbee-networks-side-by-side-in-home-assistant/" />
  <id>https://jonathas.com/running-two-zigbee-networks-side-by-side-in-home-assistant</id>
  <published>2026-02-28T09:30:00+00:00</published>
  <updated>2026-02-28T09:30:00+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;For a long time, my Zigbee setup in Home Assistant was fully based on ZHA running on Home Assistant Yellow.&lt;/p&gt;

&lt;p&gt;It was stable, reliable, and already managing more than 40 devices. So when I needed better support for a few specific devices (especially a Tuya button), I did not want to migrate everything and risk breaking a working network.&lt;/p&gt;

&lt;p&gt;Instead, I built a hybrid architecture:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keep ZHA exactly as it is for the existing mesh&lt;/li&gt;
  &lt;li&gt;Add a second coordinator for Zigbee2MQTT&lt;/li&gt;
  &lt;li&gt;Pair only selected devices to the new Zigbee2MQTT network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turned out to be the safest way to extend compatibility without downtime.&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zigbee-hybrid/hybrid-architecture-overview.png&quot; alt=&quot;Hybrid Zigbee architecture overview&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-i-did-not-migrate-everything&quot;&gt;Why I did not migrate everything&lt;/h2&gt;

&lt;p&gt;A full migration from ZHA to Zigbee2MQTT would require re-pairing every device.&lt;/p&gt;

&lt;p&gt;In a production home setup, that means unnecessary risk:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Temporary automation outages&lt;/li&gt;
  &lt;li&gt;Broken device references&lt;/li&gt;
  &lt;li&gt;Time-consuming reconfiguration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My ZHA network was already healthy, so replacing it would have solved no real problem.&lt;/p&gt;

&lt;p&gt;What I actually needed was targeted compatibility for some devices where Zigbee2MQTT has better converters and cleaner action modeling.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-architecture&quot;&gt;Final architecture&lt;/h2&gt;

&lt;p&gt;I now run two independent Zigbee networks in parallel:&lt;/p&gt;

&lt;h3 id=&quot;network-1-zha&quot;&gt;Network 1: ZHA&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Coordinator: Home Assistant Yellow internal Zigbee radio&lt;/li&gt;
  &lt;li&gt;Zigbee channel: 25&lt;/li&gt;
  &lt;li&gt;Purpose: Existing core mesh and legacy devices&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zigbee-hybrid/zha-network.png&quot; alt=&quot;ZHA network&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;network-2-zigbee2mqtt&quot;&gt;Network 2: Zigbee2MQTT&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Coordinator: SONOFF Zigbee 3.0 USB Dongle Plus V2 (EFR32MG21)&lt;/li&gt;
  &lt;li&gt;Zigbee channel: 15&lt;/li&gt;
  &lt;li&gt;Adapter type: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ember&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Purpose: Devices with better support in Zigbee2MQTT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important: these networks are independent. A device belongs to one network only.&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zigbee-hybrid/zigbee2mqtt-network-map.png&quot; alt=&quot;Zigbee2MQTT network&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;hybrid-architecture-diagram&quot;&gt;Hybrid architecture diagram&lt;/h2&gt;

&lt;p&gt;This is the mental model I use. ZHA integrates directly with Home Assistant, while Zigbee2MQTT goes through MQTT (Mosquitto) and then shows up in Home Assistant via MQTT discovery.&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
  subgraph Zigbee_Network_1[Zigbee network 1: ZHA]
    ZHA_COORD[Coordinator: HA Yellow internal radio]
    ZHA_DEV[Zigbee devices paired to ZHA]
    ZHA_DEV --&amp;gt; ZHA_COORD
  end

  subgraph Zigbee_Network_2[Zigbee network 2: Zigbee2MQTT]
    Z2M_COORD[Coordinator: Sonoff USB Dongle Plus E]
    Z2M_DEV[Zigbee devices paired to Zigbee2MQTT]
    Z2M_DEV --&amp;gt; Z2M_COORD
  end

  ZHA_COORD --&amp;gt; HA[Home Assistant]

  Z2M_COORD --&amp;gt; Z2M[Zigbee2MQTT]
  Z2M --&amp;gt; MQTT[Mosquitto MQTT broker]
  MQTT --&amp;gt; HA
&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;Before pairing anything in Zigbee2MQTT, I installed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mosquitto Broker add-on&lt;/li&gt;
  &lt;li&gt;Zigbee2MQTT add-on&lt;/li&gt;
  &lt;li&gt;MQTT integration in Home Assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives Zigbee2MQTT a stable transport layer and allows Home Assistant to auto-discover exposed entities and triggers.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-zigbee2mqtt-integrates-with-home-assistant-via-mqtt-discovery&quot;&gt;How Zigbee2MQTT integrates with Home Assistant via MQTT discovery&lt;/h2&gt;

&lt;p&gt;Unlike ZHA, which integrates directly with Home Assistant’s device registry, Zigbee2MQTT communicates entirely through MQTT.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A Zigbee device talks to the Zigbee2MQTT coordinator (the USB dongle)&lt;/li&gt;
  &lt;li&gt;Zigbee2MQTT publishes messages to the MQTT broker (Mosquitto)&lt;/li&gt;
  &lt;li&gt;Home Assistant’s MQTT integration subscribes to discovery topics and creates devices/entities/triggers automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, that means Zigbee2MQTT devices only show up inside Home Assistant if:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mosquitto is running&lt;/li&gt;
  &lt;li&gt;The MQTT integration is enabled&lt;/li&gt;
  &lt;li&gt;Zigbee2MQTT can reach the broker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that last step (MQTT discovery), Zigbee2MQTT can still pair devices and show them in its own UI, but Home Assistant will not see them.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;zigbee2mqtt-baseline-config&quot;&gt;Zigbee2MQTT baseline config&lt;/h2&gt;

&lt;p&gt;This is the core configuration I used:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;mqtt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mqtt://core-mosquitto:1883&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;serial&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/dev/ttyUSB0&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ember&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;advanced&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;15&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pan_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GENERATE&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;network_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GENERATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key point is coordinator isolation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Internal radio stays dedicated to ZHA&lt;/li&gt;
  &lt;li&gt;USB dongle stays dedicated to Zigbee2MQTT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not try to share the same coordinator between both integrations.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;adapter-type-and-firmware-why-ember-matters&quot;&gt;Adapter type and firmware: why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ember&lt;/code&gt; matters&lt;/h2&gt;

&lt;p&gt;My Sonoff Zigbee dongle uses a Silicon Labs EFR32MG21 chip, so Zigbee2MQTT must use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ember&lt;/code&gt; adapter type.&lt;/p&gt;

&lt;p&gt;Zigbee2MQTT supports multiple adapters depending on the coordinator chipset:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ember&lt;/code&gt;: Silicon Labs EFR32 (my setup)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zstack&lt;/code&gt;: Texas Instruments CC2652 based coordinators&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deconz&lt;/code&gt;: ConBee coordinators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you pick the wrong adapter type, Zigbee2MQTT typically fails to start, cannot open the serial connection correctly, or behaves unreliably.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;channel-planning-and-rf-stability&quot;&gt;Channel planning and RF stability&lt;/h2&gt;

&lt;p&gt;To reduce interference, I separated channels deliberately:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ZHA on channel 25&lt;/li&gt;
  &lt;li&gt;Zigbee2MQTT on channel 15&lt;/li&gt;
  &lt;li&gt;Wi-Fi on channel 1 or 6 where possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also used a USB extension cable for the Zigbee2MQTT dongle and kept it away from metal surfaces and other noisy electronics.&lt;/p&gt;

&lt;p&gt;That small physical change improves link quality more than many software tweaks.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pairing-workflow-for-the-second-network&quot;&gt;Pairing workflow for the second network&lt;/h2&gt;

&lt;p&gt;The pairing flow is simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Enable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Permit Join&lt;/code&gt; in Zigbee2MQTT.&lt;/li&gt;
  &lt;li&gt;Put the target device in pairing mode.&lt;/li&gt;
  &lt;li&gt;Wait for Zigbee2MQTT interview completion.&lt;/li&gt;
  &lt;li&gt;Confirm the device appears in Home Assistant via MQTT discovery.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I only pair new or problematic devices to Zigbee2MQTT.&lt;/p&gt;

&lt;p&gt;Everything already stable in ZHA stays untouched.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;real-world-example-tuya-ts0041-button&quot;&gt;Real-world example: Tuya TS0041 button&lt;/h2&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zigbee-hybrid/button.jpg&quot; alt=&quot;Tuya TS0041 button&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;p&gt;This button was the main reason I introduced Zigbee2MQTT.&lt;/p&gt;

&lt;p&gt;In my setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ZHA exposed it as a switch instead of a button, with an On-Off toggle instead of action events.&lt;/li&gt;
  &lt;li&gt;Zigbee2MQTT exposed clear action events like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;single&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;double&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hold&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made automations significantly cleaner and easier to maintain.&lt;/p&gt;

&lt;p&gt;Instead of parsing raw event payloads, I can build automations directly with device triggers in the Home Assistant UI.&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/zigbee-hybrid/button_automation.png&quot; alt=&quot;Tuya TS0041 actions in Zigbee2MQTT and Home Assistant triggers&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-this-hybrid-model-gives-me&quot;&gt;What this hybrid model gives me&lt;/h2&gt;

&lt;p&gt;Running both integrations side by side delivered exactly what I wanted:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No migration downtime&lt;/li&gt;
  &lt;li&gt;No mass re-pairing effort&lt;/li&gt;
  &lt;li&gt;Better support for selected edge-case devices&lt;/li&gt;
  &lt;li&gt;Incremental rollout with low risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, ZHA remains the stable baseline while Zigbee2MQTT acts as a compatibility layer for specific devices.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h2&gt;

&lt;p&gt;If you already have a stable ZHA mesh, a full migration is often unnecessary.&lt;/p&gt;

&lt;p&gt;A second Zigbee network with Zigbee2MQTT can be a cleaner strategy when you need better support for specific devices.&lt;/p&gt;

&lt;p&gt;The two rules that mattered most in my setup were:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Keep coordinators strictly isolated.&lt;/li&gt;
  &lt;li&gt;Plan channels and physical dongle placement carefully.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those in place, both networks can coexist reliably in Home Assistant.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/running-two-zigbee-networks-side-by-side-in-home-assistant/&quot;&gt;Running two Zigbee networks side by side in Home Assistant&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on February 28, 2026.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Automating winter humidity control with Home Assistant and dehumidifiers]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/automating-winter-humidity-control-with-home-assistant-and-dehumidifiers/" />
  <id>https://jonathas.com/automating-winter-humidity-control-with-home-assistant-and-dehumidifiers</id>
  <published>2026-02-21T14:30:00+00:00</published>
  <updated>2026-02-21T14:30:00+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;Every winter, the same problem returned in my flat:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Wet windows in the morning&lt;/li&gt;
  &lt;li&gt;Condensation around glass doors&lt;/li&gt;
  &lt;li&gt;Bedrooms sitting at 60-70% relative humidity overnight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One morning I noticed water pooling on the floor near the glass door. That was the moment I stopped treating this as a minor annoyance.&lt;/p&gt;

&lt;p&gt;Outside humidity here during winter is often 70–90%, while temperatures frequently sit around 0 °C and regularly drop below freezing at night. Even with heating and regular ventilation, indoor humidity stayed too high for comfort.&lt;/p&gt;

&lt;p&gt;The result was obvious: moisture everywhere.&lt;/p&gt;

&lt;p&gt;I wanted a fully automated and resilient setup in Home Assistant that could keep humidity under control without constantly toggling devices manually.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-humidity-control-gets-tricky-in-winter&quot;&gt;Why humidity control gets tricky in winter&lt;/h2&gt;

&lt;p&gt;A common mistake is aiming for one perfect number (for example 45% RH) and forcing the system to stay there all the time.&lt;/p&gt;

&lt;p&gt;In practice, that can overwork dehumidifiers, increase noise, and still produce unstable cycling.&lt;/p&gt;

&lt;p&gt;What worked better for me was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Room-level control instead of one global target&lt;/li&gt;
  &lt;li&gt;Hysteresis (different ON and OFF thresholds)&lt;/li&gt;
  &lt;li&gt;Stability timers to avoid oscillation&lt;/li&gt;
  &lt;li&gt;Independent automations per room&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made the system quieter, more efficient, and much more reliable.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-hardware-setup&quot;&gt;Final hardware setup&lt;/h2&gt;

&lt;p&gt;I ended up with distributed moisture control:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1x Comfee dehumidifier in the bathroom&lt;/li&gt;
  &lt;li&gt;2x Xiaomi Smart Dehumidifier Lite (main bedroom + children’s bedroom)&lt;/li&gt;
  &lt;li&gt;Wall-mounted Zigbee humidity sensors in each room&lt;/li&gt;
  &lt;li&gt;Home Assistant Yellow with stable Zigbee routing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To connect everything cleanly in Home Assistant, I had to install two integrations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Xiaomi Miot&lt;/code&gt; from HACS (for Xiaomi dehumidifiers)&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dehumidifiers/xiaomi-miot-hacs.png&quot; alt=&quot;Xiaomi Miot integration in HACS&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Midea Air Appliances&lt;/code&gt; (for the Comfee dehumidifier)&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dehumidifiers/midea-air-appliances.png&quot; alt=&quot;Midea Air Appliances integration setup in Home Assistant&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;p&gt;The key design decision:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Wall sensors are the source of truth, not dehumidifier internal sensors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internal sensors measure intake air and are heavily affected by device placement. Wall sensors reflect what people actually feel in the room.&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dehumidifiers/humidity-trends.png&quot; alt=&quot;Room humidity trends in Home Assistant&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;main-bedroom-automation-logic&quot;&gt;Main bedroom automation logic&lt;/h2&gt;

&lt;p&gt;The main bedroom follows a conservative hysteresis model:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Turn ON above 50% RH (after 2 minutes of stability)&lt;/li&gt;
  &lt;li&gt;Turn OFF below 48% RH (after 10 minutes of stability)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also use periodic checks and Home Assistant startup triggers so the automation self-heals after restarts.&lt;/p&gt;

&lt;h3 id=&quot;on-automation&quot;&gt;ON automation&lt;/h3&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main Bedroom - Turn dehumidifier ON&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;numeric_state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensor.main_bedroom_temperature_and_humidity_humidity&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;above&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;00:02:00&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time_pattern&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/5&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;homeassistant&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;start&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.xiaomi_lite_814e_dehumidifier&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;numeric_state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensor.main_bedroom_temperature_and_humidity_humidity&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;above&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.set_humidity&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.xiaomi_lite_814e_dehumidifier&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;humidity&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;45&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.turn_on&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.xiaomi_lite_814e_dehumidifier&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;off-automation&quot;&gt;OFF automation&lt;/h3&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main Bedroom - Turn dehumidifier OFF&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;numeric_state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensor.main_bedroom_temperature_and_humidity_humidity&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;below&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;48&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;00:10:00&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time_pattern&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/5&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;homeassistant&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;start&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.xiaomi_lite_814e_dehumidifier&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;numeric_state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensor.main_bedroom_temperature_and_humidity_humidity&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;below&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;48&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.turn_off&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;humidifier.xiaomi_lite_814e_dehumidifier&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That 2% gap plus different hold times was enough to eliminate constant ON/OFF flapping.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;tank-full--fault-detection-for-xiaomi-units&quot;&gt;Tank-full / fault detection for Xiaomi units&lt;/h2&gt;

&lt;p&gt;The Xiaomi integration exposes fault codes through:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sensor.xiaomi_lite_814e_device_fault&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Attribute: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dehumidifier.fault&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; means normal state, and values above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; usually indicate a fault (most commonly a full tank).&lt;/p&gt;

&lt;p&gt;Instead of silently failing, I trigger push notifications when faults persist for a few seconds.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main Bedroom Dehumidifier Tank Full&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;numeric_state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sensor.xiaomi_lite_814e_device_fault&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;attribute&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dehumidifier.fault&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;above&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.mobile_app_jon_iphone&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main Bedroom Dehumidifier Tank Full&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;The main bedroom dehumidifier reported a fault (code: {{ state_attr(&apos;sensor.xiaomi_lite_814e_device_fault&apos;, &apos;dehumidifier.fault&apos;) }}).&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;This is usually a full tank. Please check and empty it.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This simple alert removed one of the most annoying failure modes: the dehumidifier is technically “on”, but nothing is being dehumidified.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;childrens-bedroom-and-bathroom-strategy&quot;&gt;Children’s bedroom and bathroom strategy&lt;/h2&gt;

&lt;p&gt;I do not use exactly the same thresholds everywhere.&lt;/p&gt;

&lt;h3 id=&quot;childrens-bedroom&quot;&gt;Children’s bedroom&lt;/h3&gt;

&lt;p&gt;I keep slightly softer thresholds for comfort:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ON around 52-55%&lt;/li&gt;
  &lt;li&gt;OFF around 48-50%&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;bathroom-comfee&quot;&gt;Bathroom (Comfee)&lt;/h3&gt;

&lt;p&gt;The bathroom uses more aggressive source control:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ON around 58-60%&lt;/li&gt;
  &lt;li&gt;OFF around 52%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This room creates short humidity spikes (showers), so local response works much better than trying to dry the entire flat globally.&lt;/p&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dehumidifiers/entities-overview.png&quot; alt=&quot;Dehumidifier entities and room sensors in Home Assistant&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-45-experiment-that-failed&quot;&gt;The 45% experiment that failed&lt;/h2&gt;

&lt;p&gt;I initially pushed all rooms toward 45% RH because it sounded like the “ideal” number.&lt;/p&gt;

&lt;p&gt;In my real setup, it was not ideal:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Devices ran too often&lt;/li&gt;
  &lt;li&gt;Noise increased&lt;/li&gt;
  &lt;li&gt;Comfort did not improve proportionally&lt;/li&gt;
  &lt;li&gt;Practical maintenance (tank emptying) got worse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After tuning, the 48-52% band became the sweet spot:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Condensation mostly gone&lt;/li&gt;
  &lt;li&gt;Better cycling behavior&lt;/li&gt;
  &lt;li&gt;Lower operational stress on devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the biggest lesson: optimize for a stable system, not a theoretically perfect number.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;reliability-patterns-that-mattered-most&quot;&gt;Reliability patterns that mattered most&lt;/h2&gt;

&lt;p&gt;A few implementation details made a huge difference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Multi-trigger automations (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numeric_state&lt;/code&gt; + periodic checks + HA startup)&lt;/li&gt;
  &lt;li&gt;Explicit hysteresis thresholds per room&lt;/li&gt;
  &lt;li&gt;Longer OFF stability windows than ON windows&lt;/li&gt;
  &lt;li&gt;Fault-code-based alerts instead of brittle string checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns made the setup resilient to restarts, temporary sensor noise, and common real-world device issues.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-result&quot;&gt;Final result&lt;/h2&gt;

&lt;p&gt;By the end of winter, humidity control became mostly invisible:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Windows stayed dry most mornings&lt;/li&gt;
  &lt;li&gt;Bedrooms remained in a controlled range overnight&lt;/li&gt;
  &lt;li&gt;Manual intervention dropped to occasional tank emptying&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because everything is room-level and event-driven, I can keep iterating each zone independently without breaking the whole system.&lt;/p&gt;

&lt;p&gt;If you are battling winter condensation, start with three things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use external room sensors as your source of truth.&lt;/li&gt;
  &lt;li&gt;Implement hysteresis with stability timers.&lt;/li&gt;
  &lt;li&gt;Tune each room based on real behavior, not one universal target.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That combination made all the difference in my flat.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/automating-winter-humidity-control-with-home-assistant-and-dehumidifiers/&quot;&gt;Automating winter humidity control with Home Assistant and dehumidifiers&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on February 21, 2026.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[How I made my physical mailbox send me real-time push notifications]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/how-i-made-my-physical-mailbox-send-me-real-time-push-notifications/" />
  <id>https://jonathas.com/how-i-made-my-physical-mailbox-send-me-real-time-push-notifications</id>
  <published>2026-02-15T12:06:30+00:00</published>
  <updated>2026-02-15T12:06:30+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;I live in Prague, and like many apartment buildings here, the mailboxes are grouped in the building entrance instead of inside the flat.
Letters arrive through a slot and fall directly inside the box.&lt;/p&gt;

&lt;p&gt;There is something deeply satisfying about turning physical events into software events.
Not just monitoring systems, not dashboards, not metrics.
Real-world events, like receiving a letter.&lt;/p&gt;

&lt;p&gt;This project started with a simple goal:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Receive a push notification the exact moment a letter arrives in my mailbox.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not when I open it, not when I check it, but when it physically arrives.&lt;/p&gt;

&lt;p&gt;This required bridging the physical and digital worlds using Zigbee, MQTT, and Home Assistant.&lt;/p&gt;

&lt;p&gt;What sounded like a tiny automation turned into a deeper project involving Zigbee range problems, metal interference, firmware flashing, and creative power solutions.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-physical-constraints&quot;&gt;The physical constraints&lt;/h2&gt;

&lt;p&gt;My mailbox is located outside my apartment, in the building entrance hallway.&lt;/p&gt;

&lt;p&gt;This introduces multiple problems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is physically separated from my apartment&lt;/li&gt;
  &lt;li&gt;There are several walls between the mailbox and my Zigbee coordinator&lt;/li&gt;
  &lt;li&gt;There are no power outlets near the mailbox&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately ruled out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Wi‑Fi devices&lt;/li&gt;
  &lt;li&gt;Mains‑powered Zigbee routers&lt;/li&gt;
  &lt;li&gt;Any solution requiring permanent power&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This forced a battery‑powered Zigbee routing architecture.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;highlevel-architecture&quot;&gt;High‑level architecture&lt;/h2&gt;

&lt;p&gt;To understand how everything connects, here is the full architecture:&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart TB

Mailbox[&quot;📬 Mailbox&quot;]
Sensor[&quot;👁️ Aqara P1 Motion Sensor&quot;]
Router[&quot;🔋 Sonoff Zigbee Router Dongle&lt;br /&gt;+ Powerbank&quot;]

subgraph HAHost[&quot;🖥️ Home Assistant Host&quot;]
    Z2M[&quot;Zigbee2MQTT&quot;]
    HA[&quot;Home Assistant&quot;]
    Z2M --&amp;gt; HA
end

Phone[&quot;📱 iPhone Notification&quot;]

Mailbox --&amp;gt; Sensor
Sensor --&amp;gt; Router
Router --&amp;gt; Z2M
HA --&amp;gt; Phone

&lt;/div&gt;

&lt;p&gt;This converts a physical event into a software event:&lt;/p&gt;

&lt;p&gt;Physical motion → Zigbee message → MQTT event → Automation → Push notification&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-tried-first&quot;&gt;What I tried first&lt;/h2&gt;

&lt;h3 id=&quot;direct-pairing&quot;&gt;Direct pairing&lt;/h3&gt;

&lt;p&gt;My first attempt was simply pairing the motion sensor and placing it inside the mailbox.
It worked occasionally, but the signal was unreliable.
Metal enclosures and distance are brutal for 2.4 GHz signals.&lt;/p&gt;

&lt;h3 id=&quot;normal-zigbee-repeater&quot;&gt;Normal Zigbee repeater&lt;/h3&gt;

&lt;p&gt;The standard fix would be placing a mains-powered Zigbee repeater near the entrance.
That would likely solve coverage, but there are no power sockets near the door.&lt;/p&gt;

&lt;h3 id=&quot;alternative-technologies&quot;&gt;Alternative technologies&lt;/h3&gt;

&lt;p&gt;I also considered 433 MHz sensors and a bridge.
Technically viable, but it would add a second wireless stack only for this use case.
I wanted to keep everything in the existing Zigbee ecosystem.&lt;/p&gt;

&lt;h3 id=&quot;the-key-idea&quot;&gt;The key idea&lt;/h3&gt;

&lt;p&gt;A Zigbee USB dongle can run in router mode and be powered entirely from a battery.
That unlocked the final design.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;hardware-components&quot;&gt;Hardware components&lt;/h2&gt;

&lt;h3 id=&quot;motion-sensor&quot;&gt;Motion sensor&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/mailbox-smart-home/mailbox.jpg&quot; alt=&quot;Mailbox at the building entrance&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;Aqara Motion Sensor P1 was selected because of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extremely low power consumption&lt;/li&gt;
  &lt;li&gt;Reliable Zigbee performance&lt;/li&gt;
  &lt;li&gt;Long battery life&lt;/li&gt;
  &lt;li&gt;Excellent Zigbee2MQTT compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It detects motion when letters fall inside the mailbox.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;zigbee-router&quot;&gt;Zigbee router&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/mailbox-smart-home/zigbee-dongle-and-powerbank.jpg&quot; alt=&quot;Zigbee dongle powered by a power bank&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;I used a Sonoff Zigbee Dongle‑E flashed with router firmware.&lt;/p&gt;

&lt;p&gt;Normally, this device acts as a coordinator, but when flashed with router firmware, it becomes a dedicated Zigbee router.&lt;/p&gt;

&lt;p&gt;This extends the mesh network range.
I powered the router with a power bank because there are no power outlets near my apartment’s entrance.&lt;/p&gt;

&lt;h3 id=&quot;full-hardware-list&quot;&gt;Full hardware list&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Aqara Motion Sensor P1&lt;/li&gt;
  &lt;li&gt;Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-E)&lt;/li&gt;
  &lt;li&gt;Xiaomi 20,000 mAh power bank&lt;/li&gt;
  &lt;li&gt;USB keep-alive module (adjustable dummy load)&lt;/li&gt;
  &lt;li&gt;Existing Zigbee2MQTT setup&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;critical-problem-power-bank-shutdown&quot;&gt;Critical problem: Power bank shutdown&lt;/h2&gt;

&lt;p&gt;Initially, everything worked perfectly, then it stopped working and the router disappeared randomly.&lt;/p&gt;

&lt;p&gt;Cause: power bank auto shutdown.&lt;/p&gt;

&lt;p&gt;Power banks automatically shut down when the current draw is too low. Because the Zigbee router consumes very little power, the power bank assumed nothing was connected and turned itself off.&lt;/p&gt;

&lt;p&gt;This silently killed the Zigbee network extension and broke mailbox notifications.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-fix-usb-keepalive-module&quot;&gt;Final fix: USB keep‑alive module&lt;/h2&gt;

&lt;p&gt;The solution was adding a configurable USB dummy load.
This device draws a small constant current, tricking the power bank into believing an active device is connected, preventing automatic shutdown.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href=&quot;https://www.amazon.de/-/en/dp/B0GFMN3DKL&quot;&gt;exact model I bought on Amazon&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/mailbox-smart-home/usb-keep-alive-module.jpg&quot; alt=&quot;USB keep-alive module&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;architecture-update&quot;&gt;Architecture update&lt;/h3&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR

PowerBank --&amp;gt; Router
PowerBank --&amp;gt; DummyLoad[&quot;USB Keep‑Alive Module&quot;]

DummyLoad --&amp;gt; Load[&quot;Constant current draw&quot;]
Router --&amp;gt; Zigbee[&quot;Zigbee Mesh Network&quot;]

&lt;/div&gt;

&lt;p&gt;This keeps the router powered continuously and stabilizes the Zigbee mesh.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-by-step-implementation&quot;&gt;Step-by-step implementation&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Flash the Sonoff dongle with router firmware.
I’ve done it via the &lt;a href=&quot;https://dongle.sonoff.tech/sonoff-dongle-flasher/&quot;&gt;Sonoff website&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Pair it into Zigbee2MQTT and name it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zigbee_router_hallway&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Power it from the power bank and place it high in the hallway.&lt;/li&gt;
  &lt;li&gt;Pair the Aqara motion sensor and mount it inside the mailbox.&lt;/li&gt;
  &lt;li&gt;Verify routing in the Zigbee2MQTT network map.&lt;/li&gt;
  &lt;li&gt;Configure occupancy timeout to 90 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/mailbox-smart-home/zigbee-network-map.png&quot; alt=&quot;Zigbee2MQTT network map&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;software-architecture&quot;&gt;Software architecture&lt;/h2&gt;

&lt;p&gt;The software flow:&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
sequenceDiagram

participant Sensor
participant Router
participant Coordinator
participant Zigbee2MQTT
participant HomeAssistant
participant Phone

Sensor-&amp;gt;&amp;gt;Router: Motion detected
Router-&amp;gt;&amp;gt;Coordinator: Forward Zigbee message
Coordinator-&amp;gt;&amp;gt;Zigbee2MQTT: Publish MQTT message
Zigbee2MQTT-&amp;gt;&amp;gt;HomeAssistant: Forward event
HomeAssistant-&amp;gt;&amp;gt;Phone: Push notification

&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;motion-automation&quot;&gt;Motion automation&lt;/h2&gt;

&lt;p&gt;Home Assistant automation:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Mailbox - Notify on motion&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;binary_sensor.aqara_p1_mailbox_occupancy&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.mobile_app_jon_iphone&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;📬&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Mailbox&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;You&apos;ve&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;got&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;letter!&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;router-reliability-monitoring&quot;&gt;Router reliability monitoring&lt;/h2&gt;

&lt;p&gt;Because the hallway router runs on a power bank, monitoring is as important as the mailbox notification.
If the battery dies, the automation silently stops working.&lt;/p&gt;

&lt;h3 id=&quot;why-availability-sensors-were-not-enough&quot;&gt;Why availability sensors were not enough&lt;/h3&gt;

&lt;p&gt;Zigbee2MQTT availability works for many devices, but a battery-powered router can stop sending updates without reliably publishing an offline transition.
For this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;last_seen&lt;/code&gt; is more reliable than plain availability state.&lt;/p&gt;

&lt;h3 id=&quot;stale-sensor-in-configurationyaml&quot;&gt;Stale sensor in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration.yaml&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;I used a stale detector based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;last_seen&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;binary_sensor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hallway&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Stale&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;unique_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;zigbee_router_hallway_stale&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;device_class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;connectivity&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;&lt;/span&gt;
          
          &lt;span class=&quot;s&quot;&gt;{% set s = states(&apos;sensor.zigbee_router_hallway_last_seen&apos;) %}&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;{% if s in [&apos;unknown&apos;, &apos;unavailable&apos;, &apos;none&apos;, &apos;&apos;] %}&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;{% else %}&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;{{ (now() - states.sensor.zigbee_router_hallway_last_seen.last_updated).total_seconds() &amp;gt; 900 }}&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;{% endif %}&lt;/span&gt;
          
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This sensor turns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on&lt;/code&gt; when the router has not reported anything for more than 15 minutes.&lt;/p&gt;

&lt;h3 id=&quot;offline-notification-automation&quot;&gt;Offline notification automation&lt;/h3&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Notify Zigbee Router Hallway Offline (Last seen watchdog)&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;binary_sensor.zigbee_router_hallway_stale&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;00:02:00&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.persistent_notification&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;⚠&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Offline&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;heartbeat&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;~10+&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;minutes.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Power&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bank&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;likely&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;empty.&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.mobile_app_jon_iphone&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;⚠&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Offline&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;heartbeat&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;~10+&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;minutes.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Power&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bank&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;likely&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;empty.&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;input_boolean.turn_on&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;input_boolean.zigbee_router_hallway_was_offline&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;back-online-automation&quot;&gt;Back online automation&lt;/h3&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Notify Zigbee Router Hallway Back Online (Last seen watchdog)&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;binary_sensor.zigbee_router_hallway_stale&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.persistent_notification&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;✅&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Online&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hallway&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;reporting&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;again.&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;notify.mobile_app_jon_iphone&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;✅&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Online&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hallway&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Zigbee&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;reporting&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;again.&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;input_boolean.turn_off&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;input_boolean.zigbee_router_hallway_was_offline&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This approach proved more reliable than relying on MQTT availability alone.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;power-consumption-considerations&quot;&gt;Power consumption considerations&lt;/h2&gt;

&lt;p&gt;Router consumption: ~0.5W&lt;br /&gt;
Dummy load consumption: configurable&lt;/p&gt;

&lt;p&gt;Power bank runtime depends on configured load, which creates a trade‑off:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Lower load → longer runtime&lt;/li&gt;
  &lt;li&gt;Higher load → more reliability&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-result&quot;&gt;Final result&lt;/h2&gt;

&lt;p&gt;Now the system works reliably.&lt;/p&gt;

&lt;p&gt;When a letter arrives:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Phone receives notification instantly.&lt;/li&gt;
  &lt;li&gt;No manual checking required.&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/mailbox-smart-home/mailbox-notification.png&quot; alt=&quot;Mailbox notification on phone&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;height: 16px;&quot;&gt;&lt;/div&gt;

&lt;p&gt;This small project turned an ordinary mailbox into an event-driven system, proving that software architecture does not have to stop at the boundaries of a server.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-matters&quot;&gt;Why this matters&lt;/h2&gt;

&lt;p&gt;Software architecture is fundamentally about converting events into actions.&lt;/p&gt;

&lt;p&gt;Most engineers apply this thinking inside software systems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;HTTP requests&lt;/li&gt;
  &lt;li&gt;Database changes&lt;/li&gt;
  &lt;li&gt;Queue messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the same principles apply to the physical world.&lt;/p&gt;

&lt;p&gt;A mailbox opening becomes an event.
A motion sensor becomes an event producer.
Zigbee becomes a transport layer.
MQTT becomes an event bus.
Home Assistant becomes an event processor.&lt;/p&gt;

&lt;p&gt;This is event-driven architecture applied to reality itself.&lt;/p&gt;

&lt;p&gt;Once you start thinking this way, the boundary between software and physical systems begins to disappear.&lt;/p&gt;

&lt;p&gt;Everything becomes part of your architecture.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Zigbee routers are flexible when used intentionally.&lt;/li&gt;
  &lt;li&gt;Power banks can replace mains power in constrained locations.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;last_seen&lt;/code&gt;-based monitoring is more reliable than availability flags in this case.&lt;/li&gt;
  &lt;li&gt;Conservative timeouts reduce false alerts.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;future-improvements&quot;&gt;Future improvements&lt;/h2&gt;

&lt;p&gt;I’m planning to make Home Assistant play an audio announcement on Siri/HomePod when new mail arrives. It would be awesome to have that in Matt Berry’s voice!&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/how-i-made-my-physical-mailbox-send-me-real-time-push-notifications/&quot;&gt;How I made my physical mailbox send me real-time push notifications&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on February 15, 2026.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Optimizing File Upload Architecture with AWS S3 Presigned POST]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/optimizing-file-upload-with-aws-s3-presigned-post/" />
  <id>https://jonathas.com/optimizing-file-upload-with-aws-s3-presigned-post</id>
  <published>2025-07-12T14:00:10+00:00</published>
  <updated>2025-07-12T14:00:10+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;In many systems, file uploads follow a familiar pattern:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A client sends the file to your application server.&lt;/li&gt;
  &lt;li&gt;The backend processes it (e.g., for validation or virus scanning).&lt;/li&gt;
  &lt;li&gt;Then it forwards the file to a cloud storage service like Amazon S3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this works, it’s highly inefficient. Your app becomes a middleman consuming bandwidth, CPU, and memory, just to pass files along.&lt;/p&gt;

&lt;h2 id=&quot;why-this-is-a-problem&quot;&gt;Why This Is a Problem&lt;/h2&gt;

&lt;p&gt;This upload pattern:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Consumes resources&lt;/strong&gt; on your app server.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Increases latency&lt;/strong&gt; for users.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stresses your infrastructure&lt;/strong&gt; (especially under load).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Breaks down for large files&lt;/strong&gt;, especially when you hit limits in intermediaries like API Gateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look at two common architectures where these problems show up.&lt;/p&gt;

&lt;h2 id=&quot;two-upload-architectures-compared&quot;&gt;Two Upload Architectures Compared&lt;/h2&gt;

&lt;h3 id=&quot;-architecture-1-upload-with-nginx&quot;&gt;🔸 Architecture 1: Upload with Nginx&lt;/h3&gt;

&lt;p&gt;This is typical in smaller-scale deployments with a single server behind a reverse proxy.&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
  Client[&quot;Client (Browser/App)&quot;]
  Nginx[&quot;Nginx (Reverse Proxy)&quot;]
  App[&quot;App Server&quot;]
  S3[&quot;S3 Bucket&quot;]

  Client --&amp;gt; Nginx
  Nginx --&amp;gt; App
  App --&amp;gt; S3
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nginx must &lt;strong&gt;buffer&lt;/strong&gt; the file in memory or disk (an exception can be thrown here).&lt;/li&gt;
  &lt;li&gt;App still receives and processes the full payload.&lt;/li&gt;
  &lt;li&gt;Memory and CPU are wasted just to forward the upload.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-architecture-2-upload-via-api-gateway--load-balancer&quot;&gt;🔸 Architecture 2: Upload via API Gateway + Load Balancer&lt;/h3&gt;

&lt;p&gt;This setup is common in cloud-native apps using AWS infrastructure.&lt;/p&gt;

&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
  Client[&quot;Client (Browser/App)&quot;]
  APIGW[&quot;AWS API Gateway&quot;]
  ALB[&quot;Application Load Balancer&quot;]
  App[&quot;App Server&quot;]
  S3[&quot;S3 Bucket&quot;]

  Client --&amp;gt; APIGW
  APIGW --&amp;gt; ALB
  ALB --&amp;gt; App
  App --&amp;gt; S3
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;API Gateway limits uploads to 10 MB&lt;/strong&gt; max per request.&lt;/li&gt;
  &lt;li&gt;Both ALB and your app must handle the file payload.&lt;/li&gt;
  &lt;li&gt;This creates bottlenecks and risks timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;-the-solution-presigned-post-to-s3&quot;&gt;✅ The Solution: Presigned POST to S3&lt;/h2&gt;

&lt;p&gt;Instead of routing uploads through your backend, you can &lt;strong&gt;let clients upload directly to S3&lt;/strong&gt;, securely, using a &lt;strong&gt;presigned POST policy&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;The client asks your backend for a signed upload policy.&lt;/li&gt;
  &lt;li&gt;Your backend returns a temporary URL and form fields.&lt;/li&gt;
  &lt;li&gt;The client uploads the file directly to S3 using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart/form-data&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;mermaid&quot;&gt;
sequenceDiagram
  autonumber
  participant Client as Client (Browser/App)
  participant API as App Server
  participant S3 as S3 Bucket

  Client-&amp;gt;&amp;gt;API: GET /attachments/presigned-post-policy
  API--&amp;gt;&amp;gt;Client: 200 OK (URL + fields)
  Client-&amp;gt;&amp;gt;S3: POST file upload (multipart/form-data)
  S3--&amp;gt;&amp;gt;Client: 204 No Content
&lt;/div&gt;

&lt;h2 id=&quot;benefits-of-presigned-uploads&quot;&gt;Benefits of Presigned Uploads&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;🚀 &lt;strong&gt;Bypass Infrastructure&lt;/strong&gt;: No more file handling by Nginx, ALB, or API Gateway.&lt;/li&gt;
  &lt;li&gt;📉 &lt;strong&gt;Reduce Server Load&lt;/strong&gt;: No memory or CPU usage from streaming file data.&lt;/li&gt;
  &lt;li&gt;⏱️ &lt;strong&gt;Improve Latency&lt;/strong&gt;: Fewer hops and no buffering.&lt;/li&gt;
  &lt;li&gt;💡 &lt;strong&gt;Avoid Limits&lt;/strong&gt;: Works around API Gateway’s 10MB payload cap.&lt;/li&gt;
  &lt;li&gt;🔐 &lt;strong&gt;Secure&lt;/strong&gt;: You control what, where, and for how long uploads are allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;example-server-side-implementation&quot;&gt;Example Server-Side implementation&lt;/h2&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Injectable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@nestjs/common&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;S3Client&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createPresignedPost&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@aws-sdk/s3-presigned-post&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Injectable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;S3Service&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getPresignedPostPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;bucketConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;S3BucketResourceConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expiresInSeconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Default 10 minutes&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;maxSizeBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;150&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Default 150 MB&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;s3ClientConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;S3ClientConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bucketConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createPresignedPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;S3Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s3ClientConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bucketConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Conditions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;content-length-range&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;maxSizeBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;starts-with&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;$Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;contentType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;...(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contentType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;contentType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;Expires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expiresInSeconds&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

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

&lt;h2 id=&quot;example-client-implementation&quot;&gt;Example Client Implementation&lt;/h2&gt;

&lt;p&gt;Here’s how a client might perform the upload:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axios&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;axios&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uploadFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Step 1: Get the presigned policy&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/api/get-presigned-policy&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;contentType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Step 2: Prepare the upload form&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FormData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// must be last&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Step 3: Upload to S3&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uploadRes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;multipart/form-data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;uploadRes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;204&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Upload successful&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Upload failed&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;when-should-you-use-presigned-post&quot;&gt;When Should You Use Presigned POST?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;When handling &lt;strong&gt;large file uploads&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;When deploying behind &lt;strong&gt;API Gateway&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;When you want to &lt;strong&gt;scale uploads&lt;/strong&gt; without scaling your backend.&lt;/li&gt;
  &lt;li&gt;When you want &lt;strong&gt;better performance&lt;/strong&gt; and &lt;strong&gt;lower infrastructure costs&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;If you’re still uploading files through your backend, you’re likely wasting resources, risking timeouts, and introducing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Presigned POST policies in AWS S3 offer a better approach:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Simple to implement.&lt;/li&gt;
  &lt;li&gt;Secure and time-limited.&lt;/li&gt;
  &lt;li&gt;Scalable and resilient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Offload uploads to your storage layer, which is where they belong, and keep your app fast, simple, and maintainable.&lt;/p&gt;

&lt;h2 id=&quot;more-info-about-the-aws-feature&quot;&gt;More info about the AWS feature&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html&quot;&gt;Browser-Based Uploads Using POST (AWS Signature Version 4&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html&quot;&gt;POST Policy - Amazon Simple Storage Service&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html&quot;&gt;Example: Browser-Based Upload using HTTP POST (Using AWS Signature Version 4)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/optimizing-file-upload-with-aws-s3-presigned-post/&quot;&gt;Optimizing File Upload Architecture with AWS S3 Presigned POST&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on July 12, 2025.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Creating a CV in LaTeX: Clean, Structured, and Versioned]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/creating-a-cv-in-latex/" />
  <id>https://jonathas.com/creating-a-cv-in-latex</id>
  <published>2025-07-05T10:02:00+00:00</published>
  <updated>2025-07-05T10:02:00+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;For years, I maintained my CV using various online builders. They got the job done, but there was always a subscription fee. This last time I tried to update my CV through one of them, their payment process got stuck. They withdrew the subscription fee from my card but didn’t enable any premium features. That was the last nail in the coffin for me.&lt;/p&gt;

&lt;p&gt;This year, I decided to rebuild my CV from scratch using &lt;strong&gt;LaTeX&lt;/strong&gt;. The result is a clean, professional-looking CV that’s easy to maintain and version-controlled with Git.&lt;/p&gt;

&lt;h2 id=&quot;why-latex&quot;&gt;Why LaTeX?&lt;/h2&gt;

&lt;p&gt;LaTeX is a typesetting system widely used in academia and technical fields. It lets you separate content from presentation, gives you full control over layout and styling, and produces high-quality PDFs.&lt;/p&gt;

&lt;p&gt;For me, LaTeX offered a few major benefits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Precise control over formatting&lt;/li&gt;
  &lt;li&gt;Clean separation of content and style&lt;/li&gt;
  &lt;li&gt;Easy versioning using Git&lt;/li&gt;
  &lt;li&gt;Great print and PDF output&lt;/li&gt;
  &lt;li&gt;No reliance on visual editors or WYSIWYG templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also liked the idea of making the whole project open source, so others could see how it was done and adapt it to their own needs.&lt;/p&gt;

&lt;h2 id=&quot;tools-i-used&quot;&gt;Tools I Used&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Editor:&lt;/strong&gt; &lt;a href=&quot;https://overleaf.com&quot;&gt;Overleaf&lt;/a&gt; + local editing in VS Code with LaTeX Workshop&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Template:&lt;/strong&gt; A custom minimalist layout using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;article&lt;/code&gt; class, with optional support for the &lt;a href=&quot;https://github.com/liantze/AltaCV&quot;&gt;AltaCV class&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Version control:&lt;/strong&gt; &lt;a href=&quot;https://github.com/jonathas/your-cv-repo&quot;&gt;GitHub repository&lt;/a&gt; with full LaTeX source and compiled PDF&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Licensing:&lt;/strong&gt; Reusable for personal use under a non-commercial license&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sample-latex-code&quot;&gt;Sample LaTeX Code&lt;/h2&gt;

&lt;p&gt;Here’s a basic example using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;article&lt;/code&gt; class:&lt;/p&gt;

&lt;div class=&quot;language-latex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;\documentclass&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;[11pt,a4paper]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;article&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;[utf8]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;inputenc&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;geometry&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\geometry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;margin=1in&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;\begin{document}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;\section*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Profile&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
Senior Software Engineer with 15+ years of experience across diverse technologies and teams.

&lt;span class=&quot;k&quot;&gt;\section*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Skills&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;\begin{itemize}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;\item&lt;/span&gt; Backend: Node.js, TypeScript, Java, PHP
  &lt;span class=&quot;k&quot;&gt;\item&lt;/span&gt; DevOps: AWS, Terraform, Docker, GitHub Actions
&lt;span class=&quot;nt&quot;&gt;\end{itemize}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;\section*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Experience&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\textbf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Senior Software Engineer&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;\hfill&lt;/span&gt; Jan 2024 -- Present &lt;span class=&quot;k&quot;&gt;\\&lt;/span&gt;
Vacasa – Prague, Czech Republic (Hybrid) &lt;span class=&quot;k&quot;&gt;\\&lt;/span&gt;
Worked on cloud architecture and workflow automation projects supporting large-scale field operations.

&lt;span class=&quot;nt&quot;&gt;\end{document}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want a more modern layout, I highly recommend &lt;a href=&quot;https://github.com/liantze/AltaCV&quot;&gt;AltaCV&lt;/a&gt;, a great open-source LaTeX CV class. You can get started with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/liantze/AltaCV.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or &lt;a href=&quot;https://raw.githubusercontent.com/liantze/AltaCV/master/altacv.cls&quot;&gt;download the class file directly&lt;/a&gt; and use it when building your CV.&lt;/p&gt;

&lt;h2 id=&quot;structure-and-philosophy&quot;&gt;Structure and Philosophy&lt;/h2&gt;

&lt;p&gt;My CV is structured to be clean, readable, and compliant with applicant tracking systems (ATS). It includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A brief summary&lt;/li&gt;
  &lt;li&gt;A key skills section&lt;/li&gt;
  &lt;li&gt;Chronological work experience&lt;/li&gt;
  &lt;li&gt;A compact education section&lt;/li&gt;
  &lt;li&gt;Language skills and legal residency info (since I live in the EU)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything fits into &lt;strong&gt;two pages&lt;/strong&gt;, and updates are now fast and consistent. I no longer have to worry about formatting bugs or version mismatches.&lt;/p&gt;

&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;📂 &lt;a href=&quot;https://github.com/jonathas/cv&quot;&gt;CV GitHub Repository&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;📄 &lt;a href=&quot;https://github.com/jonathas/cv/blob/master/CV_Jonathas_Ribeiro.pdf&quot;&gt;Download the PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/cv-in-latex/cv-preview.png&quot; alt=&quot;CV Preview&quot; title=&quot;CV Preview&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;LaTeX isn’t the quickest way to build a CV, but it’s one of the most reliable and rewarding. It’s a great fit for developers, researchers, or anyone who wants full control over how their work is presented.&lt;/p&gt;

&lt;p&gt;If you’re curious, I encourage you to clone my repo or use it as inspiration for your own LaTeX CV.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/creating-a-cv-in-latex/&quot;&gt;Creating a CV in LaTeX: Clean, Structured, and Versioned&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on July 05, 2025.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[How I Transformed My Flat into a Smart Home with Home Assistant]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/how-i-transformed-my-flat-into-a-smart-home-with-home-assistant/" />
  <id>https://jonathas.com/how-i-transformed-my-flat-into-a-smart-home-with-home-assistant</id>
  <published>2025-06-29T14:30:00+00:00</published>
  <updated>2025-06-29T14:30:00+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;When I first ventured into home automation, like many, I started with Alexa. Voice commands and plug-and-play devices felt futuristic—until I began hitting limitations. Many devices required separate hubs, cloud access, or their own apps just to function. Even something as simple as turning on a socket often involved sending a request to a remote server before the action happened locally. The delay, fragmentation, and privacy concerns all started piling up. That’s when I turned to Home Assistant.&lt;/p&gt;

&lt;p&gt;Today, my entire flat runs on &lt;a href=&quot;https://www.home-assistant.io/yellow/&quot;&gt;Home Assistant Yellow&lt;/a&gt;, which comes with a built-in Zigbee radio and is built specifically for local-first automation. It was a game-changer. I went from juggling multiple ecosystems to a cohesive, responsive, and private smart home that works exactly the way I want.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/home-assistant-yellow.jpg&quot; alt=&quot;Home Assistant Yellow&quot; title=&quot;Home Assistant Yellow&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-home-assistant&quot;&gt;Why Home Assistant?&lt;/h2&gt;

&lt;p&gt;The biggest draw was flexibility and local control. Unlike Alexa or Google Home, Home Assistant doesn’t depend on the cloud to work. That means no delays, no vendor lock-in, and no worries if the internet goes down. Nearly all my devices now run on Zigbee, connecting directly to the Home Assistant Yellow hub. This drastically reduced latency and increased reliability.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/zigbee-network.png&quot; alt=&quot;Zigbee Network&quot; title=&quot;Zigbee Network&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My choice of Zigbee over Wi-Fi wasn’t just about responsiveness—it was also practical. In a modern flat with multiple smart devices, phones, laptops, TVs, and work equipment all sharing the same Wi-Fi network, congestion becomes inevitable. Zigbee devices operate on a separate mesh network that’s purpose-built for low-power, low-bandwidth communication. This means they don’t compete for bandwidth with streaming video, Zoom calls, or backups. It also makes the entire system more scalable and robust.&lt;/p&gt;

&lt;p&gt;Take my early experience with TP-Link TAPO smart sockets. While they were affordable and easy to set up, they required Wi-Fi and a round trip to the cloud to toggle a switch—even when everything was physically on the same network. It felt absurd to send an internet request just to turn on some devices. I’ve since replaced them all with Zigbee switches, which respond instantly and don’t require an internet connection at all.&lt;/p&gt;

&lt;h2 id=&quot;built-via-the-ui-no-yaml-required&quot;&gt;Built via the UI (No YAML Required)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/ha_ss_1.png&quot; alt=&quot;Home Assistant UI&quot; title=&quot;Home Assistant UI&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Although Home Assistant gives you full access to YAML configuration, I opted for a UI-first approach. The modern Home Assistant dashboard and automation editor are incredibly powerful—I was able to build nearly everything through the web interface without manually editing YAML files. This made the process smoother, safer, and more approachable. You can still find my configuration and structure documented &lt;a href=&quot;https://github.com/jonathas/homeassistant-config&quot;&gt;on GitHub&lt;/a&gt;, but most of the logic was built visually.&lt;/p&gt;

&lt;h2 id=&quot;everyday-automations&quot;&gt;Everyday Automations&lt;/h2&gt;

&lt;p&gt;Here’s where the system really shines: automation. I’ve built dozens of small automations that make daily life more convenient, comfortable, and safe. Some of my favorites include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;You’ve Got Mail&lt;/strong&gt;: When the mailbox sensor is triggered, Home Assistant sends a notification to my phone so I know mail has arrived—even before I open the door.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Smoke Alert&lt;/strong&gt;: I have three Zigbee-powered smoke sensors—one in the hallway, one in the living room, and another in the office. If any of them detect smoke, Home Assistant instantly notifies me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/push-notifications.png&quot; alt=&quot;Push notifications&quot; title=&quot;Push notifications&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Low Battery Warnings&lt;/strong&gt;: All sensors are monitored for battery status, and when one falls below a certain threshold, I get notified—no more dead devices without warning.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Shutter Control&lt;/strong&gt;: My shutters automatically close after sunset and open at sunrise, using sun elevation as the trigger.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gym Prep Mode&lt;/strong&gt;: When my Withings sleep tracking detects I’ve woken up, a webhook triggers Home Assistant to turn on the lights and get me ready for the gym—before I even touch anything.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Kitchen LED Strip&lt;/strong&gt;: A battery-powered Zigbee switch controls a sleek LED strip mounted under the kitchen counter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/led-strip.jpg&quot; alt=&quot;LED strip&quot; title=&quot;LED strip&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Smart Sockets and Switches&lt;/strong&gt;: Other battery-powered switches control lights through Zigbee-connected smart sockets.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bathroom Fan Logic&lt;/strong&gt;: If the bathroom door is closed and the light is on, the fan activates automatically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Humidity Control&lt;/strong&gt;: A dehumidifier switches on when the indoor humidity passes a predefined level.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Alarm System via Alarmo&lt;/strong&gt;: I’ve integrated a smart siren using the Alarmo custom integration, which is connected to door and window sensors. If the system is armed and a breach is detected, the siren triggers and notifications are sent immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/alarmo.png&quot; alt=&quot;Alarmo&quot; title=&quot;Alarmo&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;seamless-integration-with-apple-ecosystem&quot;&gt;Seamless Integration with Apple Ecosystem&lt;/h2&gt;

&lt;p&gt;One feature that elevates the experience is the &lt;strong&gt;HomeKit Bridge&lt;/strong&gt; integration. Through this, I’ve exposed all my Home Assistant devices to Apple’s Home app. My HomePods can now see and control every switch, light, sensor, and more. It feels native—“Hey Siri, turn off all the lights in the flat” just works—even for Zigbee devices that have no official Apple support. No subscriptions, no iCloud Home Hub required.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/apple-home.png&quot; alt=&quot;Apple Home&quot; title=&quot;Apple Home&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;smarter-lighting-simpler-bulbs&quot;&gt;Smarter Lighting, Simpler Bulbs&lt;/h2&gt;

&lt;p&gt;Instead of relying on smart bulbs—which can be expensive and often require constant power—I opted for Zigbee-enabled wall switches. This approach allows me to use standard light bulbs throughout the flat while maintaining complete control via Home Assistant. The lights behave like any normal wall-controlled circuit, but are also fully accessible for automation, scheduling, and remote control. It keeps things simple for guests and family members while preserving smart functionality in the background.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/wall-sockets.jpg&quot; alt=&quot;Wall sockets&quot; title=&quot;Wall sockets&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;nfc-tags-and-touch-based-automation&quot;&gt;NFC Tags and Touch-Based Automation&lt;/h2&gt;

&lt;p&gt;Another layer of interaction I’ve embraced is &lt;strong&gt;NFC tags&lt;/strong&gt;. These small, inexpensive stickers are placed throughout the flat and programmed to trigger specific automations when tapped with my iPhone.&lt;/p&gt;

&lt;p&gt;For instance, a tag near the bed activates a “Good Night” routine—dimming lights, turning off devices, and arming sensors. A tag by the front door toggles “Leaving Home” mode, shutting everything down and confirming the flat is secure. It’s a subtle, tactile way to control your environment, and it works offline.&lt;/p&gt;

&lt;h2 id=&quot;outdoor-sensing&quot;&gt;Outdoor Sensing&lt;/h2&gt;

&lt;p&gt;Initially, I used a standalone weather station—similar to &lt;a href=&quot;https://www.alza.cz/EN/sencor-sws-2850-d6159756.htm?o=10&quot;&gt;this model&lt;/a&gt;—to track outdoor temperature. While it served its purpose, it had a major limitation: it wasn’t smart. It couldn’t connect to Home Assistant or any other system. To fully integrate weather data into my automations, I replaced it with an Aqara temperature and humidity sensor placed just outside. Since it’s Zigbee-powered, it communicates seamlessly with Home Assistant, just like my indoor sensors. Now, outdoor conditions can influence automations too, such as adjusting ventilation or sending alerts when it’s too cold or humid outside.&lt;/p&gt;

&lt;h2 id=&quot;hallway-control-panel&quot;&gt;Hallway Control Panel&lt;/h2&gt;

&lt;p&gt;To make the smart home even more accessible to everyone in the flat, I’ve mounted a Huawei tablet in the hallway that runs the Home Assistant dashboard full-time. It acts as a dedicated control panel where we can view sensor data, toggle lights, check the status of the alarm, and access custom dashboards. It’s a great fallback for guests or anyone who prefers touch over voice or automation.&lt;/p&gt;

&lt;h2 id=&quot;xiaomi-devices-and-wi-fi-integration&quot;&gt;Xiaomi Devices and Wi-Fi Integration&lt;/h2&gt;

&lt;p&gt;Not everything in my smart home runs on Zigbee. I also use &lt;strong&gt;Xiaomi smart fans&lt;/strong&gt; and a &lt;strong&gt;Roborock vacuum&lt;/strong&gt;, both connected to Home Assistant via Wi-Fi using the &lt;strong&gt;Xiaomi Miio integration&lt;/strong&gt;. While I generally prefer local control, these devices are reliable enough and offer valuable capabilities—like scheduling fan usage during the night or triggering vacuum runs while we’re away.&lt;/p&gt;

&lt;p&gt;Even though these devices connect via Wi-Fi, the Miio integration communicates with them &lt;strong&gt;locally over the network&lt;/strong&gt;, not via the cloud. That means Home Assistant sends commands directly to the device’s IP address on my local network. This keeps the experience fast, reliable, and private—without requiring any round trips to external servers.&lt;/p&gt;

&lt;h2 id=&quot;shutter-integration-with-somfy-tahoma-switch&quot;&gt;Shutter Integration with Somfy TaHoma Switch&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/home-assistant/tahoma-switch.jpg&quot; alt=&quot;Tahoma Switch&quot; title=&quot;Tahoma Switch&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My shutters weren’t Zigbee-compatible out of the box—they required a &lt;strong&gt;TaHoma Switch&lt;/strong&gt;, which acts as a bridge between the proprietary shutter protocol and Home Assistant. Once configured, the TaHoma Switch exposed each shutter as an entity in Home Assistant, allowing me to control them through automations or directly from the dashboard. And because I’ve integrated Home Assistant with the HomeKit Bridge, these shutter controls are also available via Siri on my HomePods. Voice commands like “Hey Siri, set the office shutter to 50%” are now fully supported.&lt;/p&gt;

&lt;p&gt;If you’re curious about how it all fits together, I’ve open-sourced the entire setup at &lt;a href=&quot;https://github.com/jonathas/homeassistant-config&quot;&gt;github.com/jonathas/homeassistant-config&lt;/a&gt;. It’s modular, UI-driven, and designed to scale—from simple automations to complex routines.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/how-i-transformed-my-flat-into-a-smart-home-with-home-assistant/&quot;&gt;How I Transformed My Flat into a Smart Home with Home Assistant&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on June 29, 2025.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[How I Replaced My Frameo Digital Frame with an Android Tablet]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/how-i-replaced-my-frameo-digital-frame-with-an-android-tablet/" />
  <id>https://jonathas.com/how-i-replaced-my-frameo-digital-frame-with-an-android-tablet</id>
  <published>2025-06-08T08:10:08+00:00</published>
  <updated>2025-06-08T08:10:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;Having a digital photo frame at home has always been a nice way to keep our memories alive. Over the years, I’ve collected thousands of photos from family trips, special events, and everyday moments. This turned our digital frame into a slideshow of our best experiences. However, recently, I had issues with my Frameo-powered digital frame, leading me to look for a better solution. Here’s how I moved to a Lenovo Yoga Tab 11 running Fotoo and why it’s been a much better choice.&lt;/p&gt;

&lt;h3 id=&quot;the-issues-with-frameo&quot;&gt;The Issues with Frameo&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/frameo-vs-fotoo/frameo.jpg&quot; alt=&quot;Frameo&quot; title=&quot;Frameo&quot; style=&quot;margin-top: 1.5em;margin-bottom: 1.5em;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Initially, &lt;a href=&quot;https://www.frameo.com/&quot;&gt;Frameo&lt;/a&gt; seemed like a good idea—sending photos directly from an iPhone app to the frame. But in reality, it wasn’t very good. First, the Frameo iOS app took up a lot of space, sometimes more than 4GB on my phone, which is too much for just sending photos.&lt;/p&gt;

&lt;p&gt;Even worse, after uploading a few thousand photos, my digital frame became slow and unresponsive. Navigating photos caused it to crash frequently, needing regular restarts. The touch screen became slow, and the overall experience was frustrating. This made me realize the Frameo digital frame wasn’t reliable at its basic job—showing photos smoothly.&lt;/p&gt;

&lt;h3 id=&quot;the-difficult-frameo-workflow&quot;&gt;The Difficult Frameo Workflow&lt;/h3&gt;

&lt;p&gt;My workflow with Frameo was annoying:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Selecting and sending photos in small groups (only 10 photos at a time unless paying extra).&lt;/li&gt;
  &lt;li&gt;Manually resizing and positioning each photo on the frame to fit the display.&lt;/li&gt;
  &lt;li&gt;Regularly inserting an SD card to manually back up the photos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not exactly easy!&lt;/p&gt;

&lt;h3 id=&quot;moving-to-fotoo-on-lenovo-yoga-tab-11&quot;&gt;Moving to Fotoo on Lenovo Yoga Tab 11&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/frameo-vs-fotoo/tablet.jpg&quot; alt=&quot;Lenovo Yoga Tab 11&quot; title=&quot;Lenovo Yoga Tab 11&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Looking for something better, I decided to use a used Lenovo Yoga Tab 11 as my new digital frame. The tablet might be a bit overkill for this task, but it already has a built-in stand, perfect for displaying photos.&lt;/p&gt;

&lt;p&gt;My new setup includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installing &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.bo.fotoo&quot;&gt;Fotoo&lt;/a&gt;, a simple digital frame app for Android.&lt;/li&gt;
  &lt;li&gt;Using &lt;a href=&quot;https://localsend.org/&quot;&gt;LocalSend&lt;/a&gt; to easily transfer photos directly from my MacBook to the tablet.&lt;/li&gt;
  &lt;li&gt;Automatic backup through Google Photos using the tablet’s built-in syncing feature, so all photos are safely stored in Google Drive for free.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;optimizing-images-with-a-custom-script&quot;&gt;Optimizing Images with a Custom Script&lt;/h3&gt;

&lt;p&gt;The Frameo app on iOS automatically reduced photo quality to save storage space. LocalSend doesn’t have this feature, so I made a simple script to resize and convert my images before sending them:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;f &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.heic&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;sips &lt;span class=&quot;nt&quot;&gt;-Z&lt;/span&gt; 2000 &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; format jpeg &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; formatOptions 75 &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--out&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%.*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.jpg&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This script resizes images to save space without losing much quality, so after selecting the photos, I run the script to resize them and then can just transfer all at once to the tablet.&lt;/p&gt;

&lt;h3 id=&quot;keeping-the-same-screen-schedule&quot;&gt;Keeping the Same Screen Schedule&lt;/h3&gt;

&lt;p&gt;In Frameo, I had my frame set to turn off at 11 pm and turn on again at 7 am. Fotoo allows me to easily set up the tablet to do exactly the same, matching my previous settings perfectly.&lt;/p&gt;

&lt;h3 id=&quot;free-cloud-backup-compared-to-frameo&quot;&gt;Free Cloud Backup Compared to Frameo&lt;/h3&gt;

&lt;p&gt;One great benefit of using an Android tablet is automatic free backup with Google Photos. In Frameo, cloud backup is only available with a paid subscription, but with my tablet setup, backups happen automatically for free using Google Drive.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Switching from a Frameo digital frame to an Android tablet running Fotoo has greatly improved my experience. Using Fotoo, LocalSend, and Google Photos is an efficient and simple way to display our family’s favorite moments. If you’re frustrated with slow or limited digital frames, this setup could save you headaches and storage space!&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/how-i-replaced-my-frameo-digital-frame-with-an-android-tablet/&quot;&gt;How I Replaced My Frameo Digital Frame with an Android Tablet&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on June 08, 2025.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Composition versus Inheritance in Software Engineering]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/composition-vs-inheritance/" />
  <id>https://jonathas.com/composition-vs-inheritance</id>
  <published>2023-11-25T20:10:08+00:00</published>
  <updated>2023-11-25T20:10:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;Programming paradigms such as functional programming (FP) and object-oriented programming (OOP) are used in conjunction with some important principles and concepts in order to create modular and maintainable code. Today we’ll take a look at the concepts of composition and inheritance, demonstrating their use with some examples.&lt;/p&gt;

&lt;h2 id=&quot;in-this-article&quot;&gt;In this article&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-composition&quot;&gt;What is Composition?&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#pros-of-using-composition&quot;&gt;Pros of using Composition&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cons-of-using-composition&quot;&gt;Cons of using Composition&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-inheritance&quot;&gt;What is Inheritance?&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#pros-of-using-inheritance&quot;&gt;Pros of using Inheritance&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cons-of-using-inheritance&quot;&gt;Cons of using Inheritance&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#composition-over-inheritance&quot;&gt;Composition over inheritance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-is-composition&quot;&gt;What is Composition?&lt;/h2&gt;

&lt;p&gt;When organizing your codebase using composition, you need to think of a “has-a” relationship.&lt;/p&gt;

&lt;p&gt;In functional programming, functions are first-class citizens, and composition is a powerful tool for building complex behavior by combining simpler functions.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;square&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;squareAndDouble&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;squareAndDouble&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Result is 18&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;squareAndDouble is composed of the square and double functions. This allows us to create a pipeline of operations, making the code more modular and readable.&lt;/p&gt;

&lt;p&gt;Not only limited to functional programming, composition can also be used in object-oriented projects.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Engine&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Engine started&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;engine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Engine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;engine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Engine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;engine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This will start the car&apos;s engine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/composition-versus-inheritance/dodge_viper.jpg&quot; alt=&quot;Car&quot; title=&quot;Car&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this example, a Car &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has an&lt;/code&gt; Engine. Through composition, a Car is composed of an Engine. This allows for a flexible and modular design where the Car class can leverage the functionality of the Engine class without directly inheriting from it.&lt;/p&gt;

&lt;h3 id=&quot;pros-of-using-composition&quot;&gt;Pros of using Composition&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flexibility&lt;/code&gt;: Composition allows for a more flexible and dynamic relationship between classes. Objects can be composed of other objects at runtime, providing more flexibility in creating complex behaviors.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Code Reusability&lt;/code&gt;: By encapsulating objects within other objects, you can reuse components in different contexts. This promotes code reusability and modular design.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Avoidance of tight coupling&lt;/code&gt;: Changes to one class do not directly impact other classes, reducing the risk of unintended side effects. This makes the codebase more maintainable.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;No fragile base class problem&lt;/code&gt;: Unlike inheritance, changes to the base class do not affect the functionality of the derived classes. This avoids breaking existing code in derived classes, which is an issue that happens with inheritance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cons-of-using-composition&quot;&gt;Cons of using Composition&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Indirect access&lt;/code&gt;: Accessing the functionality of a composed object may require additional methods in the containing class. This can lead to more verbose code when compared with inheritance.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;More boilerplate code&lt;/code&gt;: In some cases, composition can lead to more boilerplate code, especially when dealing with multiple levels of nesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-is-inheritance&quot;&gt;What is Inheritance?&lt;/h2&gt;

&lt;p&gt;When organizing your codebase using inheritance, you need to think of a “is-a” relationship.&lt;/p&gt;

&lt;p&gt;Inheritance is a core concept in object-oriented programming, allowing for the creation of specialized classes that inherit behavior and attributes from more general classes.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Animal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Animal speaks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Animal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Dog barks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myDog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myDog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This will call the speak method inherited from Animal&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myDog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// This will call the bark method specific to Dog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/composition-versus-inheritance/husky.jpg&quot; alt=&quot;Dog&quot; title=&quot;Dog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this example, the Animal class has a speak method and a Dog class extends Animal, as a dog &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is an&lt;/code&gt; animal. Through inheritance, the Dog class inherits the speak method and adds its own bark method. This demonstrates a “is-a” relationship.&lt;/p&gt;

&lt;h3 id=&quot;pros-of-using-inheritance&quot;&gt;Pros of using Inheritance&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Code Reusability&lt;/code&gt;: Inheritance allows for the reuse of code from a base class in derived classes. This can lead to a more concise and DRY (Don’t Repeat Yourself) codebase.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Polymorphism&lt;/code&gt;: Inheritance supports polymorphism, allowing objects of derived classes to be treated as objects of the base class. This promotes a more generalized and abstract design.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Less boilerplate code&lt;/code&gt;: In some scenarios, inheritance can result in less boilerplate code compared to composition, especially for simple hierarchies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cons-of-using-inheritance&quot;&gt;Cons of using Inheritance&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tight Coupling&lt;/code&gt;: Changes in the base class can affect all derived classes, leading to tight coupling. This can make the code more fragile and less adaptable to changes.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Inflexibility&lt;/code&gt;: The “is-a” relationship enforced by inheritance may lead to a less flexible design. If the relationship between classes changes, it can be challenging to adapt the code.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Limited to single inheritance&lt;/code&gt;: Many languages support single inheritance only, which restricts the ability to inherit from multiple classes. This limitation can be addressed with interfaces or mixins, but it adds complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;composition-over-inheritance&quot;&gt;Composition over inheritance&lt;/h2&gt;

&lt;p&gt;Composition over inheritance is a design principle that suggests what the name says: favoring composition over inheritance. This approach promotes flexibility, code reuse, and maintainability.&lt;/p&gt;

&lt;p&gt;For example, if you have a Logger class, it can be part of other classes via composition:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Base class representing a Logger&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`[Log]: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Class representing a User with logging capability using composition&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Hello, my name is &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Class representing an Admin with logging capability using composition&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Admin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;announce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Admin announcement: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; is here!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Example usage&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regularUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;John&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;regularUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Outputs: [Log]: Hello, my name is John&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;systemAdmin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Admin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Admin1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;systemAdmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;announce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Outputs: [Log]: Admin announcement: Admin1 is here!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This approach provides:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Flexibility, as you can easily change or extend the behavior of User and Admin without being constrained by the limitations of a fixed inheritance hierarchy.&lt;/li&gt;
  &lt;li&gt;Code reusability, as the Logger class can be reused in other contexts as well, making it modular and reusable&lt;/li&gt;
  &lt;li&gt;Maintainability: Changes to the Logger class do not affect the functionality of other classes, reducing side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If instead we used inheritance for this:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Using inheritance&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UserWithLogger&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Hello, my name is &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AdminWithLogger&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;announce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Admin announcement: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; is here!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’d achieve the same logging functionality, but now there’s a tight coupling between the Logger and User or Admin classes. Changes to the Logger class might affect the behavior of User and Admin. Using composition over inheritance helps avoid this issue.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Choosing between composition and inheritance often involves a trade-off between flexibility, code reusability, and maintainability. In practice, a combination of both approaches is frequently used to leverage the benefits of each while mitigating their weaknesses. This allows for a more adaptable and modular design, where composition and inheritance are used where they make the most sense in the context of the application’s requirements.&lt;/p&gt;

&lt;p&gt;I’d be more inclined to use inheritance when thinking about a “is-a” relationship in the codebase and to use composition when thinking about a “has-a” relationship, so it depends on the specific requirements of the project. In a way, composition should always be there if you want to use Dependency Injection in your project, since it would be a prerequisite for it, as DI uses composition.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/composition-vs-inheritance/&quot;&gt;Composition versus Inheritance in Software Engineering&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on November 25, 2023.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Optimizing Data Access: A Deep Dive into Database Indexes]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/optimizing-data-access-database-indexes/" />
  <id>https://jonathas.com/optimizing-data-access-database-indexes</id>
  <published>2023-11-23T16:30:08+00:00</published>
  <updated>2023-11-23T16:30:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;When considering database optimization, the efficiency of retrieving information is crucial. One key player in this quest for efficiency is the database index. Let’s dive into the world of database indexes and uncover what happens under the hood.&lt;/p&gt;

&lt;h2 id=&quot;in-this-article&quot;&gt;In this article&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-are-database-indexes&quot;&gt;What are Database Indexes?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#types-of-indexes&quot;&gt;Types of Indexes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#trade-offs-and-considerations&quot;&gt;Trade-offs and Considerations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#practical-examples&quot;&gt;Practical examples&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#inserting-some-data&quot;&gt;Inserting some data&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#examples-of-index-usage&quot;&gt;Examples of index usage&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-should-be-considered-when-improving-index-usage&quot;&gt;What should be considered when improving index usage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-are-database-indexes&quot;&gt;What are Database Indexes?&lt;/h2&gt;

&lt;p&gt;At its core, a database index is a data structure that provides a faster way to locate rows in a table based on the values in one or more columns. Imagine a well-organized library where books are arranged by genres or authors. This way of organizing them significantly accelerates the process of finding a specific book. In a similar way, a database index works as a roadmap, guiding the database engine to the relevant rows in a table.&lt;/p&gt;

&lt;p&gt;Most database indexes utilize a B-tree (balanced tree) structure. This structure allows for efficient insertion, deletion, and search operations. Each entry in the index is a key-value pair, with the key representing the indexed column(s) and the value pointing to the corresponding row in the table.&lt;/p&gt;

&lt;h2 id=&quot;types-of-indexes&quot;&gt;Types of Indexes&lt;/h2&gt;

&lt;p&gt;Database indexes can be created manually by administrators or automatically by the database management system (DBMS). Indexes can be applied to one or more columns, and there are various types to cater to different needs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Unique Indexes: Ensure the uniqueness of values in the indexed columns, preventing duplicate entries.&lt;/li&gt;
  &lt;li&gt;Clustered Indexes: Determine the physical order of data rows in the table based on the index, influencing data storage layout. In a clustered index, the rows in the table are stored in the same order as the index. Each table can have only one clustered index.&lt;/li&gt;
  &lt;li&gt;Non-clustered Indexes: Create a separate structure for the index, leaving the data rows unchanged. Unlike a clustered index, a non-clustered index does not affect the physical order of the rows in the table and allows for the creation of multiple non-clustered indexes on a single table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a query involving the indexed column(s) is executed, the database engine utilizes the index to quickly locate the relevant rows. This process reduces the need to scan the entire table, resulting in a significant reduction in disk I/O operations. B-tree indexes, often sorted, enable efficient range queries, making them particularly powerful in enhancing read operation performance.&lt;/p&gt;

&lt;h2 id=&quot;trade-offs-and-considerations&quot;&gt;Trade-offs and Considerations&lt;/h2&gt;

&lt;p&gt;While database indexes offer immense benefits in terms of query performance, they come with trade-offs. Indexes consume additional disk space, and their maintenance can impact the performance of write operations such as inserts, updates, and deletes. Careful consideration is needed when deciding which columns to index, taking into account the selectivity of the index and the specific needs of the database.&lt;/p&gt;

&lt;p&gt;Updating or deleting data in indexed columns requires corresponding updates to the index to maintain accuracy. Periodic rebuilding or reorganizing of indexes may be necessary to optimize their performance over time.&lt;/p&gt;

&lt;p&gt;Indexes with high selectivity (a large number of unique values) are more effective in improving query performance.&lt;/p&gt;

&lt;p&gt;Composite indexes involve multiple columns, optimizing queries that filter on multiple criteria.&lt;/p&gt;

&lt;h2 id=&quot;practical-examples&quot;&gt;Practical examples&lt;/h2&gt;

&lt;p&gt;Theory yada-yada.. let’s see some examples.&lt;/p&gt;

&lt;p&gt;Continuing on the library idea that  was mentioned before, we have the following tables represented in SQL below:&lt;/p&gt;

&lt;h3 id=&quot;authors&quot;&gt;Authors&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;publishers&quot;&gt;Publishers&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publishers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;books&quot;&gt;Books&lt;/h3&gt;

&lt;p&gt;The books table includes information about each book, such as title, publication year, ISBN, and the publisher.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;publication_year&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;isbn&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;publisher_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publisher_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publishers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;authorsbooks-association-table&quot;&gt;AuthorsBooks (Association Table)&lt;/h3&gt;

&lt;p&gt;This table represents the many-to-many relationship between authors and books. It allows multiple authors to be associated with multiple books.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;author_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;book_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;author_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;author_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Each book can have multiple authors (many-to-many relationship).&lt;/li&gt;
  &lt;li&gt;Each author can write multiple books (many-to-many relationship).&lt;/li&gt;
  &lt;li&gt;Each book is associated with one publisher (many-to-one relationship).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;inserting-some-data&quot;&gt;Inserting some data&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Inserting authors&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;J.K.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Rowling&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;George R.R.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Martin&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Jane&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Austen&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Inserting publishers&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publishers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Penguin Books&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publishers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;HarperCollins&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Inserting books&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Harry Potter and the Philosopher&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;s Stone&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1997&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;978-0747532743&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;A Game of Thrones&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1996&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;978-0553103540&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Pride and Prejudice&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1813&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;978-1503290563&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Inserting author-book associations&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- J.K. Rowling wrote Harry Potter&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- George R.R. Martin wrote A Game of Thrones&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- Jane Austen wrote Pride and Prejudice&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;-- J.K. Rowling also wrote A Game of Thrones&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;retrieving-all-books-written-by-jk-rowling&quot;&gt;Retrieving all books written by J.K. Rowling&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;author_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;J.K.&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Rowling&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;retrieving-the-details-of-the-authors-of-a-game-of-thrones&quot;&gt;Retrieving the details of the authors of “A Game of Thrones”&lt;/h3&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors_books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;author_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;A Game of Thrones&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;examples-of-index-usage&quot;&gt;Examples of index usage&lt;/h3&gt;

&lt;p&gt;Some good examples of index usage are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The primary key index in the authors table, on the “id” attribute, as it is crucial for efficiently locating specific authors based on their unique id.&lt;/li&gt;
  &lt;li&gt;The foreign key index in the books table, on the “publisher_id” attribute, as it facilitates quick retrieval of books associated with a particular publisher, optimizing join operations.&lt;/li&gt;
  &lt;li&gt;The composite index on the authors_books association table, which is made of author_id and book_id and supports the many-to-many relationship between authors and books. It enhances the performance of queries involving specific author-book associations.&lt;/li&gt;
  &lt;li&gt;Index on frequently queried columns, like for example on the “title” attribute of the books table. If queries frenquently search for books based on their titles, creating an index on the “title” column can significantly improve query performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some bad examples of index usage are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Over-indexing: Creating an index on both first_name and last_name on the authors table might be unnecessary if queries rarely involve searching for authors based on both criteria. It could lead to index bloat and increased maintenance overhead without substantial performance gains.&lt;/li&gt;
  &lt;li&gt;Indexing on columns with low selectivity: In the books table, if most books have the same publication year, indexing on this column might not significantly enhance query performance. Indexing columns with high selectivity is generally more beneficial.&lt;/li&gt;
  &lt;li&gt;Unused indexes: If there are no queries that involve searching or joining based on the publishers table, creating an index on this table might be unnecessary and add unnecessary overhead.&lt;/li&gt;
  &lt;li&gt;Overreliance on clustered index: In the books table, while clustering on the primary key (id) is common, overreliance on the clustered index for all types of queries might lead to suboptimal performance for certain query patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-should-be-considered-when-improving-index-usage&quot;&gt;What should be considered when improving index usage&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Selective indexing: Choose indexes based on the selectivity of columns. Indexing highly selective columns enhances the efficiency of the index.&lt;/li&gt;
  &lt;li&gt;Query patterns: Consider the types of queries that are commonly executed. Indexes should align with the patterns of data retrieval in your application.&lt;/li&gt;
  &lt;li&gt;Balance between read and write operations: While indexes can significantly improve read performance, they may impact write operations. Evaluate the trade-offs based on your application’s requirements.&lt;/li&gt;
  &lt;li&gt;Regular monitoring and maintenance: Periodically review and optimize indexes based on the evolving usage patterns of your application. Unused or redundant indexes can be identified and removed.&lt;/li&gt;
  &lt;li&gt;Use tools for analysis: Utilize database management tools to analyze query execution plans and identify areas where indexes can be beneficial. This helps in making informed decisions about index creation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;When considering to optimize your database, understanding how indexes work is essential. They are very important for efficient data retrieval, but their effectiveness depends on factors such as selectivity, indexing strategies and maintenance challenges.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/optimizing-data-access-database-indexes/&quot;&gt;Optimizing Data Access: A Deep Dive into Database Indexes&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on November 23, 2023.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Bringing Back the Flags! Keyboard Layout Indicator as Country Flags on Gnome]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/keyboard-layout-indicator-as-country-flags-on-gnome/" />
  <id>https://jonathas.com/keyboard-layout-indicator-as-country-flags-on-gnome</id>
  <published>2023-01-21T12:10:08+00:00</published>
  <updated>2023-01-21T12:10:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;&lt;a href=&quot;https://www.gnome.org/&quot;&gt;Gnome&lt;/a&gt; is a free and open-source desktop environment for Linux and Unix-like operating systems. It is one of the most popular desktop environments for Linux, and is the default desktop environment for many Linux distributions. Gnome is known for its simplicity, ease of use and a user-friendly interface. It is designed to be easy to use for people of all ages and skill levels and it’s also fully themeable and customizable.&lt;/p&gt;

&lt;p&gt;One of the features that Gnome used to offer was the ability to display country flags as the keyboard layout indicator. This feature was present in earlier versions of Gnome, but was removed in later versions. The thing is, users don’t always agree with the decisions a product takes, so in this case as the product is open-source and highly configurable, it’s much easier to customize it to our liking.&lt;/p&gt;

&lt;p&gt;A few months ago I implemented a Python script which updates the config files related to X11, where Gnome fetches the information for the keyboard layout indicator, and changes the country codes to their flags instead.&lt;/p&gt;

&lt;h2 id=&quot;current-state&quot;&gt;Current state&lt;/h2&gt;

&lt;p&gt;On Gnome, the keyboard layout is presented as the code of the language which is currently selected. In the example below, the English (US) keyboard layout is selected:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/keyboard-layout-indicator/gnome-keyboard-original.jpg&quot; alt=&quot;Original Keyboard Layout indicator&quot; title=&quot;Original Keyboard Layout indicator&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;after-the-script&quot;&gt;After the script&lt;/h2&gt;

&lt;p&gt;A country flag there looks nicer to me instead of the language. After this script runs, this is what that would look like for the English (US) keyboard layout:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/keyboard-layout-indicator/gnome-keyboard-modified.jpg&quot; alt=&quot;Modified Keyboard Layout indicator&quot; title=&quot;Modified Keyboard Layout indicator&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ps: Other keyboard layouts/languages are also available&lt;/p&gt;

&lt;h3 id=&quot;the-switcher-now-looks-like-this&quot;&gt;The switcher now looks like this&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/keyboard-layout-indicator/keyboard-layout-switcher.jpg&quot; alt=&quot;Keyboard Layout switcher&quot; title=&quot;Keyboard Layout switcher&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;in-context&quot;&gt;In context&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/keyboard-layout-indicator/keyboard-layout-screenshot.jpg&quot; alt=&quot;Keyboard Layout screenshot&quot; title=&quot;Keyboard Layout screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;installing&quot;&gt;Installing&lt;/h2&gt;

&lt;p&gt;If you use &lt;a href=&quot;https://archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; or any distribution based on it, you can get it from &lt;a href=&quot;https://wiki.archlinux.org/title/Arch_User_Repository&quot;&gt;AUR&lt;/a&gt; using &lt;a href=&quot;https://github.com/Jguer/yay&quot;&gt;yay&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yay &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; x11-keyboard-flags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On other distributions which have nothing to do with Arch Linux, you can &lt;a href=&quot;https://github.com/jonathas/x11-keyboard-flags&quot;&gt;get the source code&lt;/a&gt; and install it manually.&lt;/p&gt;

&lt;h2 id=&quot;using&quot;&gt;Using&lt;/h2&gt;

&lt;p&gt;Run it with sudo:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;x11-keyboard-flags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then reset gnome-shell by pressing Alt+F2 and entering the “r” command.&lt;/p&gt;

&lt;p&gt;The script makes a backup of the original evdev.xml file from X11 before running and then sets the flags in the original evdev.xml file&lt;/p&gt;

&lt;p&gt;And that’s it! After these steps you’ll see the flags in your keyboard layout selector.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This customization can enhance the visual representation of the keyboard layout and make the language switching process more intuitive. As a further step, you can include this script in the startup process of your OS so that even if the package related to the file that was changed gets updated, the country flags will still be there.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/keyboard-layout-indicator-as-country-flags-on-gnome/&quot;&gt;Bringing Back the Flags! Keyboard Layout Indicator as Country Flags on Gnome&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on January 21, 2023.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Efficient Software Release Management with Automated Changelog Generation]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/efficient-software-release-management-with-automated-changelog-generation/" />
  <id>https://jonathas.com/efficient-software-release-management-with-automated-changelog-generation</id>
  <published>2023-01-15T21:10:08+00:00</published>
  <updated>2023-01-15T21:10:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;The software release process is a critical aspect of any software development project. A manual software release process can be time-consuming, error-prone, and often leaves room for human error. This is where an automated release process comes in. The tools mentioned in this article (used in TypeScript/Javascript projects) can help streamline and optimize the software release process, making it more efficient and less prone to errors, by reducing human involvement.&lt;/p&gt;

&lt;p&gt;The importance of the items and the order in which they are presented in this article, is that each of them is part of the final result which we wish to achieve. In the end you’ll see they all match together to make the development and release process work.&lt;/p&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;When I mention the “master/main” branch, it can be either master or main, depending on what you agreed with your team.&lt;/li&gt;
  &lt;li&gt;When I mention Github Actions it can also be done in any other CI/CD service or software&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;in-this-article&quot;&gt;In this article&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#versioning&quot;&gt;Versioning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#a-convention-to-use-in-all-commits&quot;&gt;A convention to use in all commits&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-sure-that-the-conventional-commit-rules-are-followed&quot;&gt;Making sure that the Conventional Commit rules are followed&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#local-git-hooks&quot;&gt;Local Git hooks&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#configuring-husky&quot;&gt;Configuring husky&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#way-of-working&quot;&gt;Way of working&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#branch-naming-conventions&quot;&gt;Branch naming conventions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pull-request-title&quot;&gt;Pull Request title&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#making-sure-the-pull-request-title-format-is-followed&quot;&gt;Making sure the Pull Request title format is followed&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pull-request-content&quot;&gt;Pull Request content&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#merging-strategy&quot;&gt;Merging strategy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#status-checks-on-pull-requests&quot;&gt;Status checks on Pull Requests&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#deployment-to-staging&quot;&gt;Deployment to Staging&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#deployment-to-production&quot;&gt;Deployment to Production&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#rolling-back&quot;&gt;Rolling back&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#creating-a-release&quot;&gt;Creating a Release&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#configuring-release-it&quot;&gt;Configuring release-it&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#automatic-changelog-generation&quot;&gt;Automatic Changelog generation&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#configuring-auto-changelog&quot;&gt;Configuring auto-changelog&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#further-improvements&quot;&gt;Further improvements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;versioning&quot;&gt;Versioning&lt;/h2&gt;

&lt;p&gt;When thinking about a software release, it all starts with deciding how the versioning will be handled. I’ve been following &lt;a href=&quot;https://semver.org/&quot;&gt;Semantic Versioning (semver)&lt;/a&gt; in my projects for years now and recommend it.&lt;/p&gt;

&lt;p&gt;This means that:&lt;/p&gt;

&lt;p&gt;Given a version number MAJOR.MINOR.PATCH, increment the:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;MAJOR version when you make incompatible API changes&lt;/li&gt;
  &lt;li&gt;MINOR version when you add functionality in a backwards compatible manner&lt;/li&gt;
  &lt;li&gt;PATCH version when you make backwards compatible bug fixes
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ps: There’s also a variation of it for purely &lt;a href=&quot;https://github.com/AshCoolman/semantic-frontend-versioning&quot;&gt;frontend repositories&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-convention-to-use-in-all-commits&quot;&gt;A convention to use in all commits&lt;/h2&gt;

&lt;p&gt;In most projects, if the team wants to keep some kind of changelog, it’s necessary to write the latest changes somewhere manually (Confluence, Notion, a CHANGELOG.md file, Slack, etc).
We want to automate that and remove the manual steps required in order to create a release, but we’ll get to this part on how to automate the changelog generation in a bit.
There’s one step which is a requirement for us to get there:&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0/&quot;&gt;Conventional Commits&lt;/a&gt; specification provides a group of rules on how to format every commit message so that they all follow the same pattern across the project. It is used by the &lt;a href=&quot;https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.t7ifoyph8bd3&quot;&gt;Angular&lt;/a&gt; team and many other big projects.&lt;/p&gt;

&lt;p&gt;Example of a commit message: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A commit contains the following structural elements, to communicate intent:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;fix: a commit of the type fix patches a bug in your codebase (this correlates with &lt;strong&gt;PATCH&lt;/strong&gt; in Semantic Versioning).&lt;/li&gt;
  &lt;li&gt;feat: a commit of the type feat introduces a new feature to the codebase (this correlates with &lt;strong&gt;MINOR&lt;/strong&gt; in Semantic Versioning).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;BREAKING CHANGE&lt;/strong&gt;: a commit that has a footer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BREAKING CHANGE:&lt;/code&gt;, or appends a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt; after the type/scope, introduces a breaking API change (correlating with &lt;strong&gt;MAJOR&lt;/strong&gt; in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.&lt;/li&gt;
  &lt;li&gt;types other than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix:&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feat:&lt;/code&gt; are allowed, for example &lt;a href=&quot;https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional&quot;&gt;@commitlint/config-conventional&lt;/a&gt; (based on the &lt;a href=&quot;https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines&quot;&gt;Angular convention&lt;/a&gt;) recommends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chore:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;style:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refactor:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf:&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test:&lt;/code&gt;, and others.&lt;/li&gt;
  &lt;li&gt;footers other than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BREAKING CHANGE: &amp;lt;description&amp;gt;&lt;/code&gt; may be provided and follow a convention similar to &lt;a href=&quot;https://git-scm.com/docs/git-interpret-trailers&quot;&gt;git trailer format&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g.,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feat(parser): add ability to parse arrays.&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;making-sure-that-the-conventional-commit-rules-are-followed&quot;&gt;Making sure that the Conventional Commit rules are followed&lt;/h2&gt;

&lt;p&gt;In order to enforce that the correct format is followed, the commitlint library can be automatically called via &lt;strong&gt;git hooks&lt;/strong&gt; locally when the developer tries to create a new commit. It works as a linter, but for commit messages!&lt;/p&gt;

&lt;p&gt;Install the following packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@commitlint/cli&quot;&gt;@commitlint/cli&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@commitlint/config-conventional&quot;&gt;@commitlint/config-conventional&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then add a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commitlint.config.js&lt;/code&gt; to the root of your project, with the following content:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;extends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@commitlint/config-conventional&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;local-git-hooks&quot;&gt;Local Git hooks&lt;/h2&gt;

&lt;p&gt;Git hooks are scripts that run automatically before or after executing Git commands like Commit and Push. With Git hook scripts, users can customize Git’s internal behavior by automating specific actions at the level of programs and deployment, like for example validating that the commit is following the standards and not breaking the tests.&lt;/p&gt;

&lt;p&gt;They can be managed with the use of a library called &lt;a href=&quot;https://www.npmjs.com/package/husky&quot;&gt;husky&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The hooks I usually configure are: pre-commit, commit-msg and pre-push&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;pre-commit&lt;/strong&gt;: When the developer tries to commit what was implemented, the pre-commit hook will be automatically called and run eslint with the predefined rules. If the code is invalidated by these rules, the commit doesn’t happen until the issues are fixed. It’s better to have the linter at commit time to allow for smaller and timely iterations instead of only checking these during the push to the repository&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;commit-msg&lt;/strong&gt;: When the developer writes the commit message, it is automatically checked against commitlint, which validates the commit message against the Conventional Commits specification. If the commit message is not valid, the commit doesn’t happen until the message is fixed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pre-push&lt;/strong&gt;: When the developer tries to push the commit(s) to the repository, the pre-push hook is called automatically and runs “npm test”. The push to the repository only happens if all tests are passing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;configuring-husky&quot;&gt;Configuring husky&lt;/h3&gt;

&lt;p&gt;Install the husky library as a dev dependency:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i husky &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the following to add the following to the package.json file:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm pkg &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;scripts.prepare&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;husky install&quot;&lt;/span&gt;
npm run prepare
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add the pre-commit hook that runs the linter:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx husky add .husky/pre-commit &lt;span class=&quot;s2&quot;&gt;&quot;npm run lint&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ps: My eslintrc example can be found &lt;a href=&quot;https://gist.github.com/jonathas/c6b5f110e1eaf92d94ac976a19a3a178&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add the commit-msg hook that runs commitlint:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx husky add .husky/commit-msg &lt;span class=&quot;s2&quot;&gt;&quot;npx commitlint --edit &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then finally the pre-push hook that runs the tests:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx husky add .husky/pre-push &lt;span class=&quot;s2&quot;&gt;&quot;npm test&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After these steps, commit the .husky directory to the git repository.&lt;/p&gt;

&lt;h2 id=&quot;way-of-working&quot;&gt;Way of working&lt;/h2&gt;

&lt;p&gt;For the automated changelog generation to work well, it’s important to have a well defined development process.&lt;/p&gt;

&lt;p&gt;When a developer starts to work on a ticket, a new branch needs to be created in the git repository. This branch must be created out of the &lt;strong&gt;develop branch&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;branch-naming-conventions&quot;&gt;Branch naming conventions&lt;/h3&gt;

&lt;p&gt;The name of this new branch that the developer will create can be anything. It doesn’t really matter in this case, as it must be automatically deleted after the PR is merged so that the repository doens’t become a mess of hundreds or thousands of branches. What I recommend, though, is to follow a similar format to what is done in the commits, so that it’s easy to identify what that branch is about.&lt;/p&gt;

&lt;p&gt;For example, if it’s a new feature:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;feat/name-of-the-feature&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;if it’s a fix:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;fix/name-of-the-fix&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;pull-request-title&quot;&gt;Pull Request title&lt;/h3&gt;

&lt;p&gt;When the developer is ready to submit changes to the repository, a new Pull Request needs to be created.
The Pull Request title matters more than the name of the branch, since that’s what will end up in our git history and, consequently in our automatically generated changelog! Hence, when creating a Pull Request, its name must follow the &lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0&quot;&gt;Conventional Commits&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;An example of how the PRs following Conventional Commits would look like in the repository history:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/release-process/repo-history.png&quot; alt=&quot;Repository history&quot; title=&quot;Repository history&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you’d like to also link the PRs to Jira tickets, it’s recommended to add the Jira ticket to the PR title as well, following this format:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;type&amp;gt;(scope): description [jiraticket-number]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix(calendar): default pagination limit [AG-1103]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;AG-1103 in this case being the Jira ticket.&lt;/p&gt;

&lt;p&gt;Ps: You’ll need to, of course, configure this sync between Github and Jira separately. This part is not covered by this article.&lt;/p&gt;

&lt;h3 id=&quot;making-sure-the-pull-request-title-format-is-followed&quot;&gt;Making sure the Pull Request title format is followed&lt;/h3&gt;

&lt;p&gt;Using Github Actions, it’s easy to implement a workflow that checks the Pull Request title and validates it against the Conventional Commits specification.&lt;/p&gt;

&lt;p&gt;You can find the gist with the code &lt;a href=&quot;https://gist.github.com/jonathas/0fa8c8063d8b9317a9b6e53cb7ec6450&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Save this code to the following path in your project: &lt;strong&gt;.github/workflows/lint-pr-title.yml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whenever a PR is created, this workflow runs and doesn’t allow the PR to be merged if the title is incorrect.&lt;/p&gt;

&lt;p&gt;Imagine dependabot creates 20 PRs and all of them run your Github Actions workflows. That would spend a lot of precious Github Actions minutes, which cost money!
So as a bonus, this workflow doesn’t run on PRs created automatically by &lt;a href=&quot;https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/&quot;&gt;dependabot&lt;/a&gt;, which should already be following the Conventional Commits specification.&lt;/p&gt;

&lt;h3 id=&quot;pull-request-content&quot;&gt;Pull Request content&lt;/h3&gt;

&lt;p&gt;A good pull request should allow developers to review it quickly, so it needs to be small and well explained.
The best way of thinking about it to keep it smaller is that it should cover one thing only, as in the Single Responsibility Principle (SRP).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/release-process/pr-size.png&quot; alt=&quot;PR size&quot; title=&quot;PR size&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s an interesting and worth reading article I found the other day that gets deeper into this topic: &lt;a href=&quot;https://hugooodias.medium.com/the-anatomy-of-a-perfect-pull-request-567382bb6067&quot;&gt;The anatomy of a perfect pull request&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;merging-strategy&quot;&gt;Merging strategy&lt;/h3&gt;

&lt;p&gt;In order for the changelog generation process to work correctly, the git history needs to be clean, so there must be no merge commits in the develop nor in the master/main branches.
The repository needs to be configured to only allow squash merging, so that only PRs end up in the git history.
In Github that can be done in the following area:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/release-process/github-squash-merge-config.png&quot; alt=&quot;Github config&quot; title=&quot;Github config&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And then the developers can configure git locally to always use rebase so that they won’t need to create merge commits when fixing conflicts in PRs.
This can be done globally for all projects by running the following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; pull.rebase &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then when synching the changes from the develop branch to the branch they’re working on, they can just pull the latest changes from develop into their branch and the rebase will happen automatically:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout feat/my-feature-branch
git pull origin develop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;status-checks-on-pull-requests&quot;&gt;Status checks on Pull Requests&lt;/h3&gt;

&lt;p&gt;As with the Github Action workflow to validate the PR title, it’s recommended to run other status checks on PRs to validate that the PR can only be merged to the develop branch if all the status checks are passing.
These are recommended to be added to the repository:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Run integration tests&lt;/li&gt;
  &lt;li&gt;Run unit tests&lt;/li&gt;
  &lt;li&gt;SonarCloud Code Analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bear in mind that if you have dependabot enabled in your repository, it’s usually a good idea to change the validations above so that they don’t run on PRs created by dependabot. Otherwise you’ll see a big increase in Github Actions minutes.
I won’t go deeper in details about them as it’s not the focus of this article.&lt;/p&gt;

&lt;h3 id=&quot;deployment-to-staging&quot;&gt;Deployment to Staging&lt;/h3&gt;

&lt;p&gt;From “&lt;a href=&quot;https://umbraco.com/knowledge-base/staging-environment/&quot;&gt;What is a Staging Environment?&lt;/a&gt;”&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A staging environment or staging site is a copy of your live website and is the last step in the deployment process before changes are deployed to your live website.
By having a staging environment that is a copy of your live environment you are able to test new changes made by your developers before they are released to your live website. Using multiple environments is not necessary, but it comes with a long list of advantages, which are especially important if you work on big or complex projects.
Testing new changes on a staging environment before deploying them to your live website also reduces the risk of any errors or issues that will affect your users. This effectively means happier users and more uptime for your website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The deployment to Staging should happen every time a PR or commit is merged to the &lt;strong&gt;develop branch&lt;/strong&gt;.
This can be done by configuring Github Actions, for example, to start and handle the deployment once that happens.&lt;/p&gt;

&lt;h3 id=&quot;deployment-to-production&quot;&gt;Deployment to Production&lt;/h3&gt;

&lt;p&gt;If there’s no well defined process yet for deployments to production, it’s always good to follow the golden rule of “no deployments on Fridays!”&lt;/p&gt;

&lt;p&gt;Whenever we want to deploy to production, a release needs to be created.&lt;/p&gt;

&lt;p&gt;A &lt;a href=&quot;https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow&quot;&gt;release branch&lt;/a&gt; can be created out of the develop branch, then a PR can be created so that after it’s approved it’s merged to the master/main branch.&lt;/p&gt;

&lt;p&gt;Then after that, the release process can be started manually with the help of the &lt;a href=&quot;https://www.npmjs.com/package/release-it&quot;&gt;release-it&lt;/a&gt; wizard or the Github Action can pick it up, run the release-it lib inside of it in automated mode and do what else needs to be done for deployment. We’ll talk more about this part below, so keep reading.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The release process should always be started on the master/main branch and then synched back to develop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;rolling-back&quot;&gt;Rolling back&lt;/h3&gt;

&lt;p&gt;In case the release breaks production even after all the status checks and QA approval, we need to have a way to rollback what is deployed to production to a stable version.
This can be done via a Github action workflow, which exists for this purpose and can be started manually.&lt;/p&gt;

&lt;p&gt;From the repository, the developer should be able to select which branch this workflow should run from, so that this branch with the fix replaces what is in production.
Or this workflow can instead always revert the master/main branch to the previous version. It’s a good idea to decide with your team how this process will be handled in your specific case.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-release&quot;&gt;Creating a Release&lt;/h2&gt;

&lt;p&gt;Finally after all is configured in the repository and the team is following a well defined development process, it’s time to talk about creating the release!&lt;/p&gt;

&lt;p&gt;This process is handled by the &lt;a href=&quot;https://www.npmjs.com/package/release-it&quot;&gt;release-it&lt;/a&gt; library, which can be used in two ways:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Manually: One of the developers runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run release&lt;/code&gt; locally, which starts the release-it wizard, which in turn takes care of what’s needed.&lt;/li&gt;
  &lt;li&gt;Automatically via CI/CD: After the PR is merged, the Github Action with the release-it library starts from there and takes care of what’s needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the release-it library starts, it:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Identifies which commits happened after the last version&lt;/li&gt;
  &lt;li&gt;Bumps the version in package.json following the Semver convention&lt;/li&gt;
  &lt;li&gt;Adds the changelog related to the new version to the CHANGELOG.md file (using the &lt;a href=&quot;https://www.npmjs.com/package/auto-changelog&quot;&gt;auto-changelog&lt;/a&gt; library)&lt;/li&gt;
  &lt;li&gt;Creates a commit with the new version. For example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chore(release): 0.3.1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Creates a Git tag with the version&lt;/li&gt;
  &lt;li&gt;Pushes these changes to the master/main branch of the repository&lt;/li&gt;
  &lt;li&gt;Creates a Github release&lt;/li&gt;
  &lt;li&gt;Merges the master/main branch back to develop and pushes develop, to keep the branches in sync&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/release-process/release-it-example.png&quot; alt=&quot;Release It&quot; title=&quot;Release It&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;configuring-release-it&quot;&gt;Configuring release-it&lt;/h3&gt;

&lt;p&gt;Install the release-it library:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i release-it &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add the following to the “scripts” part of the package.json file to enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run release&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;release&quot;: &quot;release-it&quot;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create a .release-it.json file on the root of your project:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;before:init&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npm test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;after:bump&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx auto-changelog -p&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;after:git:release&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git checkout develop&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git merge master&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git push origin develop&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;requireBranch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;master&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;commit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;commitMessage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chore(release): ${version}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;commitArgs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tag&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tagName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${version}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tagAnnotation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${version}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;push&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;requireCommits&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;changelog&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx auto-changelog --stdout --commit-limit false -u --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;github&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;releaseName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${version}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tokenRef&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GITHUB_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;npm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;publish&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ps: If your project is an npm library, you can also make release-it push it to npmjs.com automatically by changing npm.publish to true in the config above.&lt;/p&gt;

&lt;h2 id=&quot;automatic-changelog-generation&quot;&gt;Automatic Changelog generation&lt;/h2&gt;

&lt;p&gt;With the use of the release-it library, the changelog is generated automatically during the release process. Every item in git history from the last version until now is added to the beginning of the CHANGELOG.md file in the root of the project. It’s important that the previous steps mentioned in this article are followed so that the resulting changelog is well formatted.&lt;/p&gt;

&lt;p&gt;The automatically generated changelog looks like the following:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/release-process/changelog.png&quot; alt=&quot;Changelog&quot; title=&quot;Changelog&quot; /&gt;
&lt;a href=&quot;https://github.com/jonathas/hockeytech/blob/develop/CHANGELOG.md&quot;&gt;CHANGELOG.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the changelog file is in Markdown format, it also contains links to the specific PRs.&lt;/p&gt;

&lt;h3 id=&quot;configuring-auto-changelog&quot;&gt;Configuring auto-changelog&lt;/h3&gt;

&lt;p&gt;Install the auto-changelog library:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i auto-changelog &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it, as the integration is already configured with release-it.&lt;/p&gt;

&lt;h2 id=&quot;further-improvements&quot;&gt;Further improvements&lt;/h2&gt;

&lt;p&gt;As an idea for further improvement, in case your company has a Slack channel where people need to post changelogs to, you can extend the idea presented in this article to post the automatically generated changelog to Slack.&lt;/p&gt;

&lt;p&gt;The release-it library allows us to add a post release hook that could be integrated with that or it can be done via a job in the same Github Actions workflow.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;By using the tools and processes presented in this article, development teams can improve the software release process and deliver high-quality software faster with efficiency, better accuracy and quality. Do you have any idea for improvement or have you been working differently? Let me know in the comments and let’s discuss so I can learn more from your experience as well!&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/efficient-software-release-management-with-automated-changelog-generation/&quot;&gt;Efficient Software Release Management with Automated Changelog Generation&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on January 15, 2023.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[REST API Best Practices]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/rest-api-best-practices/" />
  <id>https://jonathas.com/rest-api-best-practices</id>
  <published>2019-01-27T19:22:08+00:00</published>
  <updated>2019-01-27T19:22:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;I’ve been working a lot with APIs during the last few years, either developing them or integrating with third party APIs.
So I’ve decided to gather some of the knowledge I gained along the way and write about it here.
Many of the APIs I’ve integrated with didn’t follow any kind of pattern, so their documentation had to be consulted more often than if they were following some of the points below.
Because of that, some of them couldn’t even be considered RESTful APIs.&lt;/p&gt;

&lt;h2 id=&quot;what-is-rest&quot;&gt;What is REST?&lt;/h2&gt;

&lt;p&gt;REST means Representational State Transfer, and it is a software architectural style that defines a set of rules to be used when creating APIs. The APIs that conform to the REST style are called RESTful APIs.
First, you need to create one or more endpoints and expose them to your clients. Then, your clients are able to access them via HTTP using the correct HTTP methods.&lt;/p&gt;

&lt;h2 id=&quot;http-methods&quot;&gt;HTTP methods&lt;/h2&gt;

&lt;p&gt;The GET method happens when you open a website and it retrieves an image from the server, for example.
The POST method is often used on forms in websites, so your data is not sent in the URL (query string), but in the body of the request in this case. A RESTful API will use these two HTTP methods in their specific cases, but also some others like PUT and DELETE.&lt;/p&gt;

&lt;p&gt;Let’s see some points to take into consideration when designing an API.&lt;/p&gt;

&lt;h2 id=&quot;endpoints-should-be-nouns-not-verbs&quot;&gt;Endpoints should be nouns, not verbs&lt;/h2&gt;

&lt;p&gt;Think of a resource you have in the system. For example, you have a collection of employees. If your client wants to retrieve a list of employees, he doesn’t need to remember he has to access the URL /getEmployees. Or even worse, what if this method was called /getAllEmployees, or something else?
Then your client would have to remember if the method for deleting an employee was /removeEmployee or /deleteEmployee, for example, and use the HTTP Method POST for that?&lt;/p&gt;

&lt;p&gt;What if you could follow a pattern that would make much more sense for everybody? The HTTP protocol already comes with methods you can implement on your API for your client to interact with, so that makes it more intuitive.&lt;/p&gt;

&lt;p&gt;Let’s suppose you have a collection of employees in your database, and you want to return all of these employees.&lt;/p&gt;

&lt;p&gt;Then your client will make an HTTP GET request to the /employees endpoint for that.&lt;/p&gt;

&lt;p&gt;The most used form when naming an endpoint is the plural, so employees instead of employee in this example, as we’re returning a list with all the employees.&lt;/p&gt;

&lt;p&gt;It makes much more sense this way, but if you insist in using the singular form (I wouldn’t recommend), it’s important that you keep the same pattern also in the other endpoints you create.&lt;/p&gt;

&lt;h2 id=&quot;use-the-correct-http-methods&quot;&gt;Use the correct HTTP methods&lt;/h2&gt;

&lt;p&gt;Following our GET /employees example, which returns a list of employees, what happens if we need to create, update, delete an employee, or retrieve just a single employee? There are HTTP methods for each of these cases:&lt;/p&gt;

&lt;h3 id=&quot;post&quot;&gt;POST&lt;/h3&gt;

&lt;p&gt;The HTTP POST method is the one for when you want to create a resource, so the client will POST to /employees, sending the data in the request body instead of in the query string. If the request body contains any array, the best practice is to stringify that array. I’ve seen people sending something like this for the server to parse:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions[0].key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;optionSMS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions[0].value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions[1].key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;optionPhone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions[1].value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;instead of just simply sending a stringified array. This would be better sent like that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;optionSMS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;optionPhone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;or even better, use an object instead:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;termsOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;optionSMS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;optionPhone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;put&quot;&gt;PUT&lt;/h3&gt;

&lt;p&gt;The HTTP PUT method is the one you use when you want to update a resource. Before accessing this endpoint, the client will need to retrieve the employees, so it is able to know the id of the employee it wants to update.&lt;/p&gt;

&lt;p&gt;An endpoint with a PUT request is accessed by sending HTTP PUT to /employees/:id, being :id the id of the chosen employee. In the body of the request must be the object that represents the employee, with the changes the client needs to make.&lt;/p&gt;

&lt;p&gt;Ps: I’ve seen some (very few) APIs implementing the HTTP PATCH method to allow the partial update of a resource, so from experience, that’s not much used. If you need to update your resource only partially, though, then use PATCH instead.&lt;/p&gt;

&lt;h3 id=&quot;delete&quot;&gt;DELETE&lt;/h3&gt;

&lt;p&gt;The HTTP DELETE method is used when you want to delete a resource. Same way as with the PUT request, the client will need to have retrieved the employees before, so it is able to know the id of the employee it wants to delete.&lt;/p&gt;

&lt;p&gt;An endpoint with a DELETE request is accessed by sending HTTP DELETE to /employees/:id, being :id the id of the chosen employee. In this request, nothing goes in the body.&lt;/p&gt;

&lt;h3 id=&quot;get&quot;&gt;GET&lt;/h3&gt;

&lt;p&gt;Following this same pattern then, what if we want to get a single employee?
That’s right, we can call it the same way we did when using the PUT request, but changing the HTTP method from PUT to GET. So it would be GET /employees/:id, being :id the id of the chosen employee.&lt;/p&gt;

&lt;h3 id=&quot;safe-or-idempotent&quot;&gt;Safe or idempotent&lt;/h3&gt;

&lt;p&gt;A GET request is safe and idempotent. An HTTP method is considered safe when invoking that method doesn’t change the state of the resource. It doesn’t matter how many times you run GET /employees/1, for example, that will not change anything in that employee (it won’t be updated or deleted). When you get the same response no matter how many times you call the same endpoint, this means it is idempotent.&lt;/p&gt;

&lt;p&gt;A POST request, for example, is not safe nor idempotent, as it always creates something and the response is not the same every time you call it.&lt;/p&gt;

&lt;p&gt;PUT and DELETE requests are not safe but are idempotent.&lt;/p&gt;

&lt;p&gt;When any of these methods are consumed in your API, a response should be returned to the client, right? Let’s see more about the response then in the next topic.&lt;/p&gt;

&lt;h2 id=&quot;use-the-correct-http-codes&quot;&gt;Use the correct HTTP codes&lt;/h2&gt;

&lt;p&gt;The HTTP protocol has a series of codes that can be used for specific cases. Many people know already (even some who don’t work directly with IT), that 404 means NOT FOUND, so that’s a valid HTTP code that should be returned by our API when a resource is not found.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/404_room.jpg&quot; alt=&quot;404&quot; title=&quot;404&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The code 200 means OK and should be returned when the request happened successfully. It is mostly used for GET requests.&lt;/p&gt;

&lt;p&gt;When you have an endpoint that creates a resource, it should return the status 201, which means CREATED. Makes sense, right?&lt;/p&gt;

&lt;p&gt;If when trying to create a resource, the client sent malformed data to the endpoint, it should return 400, which means BAD REQUEST.&lt;/p&gt;

&lt;p&gt;401 is for when the client is unauthorized, 500 for when there’s an internal error in the server, and so on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/http_status.jpg&quot; alt=&quot;HTTP status&quot; title=&quot;HTTP status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, I’ve seen many APIs always returning 200 OK for everything. Please be nice and don’t do that, as there are codes widely used to explain each case.&lt;/p&gt;

&lt;p&gt;I’ve also seen some returning always 200 as the status code, but some different status code in the body of the response. That is redundant, as there’s already a place for the status code in the response, outside the body.
That would be less worse than returning always 200 as the status code but no other status anywhere at all, though.&lt;/p&gt;

&lt;p&gt;Another thing I have seen but wouldn’t recommend would be to create some random status code, like 398 or 521, for example and return it in the body. There are several status codes that exist and can be used already, so there’s no need to invent new ones. Let’s follow the correct HTTP status codes?&lt;/p&gt;

&lt;p&gt;If you wanna know more about other HTTP status codes, here are some links:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;If you like &lt;a href=&quot;https://http.cat/&quot;&gt;cats&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you prefer &lt;a href=&quot;https://httpstatusdogs.com/&quot;&gt;dogs&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you are a very serious person / would like a &lt;a href=&quot;https://httpstatuses.com/&quot;&gt;deeper explanation&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;document-your-endpoints&quot;&gt;Document your endpoints&lt;/h2&gt;

&lt;p&gt;If you are building an API, then it means you’ll be integrating one or more clients (frontend, mobile, desktop) with it soon. It is important to write a good documentation on how to use the endpoints you’re exposing with your API, so other developers that will implement these clients, or even you in the future, are able to know how to proceed.&lt;/p&gt;

&lt;p&gt;If you document well your API endpoints and how to work with them (and keep the documentation updated), developers will ask you less questions and your company will save time/money.&lt;/p&gt;

&lt;p&gt;I’ve written a &lt;a href=&quot;https://jonathas.com/documenting-your-nodejs-api-with-apidoc/&quot;&gt;post about APIDoc&lt;/a&gt; for that purpose, but lately I’ve been using &lt;a href=&quot;https://swagger.io/&quot;&gt;Swagger&lt;/a&gt; instead (I’ll write a post about it soon).&lt;/p&gt;

&lt;h2 id=&quot;version-your-api&quot;&gt;Version your API&lt;/h2&gt;

&lt;p&gt;Software is like a living organism, constantly changing and evolving. Your business and processes will change over time, requirements will change. If you work with Software Development, you need to learn to embrace change, so do yourself a favor and start versioning your API from the start.&lt;/p&gt;

&lt;p&gt;The best way to version an API is to include the version in the URL. For example, if your API base for the employees endpoint is /api/employees, change it to /api/v1/employees&lt;/p&gt;

&lt;p&gt;If the new requirements will introduce a breaking change, then you can create an endpoint under v2, as /api/v2/employees and keep it running side by side with the /api/v1/employees endpoint, so the clients that are using the v1 are not broken with the update.&lt;/p&gt;

&lt;h2 id=&quot;send-the-authorization-token-in-the-header-instead-of-in-the-url&quot;&gt;Send the authorization token in the header instead of in the URL&lt;/h2&gt;

&lt;p&gt;There’s something called “Authorization” header which you can use to send your token, then there’s no reason to expect that to be sent in the query string, even less in the request body.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/postman_authorization_header.png&quot; alt=&quot;Authorization header&quot; title=&quot;Authorization header&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;use-pagination&quot;&gt;Use pagination&lt;/h2&gt;

&lt;p&gt;Imagine you have thousands of employees in your collection, and the client consumes the GET /employees endpoint. If you have no pagination, then the response can take a really long time or even make the service unavailable if there are thousands of requests for the same endpoint without pagination.&lt;/p&gt;

&lt;p&gt;So always return paged results when there’s a list in your response. The API can accept parameters in the query string, such as page and limit, to inform which page and how many rows to be returned, for example.&lt;/p&gt;

&lt;h2 id=&quot;check-response-size-and-never-include-images&quot;&gt;Check response size and never include images&lt;/h2&gt;

&lt;p&gt;Watch out for the size of your responses. Imagine a response of some hundreds of Kb multiplied by thousands, when your API is live (think about the future). It’s better to keep it as slim as possible and not include unecessary data in it.&lt;/p&gt;

&lt;p&gt;Also, please use an object storage service like AWS S3 for images, for example, and return only the link for those images instead of the code of the image as base64. Yes, I’ve seen that happening as well and the response gets huge!&lt;/p&gt;

&lt;p&gt;You can easily check the size of the response on &lt;a href=&quot;https://www.getpostman.com/&quot;&gt;Postman&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/postman_response_size.png&quot; alt=&quot;Response size&quot; title=&quot;Response size&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;use-ssl-but-dont-configure-that-directly-in-your-api&quot;&gt;Use SSL, but don’t configure that directly in your API&lt;/h2&gt;

&lt;p&gt;SSL is very imporant in order for your API to be more secure. Your API doesn’t need to implement it, though. It’s better to leave this to your load balancer, reverse proxy or API Gateway, so your API can be started in cluster mode behind it and not be exposed to the outside world, receiving data via local network or unix sockets.&lt;/p&gt;

&lt;h2 id=&quot;always-validate-input&quot;&gt;Always validate input&lt;/h2&gt;

&lt;p&gt;When web development used to be all in one layer, with the frontend code built by the server and the HTML outputted as static content, it seems that there used to be more focus on input validation. Now that we have frontend and backend separated in two layers, many times we see the validation happening only in the frontend. That can obviously lead to security risks, so it’s very important to always validate user input in your API as well.&lt;/p&gt;

&lt;p&gt;Do not rely on the clients to implement validation, but implement it in the API as well, returning the correct HTTP status codes according to the case.&lt;/p&gt;

&lt;h2 id=&quot;test-your-api-with-automated-tests&quot;&gt;Test your API with automated tests&lt;/h2&gt;

&lt;p&gt;Improve the quality of your code and be sure your API is responding with the correct HTTP status codes for each case, by writing automated tests. When you have tests, it’s easier to add new features because you know better if your changes are breaking what is already working, for example. This can take longer in the beginning to implement, but will save you time in the future.&lt;/p&gt;

&lt;h2 id=&quot;kiss-keep-it-simple-stupid&quot;&gt;KISS (Keep it simple, stupid)&lt;/h2&gt;

&lt;p&gt;I love this software principle, because it avoids me doing unecessary stuff and makes the code easier to understand and evolve in the future.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Always return the same format of data. Stay consistent. Don’t have many different kinds of error objects, but stick to only one, so your clients know what to expect.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Do not include unecessary data in the response. Don’t expose more than you think needs exposing. If something is not needed for any client, then there’s no need to expose it.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Building an API sometimes can be a complicated task, but if we follow best practices and if we are consistent and keep it simple, a lot of time can be saved and future headache avoided.&lt;/p&gt;

&lt;p&gt;What are some of the best practices you’ve been using? If you have any tips or know of something I forgot to mention, leave a comment below :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/rest-api-best-practices/&quot;&gt;REST API Best Practices&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on January 27, 2019.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Caching API responses with Redis for faster endpoints]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/caching-api-responses-with-redis-for-faster-endpoints/" />
  <id>https://jonathas.com/caching-api-responses-with-redis-for-faster-endpoints</id>
  <published>2017-11-04T08:14:08+00:00</published>
  <updated>2017-11-04T08:14:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;Have you ever implemented an API endpoint that takes a while to respond? Maybe some seconds?&lt;/p&gt;

&lt;p&gt;Imagine when you have thousands of clients accessing your API at the same time and this response time increases because of that.&lt;/p&gt;

&lt;p&gt;An I/O call such as reading content from a file or a very complicated query in the database may take a while to complete. If this content doesn’t change at all, it’s much better then to leave it in the RAM, as accessing something there will be much faster than accessing something that is on disk. If it changes from time to time, a script to update the cache can be implemented to be called via a cron job when needed and the endpoint code can be refactored to fetch data only from the cache.&lt;/p&gt;

&lt;p&gt;In this post I expect you to have Node.js and Redis already configured.&lt;/p&gt;

&lt;h2 id=&quot;redis&quot;&gt;Redis&lt;/h2&gt;

&lt;p&gt;Taken from their website: Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries.&lt;/p&gt;

&lt;p&gt;As Redis is a database that stores everything in memory, the access to it is lightning fast! Much faster than accessing something from disk. This is perfect for what we need.&lt;/p&gt;

&lt;h2 id=&quot;use-case&quot;&gt;Use case&lt;/h2&gt;

&lt;p&gt;This code was developed for Node.js in TypeScript and uses Redis, Bluebird, async/await and a database file from &lt;a href=&quot;https://www.maxmind.com/en/geoip2-databases&quot;&gt;Maxmind&lt;/a&gt; for the lookup of IP related data (not included in the example).
This is an example of blocking the content or part of it according to the IP address that was detected, comparing it with the regions that are allowed to receive that response (geo-targeting).&lt;/p&gt;

&lt;p&gt;If the IP address is not from one of the configured regions, the code would filter the response and respond with only the parts of it that were configured for its region. In order to do that, we need to know all the regions in the country, so they can be compared with the region that was detected for the IP address of the request.&lt;/p&gt;

&lt;p&gt;Inside the config directory I have the configuration for Redis inside the cache.ts file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pino&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;redis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisifyAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cacheAddress&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CACHE_HOST&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cachePort&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CACHE_PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6379&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cachePort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cacheAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CACHE_AUTH&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CACHE_PASS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ECONNREFUSED&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Error: The server was not able to reach Redis. Maybe it&apos;s not running?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Below is an example of a class with a method to load the regions for a country.&lt;/p&gt;

&lt;p&gt;The regions need to be loaded in order to be compared with the region of the IP address. If it is not in the cache, the method loads the file from disk into cache, so for the next request it will be serving from cache already (The return statement makes sure the rest of the code is not executed if the content is cached).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../config/cache&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GeoIP&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filterAllowedInstances&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;IConfigResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ipAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;allowedInstances&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lookup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipLookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ipGeonameId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subdivisions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;geoname_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isIPAllowed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipGeonameId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;allowedInstances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;allowedInstances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Local ip&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Wrong database&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;Validation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logErrorInternal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GeoIP&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isIPAllowed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipGeonameId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instanceRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;instanceRegions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instanceRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// No region is configured, so allow all&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;countryRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Load all the regions for the country&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;countryRegions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loadRegionsForCountry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instanceRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filteredRegion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;countryRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;regionAll&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regionAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filteredRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filteredRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;geoname_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ipGeonameId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;loadRegionsForCountry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cachedRegions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cachedRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cachedRegions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;countries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/../config/country_region_data.json`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parsedCountries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;countries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;countryCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;COUNTRY_CODE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parsedCountries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;countryShortCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;countryCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toUpperCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;regions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If thousands of requests would need to have their IP addresses filtered against regions that were in a file on disk or in the database, then it would take a lot of file reading or calls to the database for that, so when the information comes from RAM it’s much faster.&lt;/p&gt;

&lt;p&gt;Redis methods usually don’t have the “Async” suffix and use callbacks, but because of bluebird’s promisifyAll method, these methods with this suffix were created with a Promise implementation.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This was an example of loading a file into Redis, so it can be served from cache instead of from the disk. You can use this idea for your project with some other cases, for example when viewing a statistics page, where the backend code needs to calculate many things before responding so the frontend can show it. In this case, the results could be cached on Redis and updated from time to time via a cron job. This way when the user opens the statistics page, the page would load instantly instead of making him wait every time for the calculation.&lt;/p&gt;

&lt;p&gt;Do you have another interesting example, any doubt or suggestion? Leave a comment :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/caching-api-responses-with-redis-for-faster-endpoints/&quot;&gt;Caching API responses with Redis for faster endpoints&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on November 04, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Token based authentication in Node.js with Passport, JWT and bcrypt]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt/" />
  <id>https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt</id>
  <published>2017-10-21T11:14:08+00:00</published>
  <updated>2017-10-21T11:14:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;When you develop an API, most of the times you’ll need part or all of its endpoints to require authentication. How to do that using Node.js?&lt;/p&gt;

&lt;p&gt;A combination of &lt;a href=&quot;http://passportjs.org/&quot;&gt;passport.js&lt;/a&gt; with JWT and bcrypt is one of the best ways to implement it. Time to go &lt;a href=&quot;https://www.jbspeakr.cc/purpose-jwt-stateless-authentication/&quot;&gt;stateless&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This post is based on the authentication implemented in the TODO List API project that you can find &lt;a href=&quot;https://github.com/jonathas/todo-api&quot;&gt;here&lt;/a&gt;. In this post, all the code is in TypeScript and I expect you to have Node.js and MongoDB already configured on your OS.&lt;/p&gt;

&lt;h2 id=&quot;what-is-passport&quot;&gt;What is Passport&lt;/h2&gt;

&lt;p&gt;As its website states: “Passport is an authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more”.&lt;/p&gt;

&lt;p&gt;So Passport allows us to integrate login strategies for many kinds of services and they have currently more than 300 strategies that can be just plugged in, ready to be used.&lt;/p&gt;

&lt;h2 id=&quot;what-is-jwt&quot;&gt;What is JWT&lt;/h2&gt;

&lt;p&gt;JWT (JSON Web Token) is an open, industry standard &lt;a href=&quot;https://tools.ietf.org/html/rfc7519&quot;&gt;RFC 7519&lt;/a&gt; method for representing claims securely between two parties. If we use Passport with a strategy for JWT, then it generates tokens that look for example like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A token contains its expiration date and can also contain data we need for checking the user.&lt;/p&gt;

&lt;p&gt;The token goes in the Authorization header of the HTTP method call, so the Passport middleware extracts and validates it.&lt;/p&gt;

&lt;h2 id=&quot;what-is-bcrypt&quot;&gt;What is bcrypt&lt;/h2&gt;

&lt;p&gt;In order to save the user in the database and later compare the password he/she enters for generating the authentication token, we need to encrypt his/her password, as it’s not safe to keep passwords with no encryption in the database. Also, it would be a joke to use md5 for that and &lt;a href=&quot;https://www.computerworld.com/article/3173616/security/the-sha1-hash-function-is-now-completely-unsafe.html&quot;&gt;sha1 recently became unsafe&lt;/a&gt;. Enter bcrypt.&lt;/p&gt;

&lt;p&gt;From Wikipedia: “bcrypt is a password hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher, and presented at USENIX in 1999”.
&lt;a href=&quot;https://medium.com/@danboterhoven/why-you-should-use-bcrypt-to-hash-passwords-af330100b861&quot;&gt;Here is more on why&lt;/a&gt; you should use bcrypt to hash passwords.&lt;/p&gt;

&lt;h2 id=&quot;configuring-the-project&quot;&gt;Configuring the project&lt;/h2&gt;

&lt;p&gt;In order to implement authentication in our API, we need to install the following packages:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i bcryptjs dotenv moment express bluebird body-parser mongoose express-validator@2.20.8 jwt-simple passport passport-jwt@2.2.1 &lt;span class=&quot;nt&quot;&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the dev dependencies (As I’ve been using TypeScript, I also install some types):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i @types/bcryptjs @types/jwt-simple @types/passport @types/passport-jwt @types/node @types/mongoose @types/superagent chai gulp gulp-apidoc gulp-sourcemaps gulp-typescript gulp-util istanbul@1.0.0-alpha.2 mocha@3.2.0 supertest tslint typescript vinyl-fs &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The passport-jwt package is Passport’s strategy for JWT.&lt;/p&gt;

&lt;p&gt;Inside the package.json file, in “scripts”, let’s leave it like this so istanbul runs integrated with mocha for generating the code coverage report:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;scripts&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pretest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;istanbul cover node_modules/mocha/bin/_mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;prestart&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;nodemon bin/server.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Inside the test directory, create a file called mocha.opts with the following content:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/test/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;_test.js
&lt;span class=&quot;nt&quot;&gt;--reporter&lt;/span&gt; spec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s copy these files and save them to the root of our project:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/.istanbul.yml&quot;&gt;.istanbul.yml&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/gulpfile.js&quot;&gt;gulpfile.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/tsconfig.json&quot;&gt;tsconfig.json&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/tslint.json&quot;&gt;tslint.json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the root of our project, let’s create a file called .env if it isn’t there yet. This file needs to have the JWT_SECRET environment variable, with a random hash we need to generate.
It’s very important not to repeat this hash among your projects, as it has to be something unique and secure, because this is what is used for generating and comparing the token.&lt;/p&gt;

&lt;p&gt;You can generate a hash using the &lt;a href=&quot;https://lastpass.com/generatepassword.php&quot;&gt;LastPass password generator&lt;/a&gt;, for example. I recommend you to use all kinds of characters and to have the length of at least 25. An example of an .env file then would be:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;API_BASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/api/v1/&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DEFAULT_TIMEZONE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Europe/Prague&apos;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ogA9ppB$S!dy!hu3Rauvg!L96&apos;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;DB_HOST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;127.0.0.1&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DB_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;27017&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DB_AUTH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;false&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DB_USER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DB_PASS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the config directory, let’s create a db.ts file for the database connection:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bluebird&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;todo_test&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;todo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;todo_dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dbAddress&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DB_HOST&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dbPort&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DB_PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;27017&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;useMongoClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DB_AUTH&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DB_USER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DB_PASS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`mongodb://&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ECONNREFUSED&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Error: The server was not able to reach MongoDB. Maybe it&apos;s not running?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;auth-tests&quot;&gt;Auth tests&lt;/h2&gt;

&lt;p&gt;Now let’s write some tests for what we need our authentication to do.&lt;/p&gt;

&lt;p&gt;Create a file named common.ts inside the test directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../models/user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../config/express&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;supertest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chai&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;chai&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;testUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;testuser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mytestpass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UserModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UserModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({});&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;testUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And then some tests for authentication in a file called auth_test.ts inside the test directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./common&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cleanCollection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../models/user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;# Auth&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;should retrieve the token&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cleanCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;should not login with the right user but wrong password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;testuser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;anythingGoesHere&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;should return invalid credentials error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;testuser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;anotherusername&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mypass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;should return token expired message&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTg5Mzk1MTksInVzZXJuYW1lIjoidGVzdHVzZXIifQ.FUJcVCzZTkjDr62MCJj5gvCFvmxewmz2jotiknuVbOg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Do the dishes&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Your token has expired. Please generate a new one&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we have tests for some things related to our JWT implementation. We need then to configure Express to use Passport as a middleware.&lt;/p&gt;

&lt;h2 id=&quot;auth-middleware&quot;&gt;Auth middleware&lt;/h2&gt;

&lt;p&gt;Inside the config directory, let’s create a file called express.ts to configure everything related to Express.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dotenv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dotenv&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../controllers/auth&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bodyParser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;body-parser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expressValidator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;express-validator&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./db&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bodyParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expressValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;TokenExpiredError&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Your token has expired. Please generate a new one&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../routes&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the above code, if our endpoint is “login”, it will not check for authentication (return next()), as that is the endpoint the clients will use for sending the username and password for generating the token they need for the other endpoints. Any other endpoint will go through the authenticate method inside our Auth controller, which is using Passport.&lt;/p&gt;

&lt;p&gt;As you see in the code, the Auth middleware must come before the routes are required, as the authentication process needs to happen before them.&lt;/p&gt;

&lt;h2 id=&quot;auth-controller-and-route&quot;&gt;Auth controller and route&lt;/h2&gt;

&lt;p&gt;Inside the controllers directory, let’s create a file called auth.ts with the following content:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jwt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;jwt-simple&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passport&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;passport&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;moment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Strategy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ExtractJwt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;passport-jwt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../models/user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Auth&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;initialize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;passport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;jwt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getStrategy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;authenticate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;jwt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;failWithError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;genToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;unix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jwt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;username&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;JWT &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;expires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;unix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_id&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checkBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Invalid username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checkBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Invalid password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validationErrors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;User not found&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;comparePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;genToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Invalid credentials&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getStrategy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Strategy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;secretOrKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;jwtFromRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ExtractJwt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromAuthHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;passReqToCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Strategy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;cm&quot;&gt;/* istanbul ignore next: passport response */&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;cm&quot;&gt;/* istanbul ignore next: passport response */&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;The user in the token was not found&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, here we set the Passport initialize method, configured the token generation with a validity of 7 days, implemented the login method to be used in the login endpoint and the strategy using JWT, extracting the token from the Authorization header.&lt;/p&gt;

&lt;p&gt;We need now a route for the login endpoint, for this to work. Let’s create a file called auth.ts inside the routes directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Auth&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../controllers/auth&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {post} /api/v1/login Generate a token
    * @apiVersion 1.0.0
    * @apiName Login
    * @apiGroup Auth
    * @apiPermission public
    * @apiDescription In order to generate a token, you will need to already have a user in the database.
    *
    * @apiParam (Request body) {String} username The username
    * @apiParam (Request body) {String} password The password
    *
    * @apiExample {js} Example usage:
    * const data = {
    *   &quot;username&quot;: &quot;test@email.com&quot;,
    *   &quot;password&quot;: &quot;yourpassword&quot;
    * };
    *
    * $http.post(url, data)
    *   .success((res) =&amp;gt; doSomethingHere())
    *   .error((err) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess {String} token The token that must be used to access the other endpoints
    * @apiSuccess {String} expires The expiration datetime (YYYY-MM-DDTHH:mm:ssZ)
    * @apiSuccess {String} user The user id
    *
    * @apiSuccessExample {json} Success response:
     *     HTTPS 200 OK
     *     {
     *      &quot;token&quot;: &quot;JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 ... and the rest of the token here&quot;,
     *      &quot;expires&quot;: &quot;2017-10-28T14:50:17+00:00&quot;,
     *      &quot;user&quot;: &quot;57e12cab65c0c892381b8b44&quot;
     *    }
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This example is ready to be used with apiDoc for generating the API documentation, explained in my &lt;a href=&quot;https://jonathas.com/documenting-your-nodejs-api-with-apidoc/&quot;&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also need an index.ts file inside the routes directory, in case you don’t have it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Require the routes files in the routes directory&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./auth&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Welcome to the TODO API. Check the documentation for the list of available endpoints&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// If no route is matched by now, it must be a 404&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Endpoint not found&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Unexpected error: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now that we have the project configured with the packages, the auth tests, auth middleware, auth controller and routes, we need to implement the bcrypt functions for checking the user password before generating the token.&lt;/p&gt;

&lt;h2 id=&quot;user-model&quot;&gt;User model&lt;/h2&gt;

&lt;p&gt;Let’s create the file called user.ts inside the models directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bcryptjs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Document&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;comparePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;candidatePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;timestamps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;updatedAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;comparePassword&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;candidatePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;candidatePassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;IUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cleanCollection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here we added bcrypt methods to hash a new password when saving or updating a user and implemented a method to compare the password the user will post to the login endpoint when he wants to generate a token. This comparePassword method is used in the Auth controller.&lt;/p&gt;

&lt;p&gt;How to create a user for the first login?&lt;/p&gt;

&lt;p&gt;The common.ts file inside the test directory does that using the User model we just created, so when our tests run and try to login, if no user exists, it creates a user before accessing the login endpoint for posting the credentials. For a production case you can create a users controller inside the controllers directory or &lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/controllers/users.ts&quot;&gt;copy the one I have here&lt;/a&gt;, then create user routes inside the routes directory or &lt;a href=&quot;https://github.com/jonathas/todo-api/blob/master/api/routes/users.ts&quot;&gt;copy the one I have here&lt;/a&gt; to make the endpoints available for a web panel to create new users, for example. Or you could create a script that runs in the CLI and uses bcrypt and the User model to create new users in the database. I will not cover user management in this post, as the focus here is the authentication part.&lt;/p&gt;

&lt;h2 id=&quot;the-server-file&quot;&gt;The server file&lt;/h2&gt;

&lt;p&gt;We need to run our Express config and create a server with it. Let’s create a server.ts file in the root of our project:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./config/express&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Express server listening on port &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.\nEnvironment: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;running-the-tests&quot;&gt;Running the tests&lt;/h2&gt;

&lt;p&gt;Now that we configured everything that is used for requiring authentication for our endpoints, we can run the tests and check if the code is working (be sure to have MongoDB running before):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/auth_tests.jpg&quot; alt=&quot;Auth tests&quot; title=&quot;Auth tests&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;calling-an-endpoint-from-a-client&quot;&gt;Calling an endpoint from a client&lt;/h2&gt;

&lt;p&gt;After you integrate the authentication on your API, you can use your client (javascript on the browser, mobile, desktop, postman, etc) to call the login endpoint and get the token for your user.&lt;/p&gt;

&lt;p&gt;Run the project with node, nodemon or pm2 (edit that in the package.json file):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;then:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/login.png&quot; alt=&quot;Login&quot; title=&quot;Login&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The response comes with “JWT” before the actual token. This is the token bearer and it is required when sending the token in the Authorization header.&lt;/p&gt;

&lt;p&gt;If you are using the TODO List API example, you can use the tasks endpoint sending the Authorization header using your client (&lt;a href=&quot;https://www.getpostman.com/&quot;&gt;Postman&lt;/a&gt; in the example), with the token generated in the previous step:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/using_authorization.png&quot; alt=&quot;Using Authorization&quot; title=&quot;Using Authorization&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this post we saw how to implement authentication with Passport, JWT and bcrypt, integrating it to our existing API, then calling it from a client. As our authentication is stateless, when we need to deploy the API behind a load balancer, we don’t need to have sticky sessions and the authentication process is much simpler and easily scalable.&lt;/p&gt;

&lt;p&gt;If you have any suggestion, doubt or use something else, leave a comment below :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt/&quot;&gt;Token based authentication in Node.js with Passport, JWT and bcrypt&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on October 21, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Documenting your Node.js API with apiDoc]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/documenting-your-nodejs-api-with-apidoc/" />
  <id>https://jonathas.com/documenting-your-nodejs-api-with-apidoc</id>
  <published>2017-10-14T09:10:08+00:00</published>
  <updated>2017-10-14T09:10:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;When you are developing an API and one or more developers (frontend, desktop, mobile, etc) will have to integrate their code with it, it’s very important to have it well documented so they know what and how they can use.&lt;/p&gt;

&lt;p&gt;For that, in Node.js projects I’ve been using &lt;a href=&quot;http://apidocjs.com/&quot;&gt;apiDoc&lt;/a&gt;, as it is able to generate documentation in HTML from annotations in the source code.&lt;/p&gt;

&lt;p&gt;For this post, I’ll use the TODO List API I developed as an example, once more. So you can clone or download it from &lt;a href=&quot;https://github.com/jonathas/todo-api&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;routes-and-annotations&quot;&gt;Routes and annotations&lt;/h2&gt;

&lt;p&gt;On my post about &lt;a href=&quot;https://jonathas.com/tests-and-code-coverage-on-node-using-typescript-with-mocha-and-istanbul/&quot;&gt;tests with mocha and code coverage with istanbul&lt;/a&gt;, I showed the example of the Task endpoints in our TODO List API:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../controllers/tasks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This represents all our endpoints related to tasks in the system. How can we use them? What should the developers that will consume the API send to each of these endpoints?&lt;/p&gt;

&lt;p&gt;So far there is no way for them to know other than looking at the code, which shouldn’t be needed.&lt;/p&gt;

&lt;p&gt;Because of apiDoc, we can achieve that with annotations. The way I configure it, is by writing them before each endpoint configured in the files inside the routes directory. If you are not sure of what I’m talking about, &lt;a href=&quot;https://jonathas.com/tests-and-code-coverage-on-node-using-typescript-with-mocha-and-istanbul/&quot;&gt;check here&lt;/a&gt; when I mentioned how I configure and organize my Node.js projects.&lt;/p&gt;

&lt;p&gt;With annotations, our Task endpoints (inside routes/tasks.ts) would look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../controllers/tasks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;API_BASE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {post} /api/v1/tasks Create a task
    * @apiVersion 1.0.0
    * @apiName Create
    * @apiGroup Task
    * @apiPermission authenticated user
    *
    * @apiParam (Request body) {String} name The task name
    *
    * @apiExample {js} Example usage:
    * const data = {
    *   &quot;name&quot;: &quot;Do the dishes&quot;
    * }
    *
    * $http.defaults.headers.common[&quot;Authorization&quot;] = token;
    * $http.post(url, data)
    *   .success((res, status) =&amp;gt; doSomethingHere())
    *   .error((err, status) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess (Success 201) {String} message Task saved successfully!
    * @apiSuccess (Success 201) {String} id The campaign id
    *
    * @apiSuccessExample {json} Success response:
    *     HTTPS 201 OK
    *     {
    *      &quot;message&quot;: &quot;Task saved successfully!&quot;,
    *      &quot;id&quot;: &quot;57e903941ca43a5f0805ba5a&quot;
    *    }
    *
    * @apiUse UnauthorizedError
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {delete} /api/v1/tasks/:id Delete a task
    * @apiVersion 1.0.0
    * @apiName Delete
    * @apiGroup Task
    * @apiPermission authenticated user
    *
    * @apiParam {String} id The task id
    *
    * @apiExample {js} Example usage:
    * $http.defaults.headers.common[&quot;Authorization&quot;] = token;
    * $http.delete(url)
    *   .success((res, status) =&amp;gt; doSomethingHere())
    *   .error((err, status) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess {String} message Task deleted successfully!
    *
    * @apiSuccessExample {json} Success response:
     *     HTTPS 200 OK
     *     {
     *      &quot;message&quot;: &quot;Task deleted successfully!&quot;
     *    }
     *
     * @apiUse UnauthorizedError
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {get} /api/v1/tasks/:id Retrieve a task
    * @apiVersion 1.0.0
    * @apiName GetOne
    * @apiGroup Task
    * @apiPermission authenticated user
    *
    * @apiParam {String} id The task id
    *
    * @apiExample {js} Example usage:
    * $http.defaults.headers.common[&quot;Authorization&quot;] = token;
    * $http.get(url)
    *   .success((res, status) =&amp;gt; doSomethingHere())
    *   .error((err, status) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess {String} _id The task id
    * @apiSuccess {String} name The task name
    *
    * @apiSuccessExample {json} Success response:
     *     HTTPS 200 OK
     *     {
     *        &quot;_id&quot;: &quot;57e8e94ea06a0c473bac50cc&quot;,
     *        &quot;name&quot;: &quot;Do the disehs&quot;,
     *        &quot;__v&quot;: 0
     *      }
     *
     * @apiUse UnauthorizedError
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {get} /api/v1/tasks Retrieve all tasks
    * @apiVersion 1.0.0
    * @apiName GetAll
    * @apiGroup Task
    * @apiPermission authenticated user
    *
    * @apiExample {js} Example usage:
    * $http.defaults.headers.common[&quot;Authorization&quot;] = token;
    * $http.get(url)
    *   .success((res, status) =&amp;gt; doSomethingHere())
    *   .error((err, status) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess {String} _id The task id
    * @apiSuccess {String} name The task name
    *
    * @apiSuccessExample {json} Success response:
    *     HTTPS 200 OK
    *     [{
    *       &quot;_id&quot;: &quot;57e8e94ea06a0c473bac50cc&quot;,
    *       &quot;name&quot;: &quot;Do the disehs&quot;
    *      },
    *      {
    *       &quot;_id&quot;: &quot;57e903941ca43a5f0805ba5a&quot;,
    *       &quot;name&quot;: &quot;Take out the trash&quot;
    *     }]
    *
    * @apiUse UnauthorizedError
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
    * @api {put} /api/v1/tasks/:id Update a task
    * @apiVersion 1.0.0
    * @apiName Update
    * @apiGroup Task
    * @apiPermission authenticated user
    *
    * @apiParam {String} id The task id
    *
    * @apiParam (Request body) {String} name The task name
    *
    * @apiExample {js} Example usage:
    * const data = {
    *   &quot;name&quot;: &quot;Run in the park&quot;
    * }
    *
    * $http.defaults.headers.common[&quot;Authorization&quot;] = token;
    * $http.put(url, data)
    *   .success((res, status) =&amp;gt; doSomethingHere())
    *   .error((err, status) =&amp;gt; doSomethingHere());
    *
    * @apiSuccess {String} message Task updated successfully!
    *
    * @apiSuccessExample {json} Success response:
     *     HTTPS 200 OK
     *     {
     *      &quot;message&quot;: &quot;Task updated successfully!&quot;
     *    }
     *
     * @apiUse UnauthorizedError
    */&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/:id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, we have the HTTP method type (post, put, get, delete), the endpoint address, the api version, the kind of permission it needs, the params it requires, the response and the error if the user is not authorized.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;http://apidocjs.com/&quot;&gt;official website&lt;/a&gt; you can check the documentation and available params for the annotations.&lt;/p&gt;

&lt;p&gt;Where is this UnauthorizedError coming from?&lt;/p&gt;

&lt;h2 id=&quot;apidoc-settings&quot;&gt;apiDoc settings&lt;/h2&gt;

&lt;p&gt;There are some settings that can be done for apiDoc and this UnauthorizedError is the one I usually implement.&lt;/p&gt;

&lt;p&gt;Create a file called _apidoc.js inside the routes directory with the following content:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// General apiDoc documentation blocks and old history blocks.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Current Success.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Current Errors.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Current Permissions.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/**
 * @apiDefine UnauthorizedError
 * @apiVersion 1.0.0
 *
 * @apiError Unauthorized Only authenticated users can access the endpoint.
 *
 * @apiErrorExample  Unauthorized response:
 *     HTTP 401 Unauthorized
 *     {
 *       &quot;message&quot;: &quot;Invalid credentials&quot;
 *     }
 */&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// History.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I also create another file, also inside the routes directory, called apidoc.json&lt;/p&gt;

&lt;p&gt;This file contains the following content (example):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot; data-lang=&quot;typescript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Test API - This is my API&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1.0.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;This is my very powerful API&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Test API - This is my API&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://testapi.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These two files will be used by apiDoc when generating the documentation.&lt;/p&gt;

&lt;h2 id=&quot;generating-the-documentation&quot;&gt;Generating the documentation&lt;/h2&gt;

&lt;p&gt;After writing annotations for every endpoint and configuring our project, we need to configure a task to generate the documentation.&lt;/p&gt;

&lt;p&gt;The way I do that is using gulp. Install the required dependencies:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm i gulp gulp-apidoc &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then create a file called gulpfile.js in the project’s root directory. If it’s there already, just add the parts related to apiDoc to it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;apidoc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gulp-apidoc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;apidoc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;apidoc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./routes&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../docs/apidoc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./routes/**&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;apidoc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can change the “dest” there to another directory that suits you better. That’s where you want the output to be generated to.&lt;/p&gt;

&lt;p&gt;Now all you need to do is to run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gulp apidoc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, you just need to open the index.html file inside the directory you configured in the “dest” above and you’ll see something like this (Click on the image below to enlarge):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/apidoc.png&quot;&gt;&lt;img src=&quot;/images/posts/thumbs/apidoc_tn.jpg&quot; alt=&quot;apidoc&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the other developers can generate the same with gulp as well or you can even serve this generated directory via Nginx, for example.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this post we saw how to document our APIs with annotations and generate the HTML for them using apiDoc.&lt;/p&gt;

&lt;p&gt;Do you use another software for documenting your APIs or do you use apiDoc in a different way? Leave a comment below!&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/documenting-your-nodejs-api-with-apidoc/&quot;&gt;Documenting your Node.js API with apiDoc&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on October 14, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Backup your MongoDB databases to Amazon S3]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/backup-your-mongodb-databases-to-amazon-s3/" />
  <id>https://jonathas.com/backup-your-mongodb-databases-to-amazon-s3</id>
  <published>2017-10-07T09:55:08+00:00</published>
  <updated>2017-10-07T09:55:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;I’ve developed a backup script for MongoDB using TypeScript. It’s available as GPL3 &lt;a href=&quot;https://github.com/jonathas/mongo-s3-backup&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you download or clone the repository, you can choose to run it with Docker (so cron is already configured) or run it without Docker (If you want to configure &lt;a href=&quot;https://corenominal.org/2016/05/12/howto-setup-a-crontab-file/&quot;&gt;cron on your host OS by yourself&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The cron example there is the same I wrote about on my &lt;a href=&quot;https://jonathas.com/scheduling-tasks-with-cron-on-docker/&quot;&gt;previous post about cron with Docker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In any case, you’ll need to have Node.js and yarn installed.&lt;/p&gt;

&lt;p&gt;This script generates a dump from your MongoDB databases, compresses this dump as tar.bz2, generates a hash file for integrity check and then uploads both files to your Amazon S3 bucket.&lt;/p&gt;

&lt;h2 id=&quot;configuring&quot;&gt;Configuring&lt;/h2&gt;

&lt;p&gt;I expect you to have your Amazon Web Services (AWS) account ready and know your access key and secret access key before, also to have a bucket created on S3 in order to store the backup files.&lt;/p&gt;

&lt;p&gt;In order to configure the backup script, you need to open the .env file and enter your data there, such as your timezone and AWS credentials.&lt;/p&gt;

&lt;p&gt;Cron is configured to run the backup script every day at midnight. If you want to change the time there, change the file called root inside /infra/cron. If you need to change the timezone of the cron container, then open /infra/cron/Dockerfile&lt;/p&gt;

&lt;h2 id=&quot;building&quot;&gt;Building&lt;/h2&gt;

&lt;p&gt;In order to build the script and start the cron process with Docker, run the following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will run the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install the required packages&lt;/li&gt;
  &lt;li&gt;Run the build script&lt;/li&gt;
  &lt;li&gt;Build the cron Docker image&lt;/li&gt;
  &lt;li&gt;Start the cron Docker container&lt;/li&gt;
  &lt;li&gt;Run all the tests with npm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you chose not to use Docker, but configure cron yourself on your host OS, then run this instead:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make no-docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will run the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install the required packages&lt;/li&gt;
  &lt;li&gt;Run all the tests with npm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;enabling-daemon-on-system-startup&quot;&gt;Enabling daemon on system startup&lt;/h2&gt;

&lt;p&gt;In order to start this container automatically if your server reboots:&lt;/p&gt;

&lt;p&gt;Change the path inside the file infra/host/etc/systemd/system/mongo-backup.service to be the path where you put the mongo-s3-backup source code.&lt;/p&gt;

&lt;p&gt;As root, copy this file to /etc/systemd/system in your server&lt;/p&gt;

&lt;p&gt;Reload the daemons:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enable and start it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;mongo-backup
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl start mongo-backup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Today we saw how to configure and run the MongoDB backup script.&lt;/p&gt;

&lt;p&gt;If you have any idea or suggestion, leave a comment or send a pull request.&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/backup-your-mongodb-databases-to-amazon-s3/&quot;&gt;Backup your MongoDB databases to Amazon S3&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on October 07, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[Scheduling tasks with cron on Docker]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/scheduling-tasks-with-cron-on-docker/" />
  <id>https://jonathas.com/scheduling-tasks-with-cron-on-docker</id>
  <published>2017-09-30T07:30:10+00:00</published>
  <updated>2017-09-30T07:30:10+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;When you need to setup a cron job, you can do it using Docker. The best way to do it with Docker is using an Alpine Linux image, as it has only 5MB initial size.&lt;/p&gt;

&lt;p&gt;Don’t know what is Docker? Check more about it &lt;a href=&quot;https://www.docker.com/what-docker&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post I expect you to already have Docker and docker-compose installed on your OS.&lt;/p&gt;

&lt;h2 id=&quot;creating-the-image&quot;&gt;Creating the image&lt;/h2&gt;

&lt;p&gt;In the case below I needed an Alpine Linux container with Node.js inside, as I wanted cron to run a Node.js script.&lt;/p&gt;

&lt;p&gt;First we create a file called Dockerfile with the following content (yes, with no file extension):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;FROM node:8-alpine
MAINTAINER Jonathas Ribeiro &amp;lt;contact@jonathas.com&amp;gt;

RUN apk update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add tzdata &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /usr/share/zoneinfo/Europe/Prague /etc/localtime &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Europe/Prague&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/timezone &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
    apk del tzdata &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; /var/cache/apk/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;

CMD &lt;span class=&quot;nb&quot;&gt;chown &lt;/span&gt;root:root /etc/crontabs/root &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; /usr/sbin/crond &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s save this file inside a directory called cron.&lt;/p&gt;

&lt;p&gt;The FROM command tells Docker which image we want to use for building our image.&lt;/p&gt;

&lt;p&gt;The MAINTAINER command tells Docker who is maintaining this Dockerfile.&lt;/p&gt;

&lt;p&gt;The RUN command tells Docker which commands to run on image creation.&lt;/p&gt;

&lt;p&gt;It’s very important for your Dockerfile to have the least number of commands possible, as another RUN command for example, would create another layer in the resulting image. That’s why we concatenate the commands with &amp;amp;&amp;amp; instead of writing a RUN command for each of them. Simplicity should be the goal.&lt;/p&gt;

&lt;p&gt;In the example, I set the timezone to Prague (my current timezone), but you can change it to yours instead, of course.&lt;/p&gt;

&lt;p&gt;The CMD command tells Docker what will run inside the container that will be created from this image, which is cron in this case. Exactly what we need.&lt;/p&gt;

&lt;h2 id=&quot;configuring-cron&quot;&gt;Configuring cron&lt;/h2&gt;

&lt;p&gt;Now we need to configure what this image will run using cron.&lt;/p&gt;

&lt;p&gt;I created this silly example below just to illustrate what could be done.&lt;/p&gt;

&lt;p&gt;Save the content below to a file called root in the same directory where you saved the Dockerfile (the cron directory you created before).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# ┌───────────── minute (0 - 59)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ ┌───────────── hour (0 - 23)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ ┌───────────── day of month (1 - 31)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ │ ┌───────────── month (1 - 12)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ │ │ │                                       7 is also Sunday on some systems)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ │ │ │&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# │ │ │ │ │&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# * * * * *  command to execute&lt;/span&gt;

0 7,19 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; /usr/local/bin/node /home/node/hello.js &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/crontest/hello.log 2&amp;gt;&amp;amp;1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now create a directory called scripts on the same level of the cron directory, not inside of it.&lt;/p&gt;

&lt;p&gt;Inside this scripts directory, create our hello.js script with the following content:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example I told cron to execute a JavaScript file using the Node.js executable that comes inside the container and output the response to a hello.log file.
This cron command, as you can see in the explanation in the comments, will run every day at 7:00 and 19:00.&lt;/p&gt;

&lt;p&gt;Ok, so how to add this to our container?&lt;/p&gt;

&lt;h2 id=&quot;docker-compose&quot;&gt;Docker-compose&lt;/h2&gt;

&lt;p&gt;I like using docker-compose as it enables me to orchestrate many containers in a simple way, using only one docker-compose.yml file.&lt;/p&gt;

&lt;p&gt;With docker-compose you can for example create a file with all the containers you need and then run docker-compose up for it to start all of them at once, instead of dealing with docker run and passing all parameters every time, etc.&lt;/p&gt;

&lt;p&gt;Let’s create a file called docker-compose.yml with the following content in same level as our scripts and cron directories:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cron&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;crontest&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/log/crontest:/var/log/crontest&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cron/root:/etc/crontabs/root&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sripts:/home/node&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Spacing is very important here. Visual Studio Code can help you with that, as it detects yml files.&lt;/p&gt;

&lt;p&gt;The cron directory is informed in the build atribute there, so docker-compose will build the image using the Dockerfile we created inside the cron directory.&lt;/p&gt;

&lt;p&gt;In “volumes”, the crontest directory inside your host OS is being mounted inside the container using the same path there, so when our script outputs to hello.log as configured above, the log will be easily accessible from the host OS instead of having to enter the cron container to read it. If this directory doesn’t exist on your host OS, docker-compose will create it.&lt;/p&gt;

&lt;p&gt;For the next lines configured inside “volumes”, the root file we created inside the cront directory is being mounted inside the container in the /etc/crontabs/root path. On the 3rd line, everything from the scripts directory is being mounted inside /home/node inside the container, as this is the home directory this Node.js container comes with.&lt;/p&gt;

&lt;p&gt;Instead of mounting these files inside the container using docker-compose, you could have added them on image creation using the &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/#add&quot;&gt;ADD command&lt;/a&gt;, but then you’d have to rebuild the image if you needed to change any of these files and the resulting image would also have more layers. Because of that, I prefer to mount them from the host OS instead, so if I need to modify any of these files, they are not builtin with the image in any way.&lt;/p&gt;

&lt;p&gt;After saving the docker-compose.yml file, you can run the command to build our docker image and start our container from it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way all the commands will show up on your terminal and you won’t be able to use this terminal for anything else. If you add -d in the end of the command, then it runs as a daemon and your terminal will be free, but you wont see any output about the image build process or when it’s running.&lt;/p&gt;

&lt;p&gt;If you run docker-compose witout the daemon option, you just need to use Ctrl + c when you want to stop the containers specified in your docker-compose.yml file. If you run as a daemon, you can run docker-compose stop to stop the containers and then if you want to remove them you can run docker-compose rm&lt;/p&gt;

&lt;p&gt;In order to list the current containers:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker ps &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the images:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker images
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;running-on-startup&quot;&gt;Running on startup&lt;/h2&gt;

&lt;p&gt;After creating the image and checking that everything works as expected, we need to create a startup script so if our OS gets restarted the cron container runs automatically.&lt;/p&gt;

&lt;p&gt;The servers I configure are usually running &lt;a href=&quot;https://www.debian.org/&quot;&gt;Debian Linux&lt;/a&gt;, my personal notebook runs &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; and at work I run &lt;a href=&quot;https://manjaro.org/&quot;&gt;Manjaro&lt;/a&gt;. All these Linux distributions come with systemd, so I’ll show an example of how the startup could be configured for that.&lt;/p&gt;

&lt;p&gt;Let’s save a file called docker-infra.service inside /etc/systemd/system/ with the following content:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Unit]
&lt;span class=&quot;nv&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Docker infra
&lt;span class=&quot;nv&quot;&gt;Requires&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;docker.service
&lt;span class=&quot;nv&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;docker.service

&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Service]
&lt;span class=&quot;nv&quot;&gt;Restart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;always
&lt;span class=&quot;nv&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/local/bin/docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /home/jon/crontest/docker-compose.yml up
&lt;span class=&quot;nv&quot;&gt;ExecStop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/local/bin/docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /home/jon/crontest/docker-compose.yml stop

&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Install]
&lt;span class=&quot;nv&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;default.target
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You need to change the path to your docker-compose.yml file to the one where you saved yours inside your server, of course.&lt;/p&gt;

&lt;p&gt;Then let’s reload the system daemons:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and enable our newly created daemon to run on system startup:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;docker-infra
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you need to stop or check the status of the daemon that controls our docker container, just change the enable in the systemctl command above to stop or status and that’s it.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this post we saw how to create a Docker image to use as cron to run Node.js scripts. Then we saw how to use docker-compose and create a daemon for it to run on system startup.&lt;/p&gt;

&lt;p&gt;If you have any suggestion or doubt, let me know in the comments below :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/scheduling-tasks-with-cron-on-docker/&quot;&gt;Scheduling tasks with cron on Docker&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on September 30, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[My favorite fitness apps for Android]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/my-favorite-fitness-apps-for-android/" />
  <id>https://jonathas.com/my-favorite-fitness-apps-for-android</id>
  <published>2017-09-24T08:20:08+00:00</published>
  <updated>2017-09-24T08:20:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;Some apps are very useful when you decide to keep track of your fitness activities and habits.&lt;/p&gt;

&lt;p&gt;Here I list some apps I have used and others I still use and recommend.&lt;/p&gt;

&lt;h2 id=&quot;runkeeper&quot;&gt;Runkeeper&lt;/h2&gt;

&lt;p&gt;This is one of the most well known apps out there. If you like taking long walks, running or biking, it’s perfect for that. It tracks your speed, pace and distance (using your phone’s GPS). In the end of your activity it shows you a map with where you’ve been through and it also allows you to add a photo to your activity so you can create good memories for yourself to look at in the future or for your friends. Talking about friends, it’s even better when you have many friends added there, so you can compete with each other and set goals in the app and website.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/runkeeper_00.png&quot; alt=&quot;RunKeeper&quot; title=&quot;RunKeeper&quot; /&gt; &lt;img src=&quot;/images/posts/runkeeper_01.png&quot; alt=&quot;RunKeeper&quot; title=&quot;RunKeeper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.fitnesskeeper.runkeeper.pro&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;jefit&quot;&gt;Jefit&lt;/h2&gt;

&lt;p&gt;In my opinion this is the best workout app ever. I always use it when working out at the gym, as I’ve saved all my trainings and routines there. It allows you to create new workout plans by days and body parts, it has several registered exercises you can add to your routines and when you start a workout, you can log the weights you’re using so you keep track of your progress. After each exercise it starts a counter for your rest time (60 seconds or 30 seconds, for example) before it shows you the next exercise on your list. The free version comes with ads but the Pro version is worth it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/jefit_00.jpg&quot; alt=&quot;Jefit&quot; title=&quot;Jefit&quot; /&gt; &lt;img src=&quot;/images/posts/jefit_01.jpg&quot; alt=&quot;Jefit&quot; title=&quot;Jefit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=je.fit&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;hiit-interval-training-timer&quot;&gt;HIIT Interval Training Timer&lt;/h2&gt;

&lt;p&gt;If you need to burn fat, HIIT (High-intensity interval training) is the best way there is. Many articles and studies say it’s better than steady state cardio (running having distance as a goal, for example). On a HIIT workout you run as fast as you can for some seconds, then go slowly, then repeat that for some series. This app helps with that by counting the intervals and sounding a whistle when they begin.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hiit_00.jpg&quot; alt=&quot;HIIT&quot; title=&quot;HIIT&quot; /&gt; &lt;img src=&quot;/images/posts/hiit_01.jpg&quot; alt=&quot;HIIT&quot; title=&quot;HIIT&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.ihunda.android.hiit&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;fitocracy&quot;&gt;Fitocracy&lt;/h2&gt;

&lt;p&gt;As their description states: “Track your workouts, earn points, unlock achievements, beat quests, and slay the laziness dragon”.
This app and website represent a community of people who are working hard every day to level up. Fitocracy works with a lot of gamification, so you can earn badges and level up according to your points. It’s great for when you need some extra motivation for your achievements. It also has many challenges you can complete in order to earn more badges and points. It integrates with Runkeeper but, unfortunately, not with Jefit. I’ve always used Jefit, so I had to log the exercises to Fitocracy manually, back when I was using it more often.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/fitocracy_00.jpg&quot; alt=&quot;Fitocracy&quot; title=&quot;Fitocracy&quot; /&gt; &lt;img src=&quot;/images/posts/fitocracy_01.jpg&quot; alt=&quot;Fitocracy&quot; title=&quot;Fitocracy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.fitocracy.app&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;stretch&quot;&gt;Stretch&lt;/h2&gt;

&lt;p&gt;Stretching is always important and it brings many benefits to your health. This app has many stretching poses arranged in categories for training various body parts. You can pick a few stretching poses as a daily routine.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/stretching.jpg&quot; alt=&quot;Stretch&quot; title=&quot;Stretch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=imoblife.stretchexercises.lite&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;muay-thai&quot;&gt;Muay Thai&lt;/h2&gt;

&lt;p&gt;I don’t believe in this “learn Muay Thai by yourself” crap, but this is a good app if you need some suggestions for a trainig with a punching bag or with a friend, also for some shadow boxing.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/muaythai.jpg&quot; alt=&quot;Muay Thai&quot; title=&quot;Muay Thai&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.fitivity.muay_thai_training&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;hydro&quot;&gt;Hydro&lt;/h2&gt;

&lt;p&gt;It is very important to drink a certain amount of water every day and it even helps you to lose weight. This app allows you to set hourly reminders so you are able to achieve your goal for the day. If you do not drink the correct amount of water, this is the perfect application, which will restore your healthy habits and will help you care for proper hydration.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hydro.jpg&quot; alt=&quot;Hydro&quot; title=&quot;Hydro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.freshware.hydro&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;myfitnesspal&quot;&gt;MyFitnessPal&lt;/h2&gt;

&lt;p&gt;This is a great app for tracking what you eat and counting calories intake. It comes with a barcode scanner and it has a huge database of products, so it will almost certainly identify what you ate if you scan its barcode. You just need to put there the product and the amount and it will count how many calories, protein, carbs, etc. I tested it with products in Brazil and products in Czech Republic and the barcode scan worked very well, identifying many products. It also has a log for how much water you drink during your day and you can integrate the app with Google Fit or with Fitbit so it sends the calories you log to these apps.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/myfitnesspal_00.png&quot; alt=&quot;MyFitnessPal&quot; title=&quot;MyFitnessPal&quot; /&gt; &lt;img src=&quot;/images/posts/myfitnesspal_01.png&quot; alt=&quot;MyFitnessPal&quot; title=&quot;MyFitnessPal&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.myfitnesspal.android&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;google-fit&quot;&gt;Google Fit&lt;/h2&gt;

&lt;p&gt;This app allows you to track your walking, running or biking activities using your phone or Android wear. It shows real time stats so you can see your speed, pace, distance, etc. It can be integrated with Runkeeper, MyFitnessPal and others. You can set goals based on steps, time, distance, calories burned and reach your fitness goals. Last time I used it was long ago, so it seems to have evolved pretty well. Nowadays I don’t use it anymore as I use Fitbit instead.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/googlefit_00.jpg&quot; alt=&quot;Google Fit&quot; title=&quot;Google Fit&quot; /&gt; &lt;img src=&quot;/images/posts/googlefit_01.jpg&quot; alt=&quot;Google Fit&quot; title=&quot;Google Fit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.apps.fitness&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;fitbit&quot;&gt;Fitbit&lt;/h2&gt;

&lt;p&gt;I just bought a Fitbit Charge 2 band and I’m really enjoying it. This app works integrated with your fitness band by Fitbit and it tracks your steps, distance (using your phone’s GPS), calories burned, allows you to set daily goals and has some gamification as well, with badges. It also tracks how well you sleep so you can set goals to sleep better. You can also tack your weight changes, body fat, heart rate, water and food intake. On the fitness band you can check the current time, your current heart rate, start an activity, start the stop watch, have a 2 minutes relaxation exercise and set silent alarms. A silent alarm is an alarm that vibrates the fitness band instead of sounding any alarm, so you can set reminders or even set it to wake you up with no sound (doesn’t work for me, as I keep sleeping after that). It also sets a reminder to get you ready to go to bed some minutes before the time you configured that you plan to start sleeping and it also shows on the fitness band screen notifications from your favorite messenger (Facebook messenger, whatsapp, whatever you use more often) and phone calls.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/fitbit.jpg&quot; alt=&quot;Fitbit&quot; title=&quot;Fitbit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.fitbit.FitbitMobile&quot;&gt;Get it on Google Play&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Do you know another app that could be included on this list? Please let me know in the comments below :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/my-favorite-fitness-apps-for-android/&quot;&gt;My favorite fitness apps for Android&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on September 24, 2017.&lt;/p&gt;
  </content>
</entry>


<entry>
  <title type="html"><![CDATA[One year working with Node.js after PHP and .NET - Challenges, comparisons and improvements]]></title>
  <link rel="alternate" type="text/html" href="https://jonathas.com/one-year-working-with-nodejs-after-php-and-dotnet/" />
  <id>https://jonathas.com/one-year-working-with-nodejs-after-php-and-dotnet</id>
  <published>2017-09-16T08:20:08+00:00</published>
  <updated>2017-09-16T08:20:08+00:00</updated>
  <author>
    <name>Jonathas Ribeiro</name>
    <uri>https://jonathas.com</uri>
    <email>contact@jonathas.com</email>
  </author>
  <content type="html">
    &lt;p&gt;In my software development career I’ve worked so far with both frontend and backend development, also with Desktop apps development (.NET) and Hybrid mobile development (Ionic Framework), but always enjoyed and focused more on the backend. I started my career in 2008 with PHP, so around this time JavaScript was mostly for creating alert messages, form validation and a bit of user interaction. Who could tell it would become what it is today?&lt;/p&gt;

&lt;p&gt;In 2010 I’ve worked with Java web for a while, then PHP again, then around 2013 I changed to .NET for desktop and web, then AngularJS (Ionic Framework), then PHP again and then Node.js (since 2016).
So this year (2017) I complete 1 year working with Node.js and related technologies. It has been a great journey so far, full of ups and downs.&lt;/p&gt;

&lt;h2 id=&quot;asynchronous-vs-synchronous&quot;&gt;Asynchronous vs Synchronous&lt;/h2&gt;

&lt;p&gt;When you write your code in PHP, .NET or Python, the instructions you write are followed line by line when the code is executed. That doesn’t happen with JavaScript when you write a function that reads a file or fetches something from the database, for example. That function is called and the code continues its execution on the next line before that function is finished, as it works asynchronously. This can be mind blowing sometimes for someone who is coming from other languages, so it was the biggest change for me when starting to work with Node.js. Even though Node.js has many methods that have synchonous versions, it’s not recommended to use them, as the Node process would be waiting for the method execution to finish before doing anything else. So it’s always recommended to use async functions in order to allow the event loop to work in an optimized way.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/eventloop.jpg&quot; alt=&quot;Event loop&quot; title=&quot;Event loop&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You get used to it after a while, but the callback hell was probably what made all look more confusing.&lt;/p&gt;

&lt;h2 id=&quot;callbacks-vs-promises-vs-asyncawait&quot;&gt;Callbacks vs Promises vs async/await&lt;/h2&gt;

&lt;p&gt;As I started learning more about Node.js, all the courses I took online and books I read were showing how to do everything with callbacks. Sometimes there were callbacks inside callbacks, creating what is called the “&lt;a href=&quot;http://callbackhell.com/&quot;&gt;callback hell&lt;/a&gt;”, code with many levels of indentation.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a.txt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;b.txt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
           &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ab.txt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;we are done&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As a result, the first system I wrote in Node.js was full of callbacks. When I had to use a for loop with callbacks inside, then it was an even bigger problem. So I found out I could use the &lt;a href=&quot;https://www.npmjs.com/package/async&quot;&gt;async module&lt;/a&gt; for that, to orchestrate these callbacks and loop through them and that way the code would wait instead of going to the next line and ignoring the loop.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;directories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir01&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir03&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir04&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;directories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;loopCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/a.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/b.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/ab.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`We are done for directory &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;loopCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Do something with the error&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;All done!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Do something else here that depends on the ab.txt files&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Promises looked like a cleaner alternative for the callbacks, so I started refactoring parts of the code to use them and they looked indeed much cleaner.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFileContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeFileContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFileContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`a.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;concatContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFileContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`b.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;concatContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeFileContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`ab.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* etc etc */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then on ES7 we finally were allowed to make our code look normal like in some other languages, with instruction below instruction, instead of instruction inside instruction. When you have less levels of indentation, your code looks clearer and simpler. So I started refactoring all code to async/await and using util.promisify. Now it is much shorter and looks much simpler and easier to understand.
(Code refactored by &lt;a href=&quot;https://www.reddit.com/user/alsiola&quot;&gt;alsiola&lt;/a&gt; on reddit)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promisify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/a.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/b.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/ab.txt`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dirs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir01&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir03&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dir04&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;dirs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;concatDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;All Done!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;doSomethingElse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;concatFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Do something else here that depends on the ab.txt files inside every directory&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Return a promise&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is easier to work with that on Node.js than on the frontend, as you’ll only need to update Node version to 8 or later, while on the frontend you’d have to use Babel to transpile to older ECMAScript versions in order to support older browsers that don’t have async/await implemented in their old versions.&lt;/p&gt;

&lt;h2 id=&quot;mongodb-and-the-nosql-paradigm&quot;&gt;MongoDB and the NoSQL paradigm&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/mongodb.jpg&quot; alt=&quot;MongoDB&quot; title=&quot;MongoDB&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This database has nothing an everything to do with Node.js. It can be used with many other languages as well, but it’s the most used one by Node.js developers, forming the MEAN (MongoDB, Express, AngularJS, Node.js) or MERN (MongoDB, Express, React, Node.js) stack. Before starting to work with Node.js, I had used MySQL, SQL Server, Oracle and studied about PostgreSQL, which are all relational databases. So this was also a change of paradigm for me, as MongoDB is a NoSQL, non relational database. It doesn’t have queries, tables nor relationships. In the beggining I ended up modelling the database with the relational paradigm in mind, which didn’t work well for MongoDB (got really slow), so then I learned better and had to model it again to use the NoSQL way. Instead of queries with joins, I had to learn about the Aggregation framework, which works with pipelines filtering the results from the database. In my opinion that looks more confusing than any SQL query.&lt;/p&gt;

&lt;h2 id=&quot;vagrant-and-docker&quot;&gt;Vagrant and Docker&lt;/h2&gt;

&lt;p&gt;Some years ago while working with PHP, I started using Vagrant with Puppet for provisioning development environments, but it would use a full virtual machine with Virtualbox for that with everything in one place and most of times it would become huge. Since starting to work with Node.js I also started using Docker, which I wanted to start using long before already. Docker is much lighter and not a full virtual machine, but a &lt;a href=&quot;https://www.docker.com/what-container&quot;&gt;container&lt;/a&gt;, so it can be used for development and I started using it even for production in order to make the deployment process more automatic and much simpler. The same environment you have on development you have on production, so this takes away the “works on my machine” excuse.&lt;/p&gt;

&lt;h2 id=&quot;deployment-challenges&quot;&gt;Deployment challenges&lt;/h2&gt;

&lt;p&gt;This part is where I had the biggest challenge. Some years ago when I was studying about Ruby on Rails, I wanted to learn how to deploy it to production, as what I had done the most before was LAMP (Linux, Apache, MySQL, PHP). I found out about &lt;a href=&quot;https://www.phusionpassenger.com/&quot;&gt;Phusion Passenger&lt;/a&gt; at that time, and that many people were using it to deploy their Ruby on Rails systems.&lt;/p&gt;

&lt;p&gt;When I started thinking about how I would deploy Node.js, my company wanted to deploy to a Debian Linux server (IaaS) instead of to a managed platform like &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; or &lt;a href=&quot;https://www.openshift.com/&quot;&gt;Openshift&lt;/a&gt; (PaaS). I saw no problem with that, as I had been using Linux since 2006 and had already configured many LAMP servers before. So I rememberd about Phusion Passanger, went to look for it and saw that they also supported Node.js. I found out they had a Docker image as well, with everything installed and ready to be used on production, so I went with that. I read through their whole documentation for Node.js and the best way to deploy Passenger integrated with Nginx for Node.js.&lt;/p&gt;

&lt;p&gt;Their documentation stated that in order to deploy in an optimized way for thousands of simultaneous requests, several Passenger instances should be started, according to the amount of RAM available on the server. So following a calculation they had on their documentation, I configured the number of Passenger instances according to the amount of RAM to handle the load. Then it’s when the problems started to happen. When we started testing it with thousands of simultaneous clients, the Passenger instances would start handling the load but would also start bloating the RAM. After a while all the RAM was full and the server started swapping, until it couldn’t take it anymore and would shutdown. We tried increasing the amount of available RAM several times, but it would use as much RAM as it had available.&lt;/p&gt;

&lt;p&gt;Looking into the problem and discussing it on some IRC channels, I realised the issue was that Passenger’s deployment documentation was written for several languages and technologies (As Passenger can be used for Rails and Python as well), not specifically for a Node.js system, so that’s why it had that kind of calculation there to discover the number of instances that should be spinned in cluster mode. Because of that configuration, the CPU was becoming crazy with the context switching because of so many requests and the RAM was getting bloated. For Node.js, a non multi-thread technology, it should be maximum 1 process per CPU core.&lt;/p&gt;

&lt;p&gt;After researching more and more about Node.js deployment, I decided to change from Passenger integrated with Nginx (all inside one Docker container) to a &lt;a href=&quot;http://pm2.keymetrics.io/&quot;&gt;pm2&lt;/a&gt; Docker container with a separate container for Nginx in front of it as a reverse proxy. Passenger is written in C++ and its &lt;a href=&quot;https://github.com/phusion/passenger-docker&quot;&gt;Docker image&lt;/a&gt; is based on Ubuntu with Nginx and even Redis or Memcached inside, which is a wrong way of using Docker, putting everything inside the same container as if it was a virtual machine with everything you need. The &lt;a href=&quot;https://hub.docker.com/r/keymetrics/pm2/&quot;&gt;pm2 Docker image&lt;/a&gt; comes only with pm2 and is based on &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;, which has only 5mb size, so it’s much more optimized.&lt;/p&gt;

&lt;p&gt;After this change of process manager for Node.js, I also decided to split the monolith API into some microservices and run a pm2 docker container for each of these microservices, with Nginx proxying to the right address inside the network according to the accessed endpoint. The amount of pm2 instances to handle the load was configured not to be bigger than the amount of CPU cores the server has, so this time the server handled the load beautifully and consumed not even 10% of the available RAM, even under heavy load.&lt;/p&gt;

&lt;h2 id=&quot;resources-i-used-for-learning-nodejs&quot;&gt;Resources I used for learning Node.js&lt;/h2&gt;

&lt;p&gt;Some of the resources I’ve used for learning Node.js are listed below:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Courses on &lt;a href=&quot;https://www.alura.com.br/&quot;&gt;alura.com.br&lt;/a&gt; (Brazilian Portuguese). If you understand Portuguese, I always recommend these guys.&lt;/li&gt;
  &lt;li&gt;Lynda’s course &lt;a href=&quot;https://www.lynda.com/Node-js-tutorials/Node-js-Essential-Training/417077-2.html&quot;&gt;Node.js Essential Training&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Udemy’s course &lt;a href=&quot;https://www.udemy.com/the-complete-nodejs-developer-course-2/&quot;&gt;Master NodeJS: The Complete Front-End Developer Course&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Pluralsight - Building Web Apps With Node.js&lt;/li&gt;
  &lt;li&gt;Chalkstreet - Node.js: Learn Node.js from scratch&lt;/li&gt;
  &lt;li&gt;Coursera - &lt;a href=&quot;https://www.coursera.org/learn/server-side-development/home/welcome&quot;&gt;Server-side Development with NodeJS: The Hong Kong University of Science and Technology&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Book from Casa do Codigo: &lt;a href=&quot;https://www.casadocodigo.com.br/products/livro-nodejs&quot;&gt;Aplicações web real-time com Node.js - Caio Ribeiro Pereira&lt;/a&gt; (Brazilian Portuguese)&lt;/li&gt;
  &lt;li&gt;Node.js documentation&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://lmgtfy.com/?q=Node.js&quot;&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can recommend Alura (if you know Portuguese) or Pluralsight for anything you need to learn, actually. They are absolutely amazing.
I also strongly recommend using TypeScript, as it helps you organize your code much better than pure Javascript.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;These were some of my experiences so far with Node.js and some comparisons with other technologies I’ve worked with.&lt;/p&gt;

&lt;p&gt;In my opinion Node.js courses could cover more about deployment, but perhaps that’s more related to insfrastructure/DevOps.&lt;/p&gt;

&lt;p&gt;Do you work with Node.js or are planning to? Leave your comment below :)&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://jonathas.com/one-year-working-with-nodejs-after-php-and-dotnet/&quot;&gt;One year working with Node.js after PHP and .NET - Challenges, comparisons and improvements&lt;/a&gt; was originally published by Jonathas Ribeiro at &lt;a href=&quot;https://jonathas.com&quot;&gt;Jonathas Ribeiro&lt;/a&gt; on September 16, 2017.&lt;/p&gt;
  </content>
</entry>

</feed>
