<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shahid's Story]]></title><description><![CDATA[Shahid's Story]]></description><link>https://blog.shahid.codes</link><generator>RSS for Node</generator><lastBuildDate>Thu, 23 Apr 2026 03:24:05 GMT</lastBuildDate><atom:link href="https://blog.shahid.codes/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Opening a port in Ubuntu VM (OCI - Oracle Cloud Infrastructure)]]></title><description><![CDATA[Whether you're setting up a web server, database, or other services on a virtual machine (VM) in Oracle Cloud Infrastructure (OCI), you'll often need to open specific ports to allow incoming traffic. Opening a port in OCI VM is not as straightforward...]]></description><link>https://blog.shahid.codes/opening-a-port-in-ubuntu-vm-oci-oracle-cloud-infrastructure</link><guid isPermaLink="true">https://blog.shahid.codes/opening-a-port-in-ubuntu-vm-oci-oracle-cloud-infrastructure</guid><category><![CDATA[Oraclecloudinfrastructure]]></category><category><![CDATA[vm]]></category><category><![CDATA[port]]></category><category><![CDATA[OCI]]></category><category><![CDATA[Ubuntu]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Wed, 30 Aug 2023 11:44:06 GMT</pubDate><content:encoded><![CDATA[<p>Whether you're setting up a web server, database, or other services on a virtual machine (VM) in Oracle Cloud Infrastructure (OCI), you'll often need to open specific ports to allow incoming traffic. Opening a port in OCI VM is not as straightforward as other cloud providers. OCI makes use of iptables so there's an additional step involved to make it work.</p>
<p>Assuming you've</p>
<ul>
<li><p>Create a VM and have a service running on a port</p>
</li>
<li><p>Opened the port in your security list and security group</p>
</li>
</ul>
<h3 id="heading-ssh-to-the-vm">SSH to the VM</h3>
<p>First, let's ssh to our VM. We need to save our current iptables by running below command in the SSH shell</p>
<pre><code class="lang-bash">iptables-save &gt; current-rules.txt
</code></pre>
<p>After that, we need to run the below command to allow all traffic to flow.</p>
<pre><code class="lang-bash">sudo iptables -I INPUT -j ACCEPT
</code></pre>
<p>This will temporarily enable the flow of all traffic. Test everything and make sure it works.</p>
<p>After you've verified everything is fine, we need to persist this rule across reboots.</p>
<p>Run the below command</p>
<pre><code class="lang-bash">sudo iptables-save -f /etc/iptables/rules.v4
</code></pre>
<p>This will persist the rule across reboot.</p>
<p>Now, for any firewalling, you need to use either a security list or a security group.</p>
<p>Credit goes to <a target="_blank" href="https://stackoverflow.com/a/73400079">https://stackoverflow.com/a/73400079</a></p>
<p>Happy OCI'ing</p>
]]></content:encoded></item><item><title><![CDATA[Building Your First Virtual Router Using PFSense/OPNSense and Proxmox]]></title><description><![CDATA[I've been thinking about virtualizing my router for a long time now. It gives excellent control over what goes in and out of your network. Plus you can do all sorts of cool things like running a VPN server, a recursive DNS server and more. I had a sp...]]></description><link>https://blog.shahid.codes/building-your-first-virtual-router-using-pfsenseopnsense-and-proxmox-with-single-nic</link><guid isPermaLink="true">https://blog.shahid.codes/building-your-first-virtual-router-using-pfsenseopnsense-and-proxmox-with-single-nic</guid><category><![CDATA[pfsense]]></category><category><![CDATA[virtualization]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[opnsense]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Sat, 10 Sep 2022 17:31:05 GMT</pubDate><content:encoded><![CDATA[<p>I've been thinking about virtualizing my router for a long time now. It gives excellent control over what goes in and out of your network. Plus you can do all sorts of cool things like running a VPN server, a recursive DNS server and more. I had a spare laptop lying around that I rarely use. It got a core i7 9th gen, 32 GB ram and 1 TB of SSD, which is more than enough to run proxmox.</p>
<p><strong>Prerequisite</strong></p>
<ul>
<li><p>A pc/laptop with a fresh installation of proxmox</p>
</li>
<li><p>OPNSense/PFSense ISO downloaded and uploaded to proxmox</p>
</li>
<li><p>A switch that supports 802.1q port tagging, I used the TP-Link SG108E switch.</p>
</li>
</ul>
<h2 id="heading-making-proxmox-vlan-aware">Making Proxmox VLAN Aware</h2>
<p>When you set up proxmox the first time, it will automatically create a network bridge and most probably the name will be <code>vmbr0</code>. Go to <code>Your Node &gt; Networks</code> and it will look something like below</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662828937134/38UDQai81.png" alt="image.png" /></p>
<p>To make your NIC VLAN award, just select the bridge <code>vmbr0</code> and click edit.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662829020201/mbN7LBAph.png" alt="image.png" /></p>
<p>Click the <code>VLAN Aware</code> checkbox and click ok. You will see a button "Apply Configuration", click that and wait for some time for network services to reload.</p>
<p>Congratulations, your proxmox can now receive and send VLAN traffic.</p>
<h2 id="heading-creating-pfsenseopnsense-vm">Creating PFSense/OPNSense VM</h2>
<p>The next step will be to create a VM and configure network interfaces. So go ahead and create VM and once it's complete go to the hardware tab of the VM</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662829293665/aJfec1FLU.png" alt="image.png" /></p>
<p>You will see there's one network device added to it that you have selected during the creation process. Make sure this device uses <code>vmbr0</code> as a bridge and there's no VLAN. We will use this device as a WAN interface for our router.</p>
<p>Now, click <code>Add</code> and select a network device</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662829425578/rfrckKBQh.png" alt="image.png" /></p>
<p>This time everything stays the same as above, the only thing that needs to change is the VLAN tag. Enter a VLAN tag (i.e. 10) that you will use for LAN traffic. It does not matter what you enter but make sure you don't enter a VLAN that switch uses as a Native VLAN ID. Click <code>Add</code> and you're done.</p>
<p>Now, complete the setup of PFSense/OPNSense. Do note that you have to assign the correct WAN and LAN interfaces or else it won't work. In my case, I saw the names <code>vtnet0</code> and <code>vtnet1</code> in the setup wizard. So <code>vtnet0</code> became my WAN as it's the first network device in the hardware tab of the VM and <code>vtnet1</code> became LAN.</p>
<p>Congratulation, you're almost done.</p>
<h2 id="heading-configuring-switch">Configuring Switch</h2>
<p>Here the steps will change depending on the switch you use. I will show you how I configured my TP-Link SG108E switch. The basic configuration will stay the same across the switch manufacturer, UI may change.</p>
<p>We are going to pick WAN and LAN ports on our switches. The port that connects to the proxmox machine automatically becomes the WAN port. Now it's up to you to pick LAN ports. In my case -</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>PORT</td><td>PURPOSE</td></tr>
</thead>
<tbody>
<tr>
<td>PORT 1</td><td>CONNECTS ISP MODEM</td></tr>
<tr>
<td>PORT 2</td><td>EMPTY</td></tr>
<tr>
<td>PORT 3</td><td>PROXMOX WAN PORT</td></tr>
<tr>
<td>PORT 4-7</td><td>EMPTY</td></tr>
<tr>
<td>PORT 8</td><td>LAN PORT - CONNECTS TO WIFI AP</td></tr>
</tbody>
</table>
</div><p>After you've decided on WAN and LAN ports make the below configuration to your switch</p>
<ul>
<li><p>Configure proxmox WAN and LAN to be a member of VLAN ID (i.e. which you've entered in proxmox VM above)</p>
</li>
<li><p>Proxmox WAN port is set as TAGGED port, so it accepts LAN traffic on the above VLAN ID</p>
</li>
<li><p>Proxmox WAN port is also part of native VLAN ID, in my case VLAN 1. Here if you have the default configuration of the switch it should work. This needs to make proxmox WAN work.</p>
</li>
<li><p>Set LAN port Primary/Native VLAN ID to the VLAN ID you entered above. This is for the devices that do not support VLAN Tagging.</p>
</li>
</ul>
<p>This is how it looks in my switch -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662830474651/-_NKxiUQ7.png" alt="image.png" /></p>
<p>Primary/Native VLAN ID configuration -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662830501897/tGTvWxR3M.png" alt="image.png" /></p>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<ul>
<li><p>In case you can't access PFSense/OPNSense from LAN, make sure you've entered the correct gateway and IP address when configuring LAN. I forgot to set the gateway and it took 1 day to figure that out.</p>
</li>
<li><p>Reboot your proxmox host after VLAN configuration if something is not working.</p>
</li>
<li><p>Wait for some time to switch to process the configuration, for me it took a couple of seconds to re-configure the ports.</p>
</li>
</ul>
<p>That's all. Congratulations, you've virtualized your router.</p>
]]></content:encoded></item><item><title><![CDATA[Run your CI/CD runner with Gitlab Runner in 2 minutes]]></title><description><![CDATA[I really like Gitlab for their CI/CD services. And specially their generous free usage offering. These month I've been running out of CI/CD minutes. So I decided to run my own runner inside a VM which I don't use that much.
Prerequisites

Docker inst...]]></description><link>https://blog.shahid.codes/run-your-cicd-runner-with-gitlab-runner-in-2-minutes</link><guid isPermaLink="true">https://blog.shahid.codes/run-your-cicd-runner-with-gitlab-runner-in-2-minutes</guid><category><![CDATA[Docker]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Sun, 30 Jan 2022 09:39:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643535214038/gBawHAk35.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I really like Gitlab for their CI/CD services. And specially their generous free usage offering. These month I've been running out of CI/CD minutes. So I decided to run my own runner inside a VM which I don't use that much.</p>
<p><strong>Prerequisites</strong></p>
<ul>
<li>Docker installed</li>
</ul>
<h2 id="heading-step-1">Step 1</h2>
<p>Registering our runner to Gitlab. You'll need to get the <code>Runner registration token</code> from the <code>Project Setting &gt; CI/CD</code> page.
It'll look something like below</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643535214038/gBawHAk35.png" alt="image.png" /></p>
<p>Let's register our runner. </p>
<pre><code class="lang-sh">docker run --rm -it --name gitlab-runner      -v /srv/gitlab-runner/config:/etc/gitlab-runner      -v /var/run/docker.sock:/var/run/docker.sock      gitlab/gitlab-runner:latest register
</code></pre>
<p>We're doing a few things here.</p>
<ul>
<li>First we are running gitlab-runner container and asking it to register itself to Gitlab.</li>
<li>We're also mounting <code>docker.sock</code> to the runner container so that it can spin a new docker container for our CI/CD pipeline</li>
<li>We're mount a config directory so our configuration is persisted across the container restart.</li>
</ul>
<p>Once you run above it will ask you two thing, Gitlab instance URL and your registration token. Just fill in what you obtained from above step.</p>
<p>Once that's done. We need to run the <code>gitlab-runner</code> container.</p>
<p>Let's run it </p>
<pre><code class="lang-sh">docker run -d --name gitlab-runner --restart always      -v /srv/gitlab-runner/config:/etc/gitlab-runner      -v /var/run/docker.sock:/var/run/docker.sock      gitlab/gitlab-runner:latest
</code></pre>
<p>Remember we are mounting the same config directory, and it is important otherwise <code>gitlab-runner</code> won't run.</p>
<p>And that's all. Now if you refresh the CI/CD settings page you will see your runner listed there.</p>
]]></content:encoded></item><item><title><![CDATA[How to exclude a route from caching in nest.js]]></title><description><![CDATA[I love working with  Nest.js, it's such an excellent framework for teams to build a scalable backend server. 
Recently, I've been working on caching all our routes. It worked well for a few days then I noticed I missed an endpoint that can not be cac...]]></description><link>https://blog.shahid.codes/how-to-exclude-a-route-from-caching-in-nestjs</link><guid isPermaLink="true">https://blog.shahid.codes/how-to-exclude-a-route-from-caching-in-nestjs</guid><category><![CDATA[nest]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[caching]]></category><category><![CDATA[Redis]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Thu, 13 Jan 2022 02:32:21 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642041251940/W8635Ihhq.png" alt="image.png" />
I love working with  <a target="_blank" href="nestjs.com">Nest.js</a>, it's such an excellent framework for teams to build a scalable backend server. 
Recently, I've been working on caching all our routes. It worked well for a few days then I noticed I missed an endpoint that can not be cached, which was to generate a coupon code. Imagine two customers ending up with the same coupon code. I immediately started looking at how to ignore a certain route while using global caching in nest.js but to my surprise, there is no pre-built decorator or config to do that. But there are simple ways to achieve that.</p>
<p>Nest.js uses  <a target="_blank" href="https://github.com/nestjs/nest/blob/master/packages/common/cache/interceptors/cache.interceptor.ts">CacheInterceptor</a>  to intercept our HTTP request and decide whether to cache it or not. We can create our own custom interceptor that <code>extends</code> default  <a target="_blank" href="https://github.com/nestjs/nest/blob/master/packages/common/cache/interceptors/cache.interceptor.ts">CacheInterceptor</a> and add some logic to ignore a route.</p>
<p>A suggested way can be found on  <a target="_blank" href="https://stackoverflow.com/questions/54471331/how-to-ignore-an-interceptor-for-a-particular-route-in-nestjs">StackOverflow</a> but I did not want to hard code the route paths in my interceptor.</p>
<p>There's another way we can do it by combining our custom CacheInterceptor and a CustomDecorator.</p>
<p>First, we need to create a custom cache interceptor. Note that this is slightly different from the accepted StackOverflow answer.</p>
<p><code>my-cache.interceptor.ts</code></p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { CacheInterceptor, ExecutionContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MyCacheInterceptor <span class="hljs-keyword">extends</span> CacheInterceptor {
  <span class="hljs-keyword">protected</span> isRequestCacheable(context: ExecutionContext): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">const</span> http = context.switchToHttp();
    <span class="hljs-keyword">const</span> request = http.getRequest();

    <span class="hljs-keyword">const</span> ignoreCaching: <span class="hljs-built_in">boolean</span> = <span class="hljs-built_in">this</span>.reflector.get(
      <span class="hljs-string">'ignoreCaching'</span>,
      context.getHandler(),
    );

    <span class="hljs-keyword">return</span> !ignoreCaching || request.method === <span class="hljs-string">'GET'</span>;
  }
}
</code></pre>
<p>Here, we're overriding a method on default interceptor <code>isRequestCacheable</code> which tells nest.js whether to go ahead and cache it or ignore it. In this method, we are using nest.js Reflector to get the metadata value. If metadata <code>ignoreCaching</code> is set to true, we tell nest.js to ignore it otherwise cache it if the request method is <code>GET</code>.
We're going to create a custom decorator to set this metadata value.</p>
<p>Let's create a <code>no-cache.decorator.ts</code> file somewhere -</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { SetMetadata } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> NoCache = <span class="hljs-function">() =&gt;</span> SetMetadata(<span class="hljs-string">'ignoreCaching'</span>, <span class="hljs-literal">true</span>);
</code></pre>
<p>We're almost done. Now let's test it. Let's use this decorator in our controller</p>
<pre><code class="lang-ts">
<span class="hljs-meta">@Controller</span>(<span class="hljs-string">'coupon'</span>)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CouponController {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> couponService: CouponService</span>) {}

  <span class="hljs-meta">@Get</span>(<span class="hljs-string">'code'</span>)
  <span class="hljs-meta">@NoCache</span>()
  code() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.couponService.generateCode();
  }

}
</code></pre>
<p>and, that's all. Now any controller which is decorated with <code>NoCache</code> will be ignored from caching. Pretty Neat right?</p>
]]></content:encoded></item><item><title><![CDATA[Setup Redis with TLS using Docker]]></title><description><![CDATA[Recently, I was trying to set up Redis for a production environment and I must say it was a bumpy ride. I ran into a lot of problems while setting up TLS. 
If you don't know What is Redis?

Redis is an open-source (BSD licensed), in-memory data struc...]]></description><link>https://blog.shahid.codes/setup-redis-with-tls-using-docker</link><guid isPermaLink="true">https://blog.shahid.codes/setup-redis-with-tls-using-docker</guid><category><![CDATA[Redis]]></category><category><![CDATA[Docker]]></category><category><![CDATA[TLS]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Fri, 07 Jan 2022 18:12:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/GnvurwJsKaY/upload/v1641578973811/-bCFIXukj.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1641578795885/vvBLEFKow.png" alt="image.png" />
Recently, I was trying to set up Redis for a production environment and I must say it was a bumpy ride. I ran into a lot of problems while setting up TLS. </p>
<p>If you don't know <strong>What is Redis?</strong></p>
<blockquote>
<p>Redis is an open-source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. - <em>redis.io</em></p>
</blockquote>
<p>In this guide, I will show you how you can set up Redis w/ TLS using Docker. The TLS part can be used for standalone installation. But bear in mind, you will need to build Redis from the source in order to use TLS in standalone mode.</p>
<p>So, let's get started.</p>
<p><strong>Prerequisites</strong></p>
<ul>
<li>Docker installed.</li>
<li>Docker Compose Installed.</li>
<li>A client to test the Redis installation. I recommend, https://github.com/ekvedaras/redis-gui. It's simple but feel free to use whatever you like.</li>
</ul>
<h2 id="heading-running-the-redis-container">Running the Redis container</h2>
<p>Let's run the Redis container without TLS first - </p>
<p>Create a file <code>compose.yml</code> and paste the content below.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/bitnami/redis:6.2</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">"${UID}:${GID}"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ALLOW_EMPTY_PASSWORD=false</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_PASSWORD=the-stronge-one</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'6379:6379'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'redis_data:/bitnami/redis/data'</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">redis_data:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">local</span>
</code></pre>
<p>In the <code>compose.yml</code> file we are doing </p>
<ol>
<li>Specifying an image to run. Here I picked <code>binami</code> image instead of the official docker image since it has the ability to run with TLS without needing to be built from the source.</li>
<li>Specifying the <code>port</code> 6379 and a docker volume to persist the data.</li>
<li>Setting some environment variables</li>
</ol>
<p>Let's run the container </p>
<pre><code class="lang-bash">docker-compose up -d
</code></pre>
<p>Your container should be up and running. You should be able to connect to your Redis instance by using <code>Redis GUI</code> or any other client. Make sure you do not specify any TLS options as we will come to that later in this guide.</p>
<h2 id="heading-generating-certificates">Generating Certificates</h2>
<p>In order to run Redis with TLS, we need to supply our own SSL certificate. You can buy one or for this use case we don't need to buy but generate our own self-signed certificate for both our Redis instance and the client connecting to it. This was the part that was most troublesome for me.</p>
<p>This might be confusing but bear with me and it should be fine.</p>
<h3 id="heading-the-script">The Script</h3>
<p>We can do this step manually but since on the Redis GitHub repository, there is a script that generates some certificates for our use case, we will use that. </p>
<pre><code class="lang-sh">wget https://raw.githubusercontent.com/redis/redis/cc0091f0f9fe321948c544911b3ea71837cf86e3/utils/gen-test-certs.sh
</code></pre>
<p>Once the script is downloaded, let's run it by</p>
<pre><code class="lang-sh">sh gen-test-certs.sh
</code></pre>
<p>The script will create a <code>tests/tls</code> folder, which contains all the certificates we will need.</p>
<p>Additionally, you can modify the script to add your organization name, fqdn, and other important parameters for the SSL certificate. Go ahead and take a look at the files in that directory.</p>
<p>We're interested in the below files - </p>
<ul>
<li>redis.crt </li>
<li>redis.key </li>
<li>ca.crt</li>
<li>client.crt</li>
<li>client.key</li>
</ul>
<p>Let's go to the next step, which is to update our <code>compose.yml</code> file and include these certificate files to tell Redis to start with TLS.</p>
<h2 id="heading-configuring-tls">Configuring TLS</h2>
<p>Let's tell Redis container about the above files by specifying them as environment variables. Here, the best part of <code>bitnami</code> image is you can do start the container with TLS without building the original redis image again.</p>
<p>Update your <code>compose.yml</code> file to match below</p>
<pre><code class="lang-yaml">
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/bitnami/redis:6.2</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">"${UID}:${GID}"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ALLOW_EMPTY_PASSWORD=false</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_PASSWORD=the-strong-one</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_TLS_CERT_FILE=/tls/redis.crt</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_TLS_KEY_FILE=/tls/redis.key</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_TLS_CA_FILE=/tls/ca.crt</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_TLS_ENABLED=yes</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_TLS_PORT=6379</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'6379:6379'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'redis_data:/bitnami/redis/data'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./tests/tls:/tls</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">redis_data:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">local</span>
</code></pre>
<p>Notice that we've specified the environment variable and specified a path (i.e. /tls/*) inside the container for cert files. And we mounted the cert files folder (i.e. ./tests/tls) to the container on the same path (i.e. /tls*).</p>
<p>Phew! That's it. Now we can run our container again and it will start with TLS enabled.</p>
<pre><code class="lang-sh">docker-compose up -d
</code></pre>
<p>Now, test with your client. You will need to configure your client to use our self-signed client certificate and ca certificate to connect.</p>
<p>Happy Caching. </p>
<hr />
<p>Feel free to ask if you face any problems on the way.</p>
]]></content:encoded></item><item><title><![CDATA[How to add x minutes to a date in MongoDB aggregation?]]></title><description><![CDATA[So I needed to know occupied time for an staff from orders they've been assigned. A customer creates an order and one or more staffs are assigned to that order for services that customer has requested. To get the insights of orders and staff schedule...]]></description><link>https://blog.shahid.codes/how-to-add-x-minutes-to-a-date-in-mongodb-aggregation</link><guid isPermaLink="true">https://blog.shahid.codes/how-to-add-x-minutes-to-a-date-in-mongodb-aggregation</guid><category><![CDATA[MongoDB]]></category><category><![CDATA[mongoose]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Thu, 27 May 2021 03:15:32 GMT</pubDate><content:encoded><![CDATA[<p>So I needed to know occupied time for an staff from orders they've been assigned. A customer creates an order and one or more staffs are assigned to that order for services that customer has requested. To get the insights of orders and staff schedule time, I needed to write some aggregation.</p>
<p>The MongoDB schema for orders collection is like this - </p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">orderNum</span>: <span class="hljs-built_in">Number</span>,
  <span class="hljs-attr">scheduledAt</span>: <span class="hljs-built_in">Date</span>,
  <span class="hljs-attr">services</span>: [{
    <span class="hljs-attr">staffId</span>: ObjectId,
    <span class="hljs-attr">averageTimeMinute</span>: <span class="hljs-built_in">Number</span> <span class="hljs-comment">// time taken to complete the service</span>
    <span class="hljs-comment">// ....</span>
  }],
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><code>scheduledAt</code> tells when is the service scheduled to be serviced to the customer. And <code>averageTimeMinute</code> tells the average time takes for that service. The problem here is the <code>scheduledAt</code> is a Date while <code>averageTimeMinute</code> is minutes in Number. And I needed to come up with a solution that gives us a range of time when the staff is assigned to do the service. 
That can be calculated using plain javascript by - </p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> fromTime = scheduledAt;
<span class="hljs-keyword">const</span> endTime = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(scheduledAt);
endTime.setMinutes(endTime.getMinutes() + services[i].averageTimeMinute)
</code></pre>
<p>But the better way of doing anything like this, is with the help of MongoDB aggregations. It is very powerful to manipulate data in a way we want. If you don't know what an aggregation is, you can think of it like a pipeline where you input data from one end and it does something with data and outputs from another end.</p>
<p>The thing is aggregations are hard to understand and write. After hours of trial and error, I finally came up with an aggregation which will give me the desired output I need.
Here it is</p>
<pre><code class="lang-javascript">db.getCollection(<span class="hljs-string">"orders"</span>).aggregate([
  { <span class="hljs-attr">$unwind</span>: { <span class="hljs-attr">path</span>: <span class="hljs-string">"$services"</span> } },
  {
    <span class="hljs-attr">$group</span>: {
      <span class="hljs-attr">_id</span>: <span class="hljs-string">"$service.staffId"</span>,
      <span class="hljs-attr">services</span>: {
        <span class="hljs-attr">$push</span>: {
          <span class="hljs-attr">start</span>: <span class="hljs-string">"$scheduledAt"</span>,
          <span class="hljs-attr">end</span>: {
            <span class="hljs-attr">$toDate</span>: {
              <span class="hljs-attr">$add</span>: [
                { <span class="hljs-attr">$toLong</span>: <span class="hljs-string">"$scheduledAt"</span> },
                { <span class="hljs-attr">$multiply</span>: [<span class="hljs-string">"$services.averageMinuteTime"</span>, <span class="hljs-number">60000.0</span>] },
              ],
            },
          },
        },
      },
    },
  },
]);
</code></pre>
<p>So the trick here is, since <code>scheduledAt</code> is a Date and <code>averageMinuteTime</code> is a Number, we need both of these in same unit, Number. So the first step was to convert <code>scheduledAt</code> to a unix timestamp and in mongo we can do like this</p>
<pre><code class="lang-javascript">{ <span class="hljs-attr">$toLong</span>: <span class="hljs-string">"$scheduledAt"</span> }
</code></pre>
<p>Now we have both in same type, Number, we also need to make both numbers in same type. Now <code>scheduledAt</code> is in miliseconds, we also need to make our <code>averageMinuteTime</code> into miliseconds. We can do it by multiplying by <code>60000</code>. And in MongoDB we can do like this -</p>
<pre><code class="lang-javascript"> { <span class="hljs-attr">$multiply</span>: [<span class="hljs-string">"$services.averageMinuteTime"</span>, <span class="hljs-number">60000.0</span>] },
</code></pre>
<p>Now, both our <code>scheduledAt</code> and <code>averageMinuteTime</code> is in same type and unit. Let's add both and we will get our desired <code>endTime</code> for the service.</p>
<pre><code class="lang-javascript">
<span class="hljs-attr">$add</span>: [
   { <span class="hljs-attr">$toLong</span>: <span class="hljs-string">"$scheduledAt"</span> },
   { <span class="hljs-attr">$multiply</span>: [<span class="hljs-string">"$services.averageMinuteTime"</span>, <span class="hljs-number">60000.0</span>] },
],
</code></pre>
<p>And the last step is to convert it back to date, which in very easy to do -</p>
<pre><code class="lang-javascript">
<span class="hljs-attr">$toDate</span>: {
  <span class="hljs-attr">$add</span>: [
     { <span class="hljs-attr">$toLong</span>: <span class="hljs-string">"$scheduledAt"</span> },
     { <span class="hljs-attr">$multiply</span>: [<span class="hljs-string">"$services.averageMinuteTime"</span>, <span class="hljs-number">60000.0</span>] },
  ],
},
</code></pre>
<p>and here it is, we got out desired output from the pipeline. Now I can render beautiful charts to see when our staffs are scheduled to work.</p>
<p>I hope you found this interesting. Please let me know if you have any feedback.</p>
<p>Thanks</p>
]]></content:encoded></item><item><title><![CDATA[Basic APIs Testing with JEST and SuperTest]]></title><description><![CDATA[Testing is a very important part of a developers' career. We often find ourselves testing our code manually, decreasing productivity and introducing ground for nasty bugs to surface. Many developers prefer to test code manually because they find it d...]]></description><link>https://blog.shahid.codes/basic-apis-testing-with-jest-and-supertest</link><guid isPermaLink="true">https://blog.shahid.codes/basic-apis-testing-with-jest-and-supertest</guid><category><![CDATA[APIs]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Testing]]></category><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Shahid Kamal]]></dc:creator><pubDate>Sun, 09 May 2021 07:20:53 GMT</pubDate><content:encoded><![CDATA[<p>Testing is a very important part of a developers' career. We often find ourselves testing our code manually, decreasing productivity and introducing ground for nasty bugs to surface. Many developers prefer to test code manually because they find it difficult to set up the test environment. Often I found myself refactoring a large part of code to make it testable. </p>
<p>In this article, we will see how easy it is to set up unit testing by creating a hello world API in <code>node.js.</code></p>
<h2 id="creating-a-basic-server">Creating a basic server</h2>
<p>Let's create a folder and init a node.js project.</p>
<pre><code class="lang-bash">$&gt; <span class="hljs-built_in">cd</span> hello-world-test
$&gt; npm init --yes
</code></pre>
<p>This will create a <code>package.json</code> file to track project dependencies.</p>
<p>We need to install <a target="_blank" href="https://expressjs.com/">express</a>. If you don't know, <code>express</code> is a web framework. And we will use it to create our hello world API</p>
<pre><code class="lang-bash">$&gt; npm i express
</code></pre>
<p>Now let's create our main entry point file.
Create a file <code>server.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()

<span class="hljs-comment">// start the server on port 3000</span>
app.listen(<span class="hljs-number">3000</span>)
</code></pre>
<p>Let's create our hello world api. Edit your <code>server.js</code> file to match following -</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()

<span class="hljs-comment">// hello world api</span>
app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send({ <span class="hljs-attr">message</span>: <span class="hljs-string">'hello world'</span> })
})

<span class="hljs-comment">// start the server on port 3000</span>
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'server started'</span>)
})
</code></pre>
<p>Let's start our server and see </p>
<pre><code class="lang-bash">$&gt; node server.js
</code></pre>
<p>You should see <code>server started</code> printed in the console. We are good to go.</p>
<h2 id="testing-our-api">Testing our API</h2>
<p>To test our <code>hello world</code> API, we need to make few changes to the server.</p>
<p>First, let's create a new file named <code>app.js</code>. This file will export our express app but won't start the server. This is required for the <code>supertest</code> library.</p>
<p><code>app.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()

<span class="hljs-comment">// hello world api</span>
app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send({ <span class="hljs-attr">message</span>: <span class="hljs-string">'hello world'</span> })
})

<span class="hljs-comment">// export our express app</span>
<span class="hljs-built_in">module</span>.exports = app;
</code></pre>
<p>and let's modify our <code>server.js</code> file to use the express app instance from <code>app.js.</code>  </p>
<p><code>server.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./app'</span>);
<span class="hljs-comment">// start the server on port 3000</span>
app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'server started'</span>)
})
</code></pre>
<p>Now we're ready to set up our API testing. We need our testing and assertion libraries. </p>
<p>Let's install</p>
<pre><code class="lang-bash">$&gt; npm install -D jest supertest
</code></pre>
<p>Let's create a folder <code>tests</code> and create a file <code>hello-world.spec.ts</code>, and add the following.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'supertest'</span>);
<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../app"</span>);


describe(<span class="hljs-string">"test hello-world"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should return hello world"</span>, <span class="hljs-function">(<span class="hljs-params">done</span>) =&gt;</span> {
    request(app)
      .get(<span class="hljs-string">"/"</span>)
      .expect(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-regexp">/json/</span>)
      .expect(<span class="hljs-number">200</span>)
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
        expect(response.body.message).toBe(<span class="hljs-string">"hello world"</span>);
        done();
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> done(e));
  });
});
</code></pre>
<p>Finally, let's run our test and see it passing - </p>
<pre><code class="lang-bash">$&gt; npx jest
</code></pre>
<p>If everything worked as expected, you should see below. </p>
<pre><code class="lang-bash"> PASS  tests/hello-world.spec.js
  <span class="hljs-built_in">test</span> hello-world
    ✓ should <span class="hljs-built_in">return</span> hello world (18 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.872 s, estimated 1 s
Ran all <span class="hljs-built_in">test</span> suites.
</code></pre>
<p>This means we have created an endpoint and ran a test to verify the response of the endpoint.</p>
<p>If you are still struggling you can see and compare your code from the source code found  <a target="_blank" href="https://github.com/shahidcodes/hello-world-api-test-jest-supertest">here</a>. </p>
<p>And you can also connect me on  <a target="_blank" href="https://twitter.com/shahidcodes">Twitter</a>.</p>
<p>I hope you find this article helpful. Let me know your suggestions in the comment.</p>
]]></content:encoded></item></channel></rss>