<?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[Tech with Mahad]]></title><description><![CDATA[Mahad loves building mobile and web applications and is here to take you on a journey, filled with bad decisions and learning from mistakes, through this blog]]></description><link>https://blog.mahad.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 12:41:38 GMT</lastBuildDate><atom:link href="https://blog.mahad.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to create your own email server using Main-in-a-Box]]></title><description><![CDATA[Introduction

Hosting your email server is the first step to secure your privacy

It saves you money if you own multiple domains


What You'll Need

[ ] Brand new VPS or any Linux Server (My recommended VPS provider is Linode)

[ ] Ubuntu 22.04 LTS i...]]></description><link>https://blog.mahad.dev/how-to-create-your-own-email-server-using-main-in-a-box</link><guid isPermaLink="true">https://blog.mahad.dev/how-to-create-your-own-email-server-using-main-in-a-box</guid><category><![CDATA[mail-in-a-box]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[email]]></category><category><![CDATA[email marketing]]></category><category><![CDATA[Linode]]></category><category><![CDATA[vps]]></category><category><![CDATA[hosting]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[Ubuntu 22.04]]></category><category><![CDATA[adduser]]></category><category><![CDATA[ssh]]></category><category><![CDATA[ssh-keys]]></category><category><![CDATA[terminal]]></category><category><![CDATA[ufw]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Thu, 20 Feb 2025 17:20:39 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<ul>
<li><p>Hosting your email server is the first step to secure your privacy</p>
</li>
<li><p>It saves you money if you own multiple domains</p>
</li>
</ul>
<h2 id="heading-what-youll-need">What You'll Need</h2>
<ul>
<li><p>[ ] Brand new VPS or any Linux Server (My recommended VPS provider is <a target="_blank" href="https://www.linode.com/lp/refer/?r=4169f7eb6d93952d4d05c8d100f8a4b45677ebc6">Linode</a>)</p>
</li>
<li><p>[ ] Ubuntu 22.04 LTS is the latest supported version</p>
</li>
<li><p>[ ] SSH access with sudo privilege</p>
</li>
</ul>
<h2 id="heading-step-1-create-non-root-user">Step 1: Create non-root user</h2>
<p>To create a new user, please type the following:</p>
<pre><code class="lang-bash">adduser johndoe
</code></pre>
<p>Replace <code>johndoe</code> with your own username. Then follow the prompts to set the password and other details</p>
<p>We need to let the new user run commands with sudo when we need, so, to make that happen you'll need to run:</p>
<pre><code class="lang-bash">usermod -aG sudo johndoe
</code></pre>
<p>Then connect to the server using the new user.</p>
<pre><code class="lang-bash">ssh johndoe@YOUR-SERVER-IP
</code></pre>
<p>We have logged in using the password of our new user which is not secure. To prepare our future logins to use ssh keys instead of passwords, let's quickly do the following:</p>
<pre><code class="lang-bash">mkdir -p ~/.ssh    <span class="hljs-comment"># to create .ssh folder</span>
chmod 700 ~/.ssh   <span class="hljs-comment"># set correct permissions</span>
touch ~/.ssh/authorized_keys  <span class="hljs-comment"># create file where we put our keys</span>
chmod 600 ~/.ssh/authorized_keys <span class="hljs-comment"># change file permissions</span>
</code></pre>
<p>Let's open another terminal, if you don't have ssh keys in your computer and create new one by running:</p>
<pre><code class="lang-bash">ssh-keygen -t rsa -b 4096 -C youremail@example.com
</code></pre>
<h2 id="heading-step-2-setup-and-secure-your-server">Step 2: Setup and Secure Your Server</h2>
<p>Before installing Mail-in-a-Box, it's crucial to properly secure your VPS. Let's lock down SSH access and configure the firewall to only allow necessary ports.</p>
<h3 id="heading-disable-password-authentication-for-ssh">Disable Password Authentication for SSH</h3>
<p>\==⚠️ <strong>Important</strong>: Ensure you've already set up SSH key authentication before disabling password login, or you'll lock yourself out of your server!==</p>
<p>Password-based SSH authentication is vulnerable to brute force attacks. Let's configure SSH to only accept key-based authentication:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Edit the SSH configuration file</span>
sudo nano /etc/ssh/sshd_config
</code></pre>
<p>Find and modify these settings (or add them if not present):</p>
<pre><code class="lang-bash">PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
</code></pre>
<p>Save the file and restart the SSH service:</p>
<pre><code class="lang-bash">sudo systemctl restart ssh
</code></pre>
<h3 id="heading-configure-firewall-to-allow-only-required-ports">Configure Firewall to Allow Only Required Ports</h3>
<p>Mail-in-a-Box needs specific ports open to function properly. Let's use UFW (Uncomplicated Firewall) to restrict access:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install UFW if not already installed</span>
sudo apt update &amp;&amp; sudo apt install ufw -y

<span class="hljs-comment"># set default policies</span>
sudo ufw default deny incoming
sudo ufw default allow outgoing

<span class="hljs-comment"># allow SSH (always do this first to avoid lockout)</span>
sudo ufw allow 22/tcp

<span class="hljs-comment"># allow essential Mail-in-a-Box ports</span>
sudo ufw allow 25/tcp    <span class="hljs-comment"># SMTP (mail delivery)</span>
sudo ufw allow 587/tcp   <span class="hljs-comment"># SMTP submission (outgoing mail)</span>
sudo ufw allow 465/tcp   <span class="hljs-comment"># SMTP over SSL (legacy secure mail)</span>
sudo ufw allow 143/tcp   <span class="hljs-comment"># IMAP</span>
sudo ufw allow 993/tcp   <span class="hljs-comment"># IMAP over SSL</span>
sudo ufw allow 110/tcp   <span class="hljs-comment"># POP3</span>
sudo ufw allow 995/tcp   <span class="hljs-comment"># POP3 over SSL</span>
sudo ufw allow 80/tcp    <span class="hljs-comment"># HTTP (for Let's Encrypt)</span>
sudo ufw allow 443/tcp   <span class="hljs-comment"># HTTPS (webmail and admin panel)</span>

<span class="hljs-comment"># Enable the firewall</span>
sudo ufw <span class="hljs-built_in">enable</span>
</code></pre>
<p>Verify your firewall configuration:</p>
<pre><code class="lang-bash">sudo ufw status verbose
</code></pre>
<p>You should see all the required ports listed as "ALLOW" in the output.</p>
<h2 id="heading-step-2-install-mail-in-a-box">Step 2: Install Mail-in-a-Box</h2>
<p>To install Mail-in-a-Box please run the following command:</p>
<pre><code class="lang-bash">curl -s https://mailinabox.email/setup.sh | sudo bash
</code></pre>
<p>Then follow the instructions shown in the instructions. You'll be asked to set your email. At first it will show us me@current-hostname so replace it with <a target="_blank" href="mailto:me@yourdomain.com">me@yourdomain.com</a>, make sure to replace <a target="_blank" href="http://yourdomain.com">yourdomain.com</a> with a domain name you control.</p>
<p>If you need to change later any of the answers you provided, you can run</p>
<pre><code class="lang-bash">sudo mailinabox
</code></pre>
<h2 id="heading-step-3-setup-dns-amp-domains">Step 3: Setup DNS &amp; Domains</h2>
<p>Go to your domain DNS dashboard, if you don't know how, please search "Godaddy DNS settings" replace with the name of Godaddy with the provider you use.</p>
<p>To find out what records to put in the DNS records. Go to <a target="_blank" href="http://box.yourdomain.com/admin">box.yourdomain.com/admin</a> then you'll see the rest of instructions by going to each menu and see places that have red error messages.</p>
<p>The most important places to check are the System, Mail &amp; Users dropdown in the top nav-bar of the page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740071271460/7e8f9a16-630c-4b30-a398-981372ba3e06.png" alt class="image--center mx-auto" /></p>
<p>Figer: 1</p>
<p>Some of the important subdomains you need to add in your domain name provider's DNS are the following:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Type</td><td>Hostname/Subdomain</td><td>Field: Value</td></tr>
</thead>
<tbody>
<tr>
<td>NS Record</td><td><a target="_blank" href="http://ns1.box.example.com">ns1.box.example.com</a></td><td><a target="_blank" href="http://box.example.com">box.example.com</a></td></tr>
<tr>
<td></td><td><a target="_blank" href="http://ns2.box.example.com">ns2.box.example.com</a></td><td><a target="_blank" href="http://box.example.com">box.example.com</a></td></tr>
<tr>
<td></td><td></td><td></td></tr>
<tr>
<td>MX Record</td><td><a target="_blank" href="http://box.example.com">box.example.com</a></td><td>Preference: 10  </td></tr>
</tbody>
</table>
</div><p>Subdomain: leave it empty |
|  |  |  |
| A/AAAA Record | <a target="_blank" href="http://box.example.com">box.example.com</a> | IP Address: SERVER-IP |
|  | <a target="_blank" href="http://autoconfig.example.com">autoconfig.example.com</a> | IP Address: SERVER-IP |
|  | <a target="_blank" href="http://autoconfig.box.example.com">autoconfig.box.example.com</a> | IP Address: SERVER-IP |
|  | <a target="_blank" href="http://ns1.box.example.com">ns1.box.example.com</a> | IP Address: SERVER-IP |
|  |  |  |
| TXT Record | <a target="_blank" href="http://example.com">example.com</a> | v=spf1 include:<a target="_blank" href="http://example.com">example.com</a>  include:<a target="_blank" href="http://box.example.com">box.example.com</a> -all |
|  | _dmarc | v=DMARC1; p=quarantine; rua=<a target="_blank" href="mailto:info@example.com">mailto:info@example.com</a>; pct=100; adkim=s; aspf=s |
|  | default._bimi | v=BIMI1;l=<a target="_blank" href="https://example.com/path/to/your/logo.svg;a=">https://example.com/path/to/your/logo.svg;a=</a> |
|  | s1._domainkey | Use Online Generator like <a target="_blank" href="http://easydmarc.com">easydmarc.com</a> to create what to put here. |
|  |  |  |</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740071357991/92ee0ead-6ee0-4b8f-a24e-25c1998bbb4b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740071379112/f470fb6e-2e2e-49b1-b5eb-bcaa42c0f114.png" alt class="image--center mx-auto" /></p>
<p>The above TXT records are important to make your email messages legitimate, else they'll be rejected by spam filters and end up in Junk/Spam folder instead of the Inbox.</p>
<p>Under System dropdown click TLS (SSL) Certificates then click Provision button to generate those certificates. After this succeeds you'll see most of red warnings change to green.</p>
<h2 id="heading-step-4-add-email-users">Step 4: Add Email Users</h2>
<p>To add a new domain you need to add the first email and this automatically saves the DNS records for the new domain name. Click on <em>Mail and Users</em> dropdown on the top nav-bar then click users. Fill the form.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740071406991/3c0eb9cb-8e2b-464f-ba15-50a52e26dfb6.png" alt class="image--center mx-auto" /></p>
<p>Normal user can only login through the email portal which is <a target="_blank" href="http://box.example.com/mail">box.example.com/mail</a> but Administrator can login as email user and admin user in <a target="_blank" href="http://box.example.com/admin">box.example.com/admin</a>.</p>
<h2 id="heading-testing-your-implementation">Testing Your Implementation</h2>
<p>To find out if your email has an issue or is rejected by Gmail or Microsoft you'll receive rejected email notification to the email you have put in your DNS <code>_dmarc</code> record entry. If everything is working you'll be able to send emails to all providers.</p>
<p>To use your new emails in email apps use the Instructions menu under <em>Mail &amp; Users</em> dropdown. That page lists all the ways you can use your emails on mobile and desktop apps.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<ul>
<li><p>We have created our self-hosted email service</p>
</li>
<li><p>We also secured our server</p>
</li>
<li><p>And added extra TXT DNS entries to make our emails legitimate and avoid them to end up in spam box.</p>
</li>
</ul>
<h2 id="heading-further-resources">Further Resources</h2>
<ul>
<li><p><a target="_blank" href="https://mailinabox.email">Mail-in-a-Box</a> Official Website</p>
</li>
<li><p><a target="_blank" href="https://github.com/mail-in-a-box/mailinabox">Mail-in-a-Box Github</a> Repository</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Setup your self-hosted MySQL on AWS EC2 Ubuntu]]></title><description><![CDATA[Introduction
AWS has a MySQL compatible database but it's expensive and sometimes the cost doesn't justify the convenience we get from having such a service for personal projects or projects that don't make money.
The alternative solution is to have ...]]></description><link>https://blog.mahad.dev/setup-your-self-hosted-mysql-on-aws-ec2-ubuntu</link><guid isPermaLink="true">https://blog.mahad.dev/setup-your-self-hosted-mysql-on-aws-ec2-ubuntu</guid><category><![CDATA[MySQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[Linux]]></category><category><![CDATA[MySQL Workbench]]></category><category><![CDATA[debian]]></category><category><![CDATA[ssh]]></category><category><![CDATA[rds]]></category><category><![CDATA[aws ec2]]></category><category><![CDATA[AWS RDS]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Sat, 25 Jan 2025 17:52:47 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>AWS has a MySQL compatible database but it's expensive and sometimes the cost doesn't justify the convenience we get from having such a service for personal projects or projects that don't make money.</p>
<p>The alternative solution is to have an EC2 instance that runs MySQL instance which is going to be way cheaper but needs your maintenance. You'll need to do backups and other chores such scaling all the manual tasks.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You need to either have secret .pem file to ssh to the EC2. Also make sure the port <code>3306</code> is enabled in your EC2 Security group rules. We assume your EC2 is running Ubuntu Linux.</p>
<h2 id="heading-step-1-install-packages">Step 1: Install packages</h2>
<p>Before we do anything I'm going to install <code>tmux</code> because it's great for running long-running process without canceling the process. For example, you are in the airport want to initiate some task that takes 15 minutes but the gate closes in 10 minutes so you need to start the process and close your terminal and move away. If you're not convinced, you can skip the following commands.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># update everytime before installing anything</span>
sudo apt update

sudo apt install tmux <span class="hljs-comment"># debian based linux</span>

<span class="hljs-comment"># start tmux</span>
tmux
</code></pre>
<p>Install mysql-server package</p>
<pre><code class="lang-bash">sudo apt install mysql-server
</code></pre>
<h2 id="heading-step-2-register-services">Step 2: Register services</h2>
<p>Add mysql to the background services so whenever the server restarts it will start too.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># start the service</span>
sudo systemctl start mysql.service
</code></pre>
<p><strong>Warning:</strong> you might get error <code>Failed with result 'oom-kill'</code> if that happens, you might need to upgrade to an EC2 instance with more RAM. I couldn't make it work on <code>t4g.nano</code> instance because it has less than 1GB RAM. To upgrade the EC2 instance, you'll need to stop it first by doing the following, (1) click in the top right <strong>Instance state</strong> dropdown &gt; click <strong>Stop instance</strong>. (2) Click the next dropdown <strong>Actions</strong> &gt; <strong>Instance Settings</strong> &gt; <strong>Change instance type</strong>.</p>
<h2 id="heading-step-3-configure-mysql">Step 3: Configure MySQL</h2>
<p>When installation is complete, the mysql instance is not secure, so we need to make few changes to make it more secure.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># let's connect to it fisrt</span>
sudo mysql
</code></pre>
<p>Set password for root user.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">USER</span> <span class="hljs-string">'root'</span>@<span class="hljs-string">'localhost'</span> <span class="hljs-keyword">IDENTIFIED</span> <span class="hljs-keyword">WITH</span> mysql_native_password <span class="hljs-keyword">BY</span> <span class="hljs-string">'your-password'</span>
mysql&gt; <span class="hljs-keyword">exit</span>;
</code></pre>
<p>So, now we have set the password for the root user of our database, we need to run a built-in script for completing the steps to securing the database.</p>
<p>The <code>mysql_secure_installation</code> script takes you through steps by prompting you available security options.</p>
<pre><code class="lang-bash">sudo mysql_secure_installation
</code></pre>
<p>Make sure you choose your security options wisely.</p>
<h4 id="heading-create-user-amp-database">Create User &amp; Database</h4>
<p>To access database it's good idea to use non-root user, which is why I prefer to use different user(s) for different databases/schemas. So, let's create a user <code>db_user</code> with a password (make sure you replace it with your password)</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">USER</span> <span class="hljs-string">`db_user`</span>@<span class="hljs-string">`%`</span> <span class="hljs-keyword">IDENTIFIED</span> <span class="hljs-keyword">WITH</span> caching_sha2_password <span class="hljs-keyword">BY</span> <span class="hljs-string">'your-password'</span>;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> <span class="hljs-string">`db_name`</span>;

<span class="hljs-keyword">GRANT</span> <span class="hljs-keyword">ALL</span> <span class="hljs-keyword">PRIVILEGES</span> <span class="hljs-keyword">ON</span> <span class="hljs-string">`db_name`</span>.* <span class="hljs-keyword">TO</span> <span class="hljs-string">`db_user`</span>@<span class="hljs-string">`%`</span>;
</code></pre>
<p>The <code>%</code> part in <code>db_user</code>@<code>%</code> means we can connect to this database from any machine that can connect to our EC2 instance.</p>
<h2 id="heading-step-4-connect-from-other-ec2">Step 4: Connect from other EC2</h2>
<p>You need to get your EC2 domain or IP to connect to the database.</p>
<pre><code class="lang-bash">mysql -h YOUR-EC2-IP -P 3306 -u db_user -p
</code></pre>
<p>Then you need to type your password after the prompt:</p>
<pre><code class="lang-sql">Enter password:
</code></pre>
<p>If you are using <em>MySQL Workbench</em> to access your databases, you can connect through SSH.</p>
<ol>
<li><p>Click + icon near <strong>MySQL Connections</strong></p>
</li>
<li><p>Click the <em>Connection Method</em> dropdown</p>
</li>
<li><p>Select the <strong>Standard TCP/IP over SSH</strong> option</p>
</li>
<li><p>Fill EC2 IP as the the <strong>SSH Hostname</strong></p>
</li>
<li><p>Fill EC2 SSH username</p>
</li>
<li><p>Click the [...] button on the right of <strong>SSH Key File</strong> input, then Select <code>.pem</code> file we used to ssh to the EC2</p>
</li>
<li><p>The rest are Database Hostname, Username and password. These are the same as if you were connecting locally.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737827052858/019adc08-23a5-407e-959d-483372905eb4.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This solution is great for hosting your personal databases or projects that don't make money for you. If you make enough money from your project and the price is not too much for your revenue, I would suggest you use managed database server such as RDS or similar offerings from other cloud providers.</p>
]]></content:encoded></item><item><title><![CDATA[How to deploy Next.js to EC2 Ubuntu Linux]]></title><description><![CDATA[How to Setup EC2 for Dockerized Next.js Deployment
Introduction
Next.js is great serve-side oriented frontend framework that makes building SEO-friendly websites easier.
Deploying it is trickier than it needs to be, I understand Vercel needs to make ...]]></description><link>https://blog.mahad.dev/how-to-deploy-nextjs-to-ec2-ubuntu-linux</link><guid isPermaLink="true">https://blog.mahad.dev/how-to-deploy-nextjs-to-ec2-ubuntu-linux</guid><category><![CDATA[Next.js]]></category><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Linux]]></category><category><![CDATA[nginx]]></category><category><![CDATA[Let's Encrypt]]></category><category><![CDATA[certbot]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Dockerfile]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[Ubuntu 24.04]]></category><category><![CDATA[Ubuntu 24.04 LTS]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Thu, 23 Jan 2025 22:54:41 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-how-to-setup-ec2-for-dockerized-nextjs-deployment">How to Setup EC2 for Dockerized Next.js Deployment</h1>
<h2 id="heading-introduction">Introduction</h2>
<p>Next.js is great serve-side oriented frontend framework that makes building SEO-friendly websites easier.</p>
<p>Deploying it is trickier than it needs to be, I understand Vercel needs to make money off of their creation. If you need an alternative way to deploy your website, dockerizing it will remove few of the headaches.</p>
<p>In this example, I'm using AWS EC2 for deployment but most of the steps would work on any other Linux VPS (Like <a target="_blank" href="https://www.linode.com/lp/refer/?r=4169f7eb6d93952d4d05c8d100f8a4b45677ebc6">Linode</a> and etc)</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>AWS EC2 instance or any Linux server through SSH</p>
</li>
<li><p>Basic understanding of:</p>
<ul>
<li><p>Docker</p>
</li>
<li><p>Next.js</p>
</li>
<li><p>AWS EC2, make sure you have .pem file for authentication over SSH</p>
</li>
<li><p>Command-line interface</p>
</li>
<li><p>SSH client</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-step-1-prepare-your-nextjs-application">Step 1: Prepare Your Next.js Application</h2>
<p>Create <code>Dockerfile</code> for Next.js application with the following content:</p>
<pre><code class="lang-Dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine AS base

<span class="hljs-comment"># install dependencies only when needed</span>
<span class="hljs-keyword">FROM</span> base AS deps
<span class="hljs-comment"># Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache libc6-compat</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># install dependencies based on the preferred package manager</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> \
    <span class="hljs-keyword">if</span> [ -f yarn.lock ]; <span class="hljs-keyword">then</span> yarn --frozen-lockfile; \
    <span class="hljs-keyword">elif</span> [ -f package-lock.json ]; <span class="hljs-keyword">then</span> npm ci; \
    <span class="hljs-keyword">elif</span> [ -f pnpm-lock.yaml ]; <span class="hljs-keyword">then</span> corepack <span class="hljs-built_in">enable</span> pnpm &amp;&amp; pnpm i --frozen-lockfile; \
    <span class="hljs-keyword">else</span> <span class="hljs-built_in">echo</span> <span class="hljs-string">"Lockfile not found."</span> &amp;&amp; <span class="hljs-built_in">exit</span> 1; \
    <span class="hljs-keyword">fi</span></span>


<span class="hljs-comment"># rebuild the source code only when needed</span>
<span class="hljs-keyword">FROM</span> base AS builder
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=deps /app/node_modules ./node_modules</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> \
    <span class="hljs-keyword">if</span> [ -f yarn.lock ]; <span class="hljs-keyword">then</span> yarn run build; \
    <span class="hljs-keyword">elif</span> [ -f package-lock.json ]; <span class="hljs-keyword">then</span> npm run build; \
    <span class="hljs-keyword">elif</span> [ -f pnpm-lock.yaml ]; <span class="hljs-keyword">then</span> corepack <span class="hljs-built_in">enable</span> pnpm &amp;&amp; pnpm run build; \
    <span class="hljs-keyword">else</span> <span class="hljs-built_in">echo</span> <span class="hljs-string">"Lockfile not found."</span> &amp;&amp; <span class="hljs-built_in">exit</span> 1; \
    <span class="hljs-keyword">fi</span></span>

<span class="hljs-comment"># Production image, copy all the files and run next</span>
<span class="hljs-keyword">FROM</span> base AS runner
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">ENV</span> NODE_ENV=production

<span class="hljs-keyword">RUN</span><span class="bash"> addgroup --system --gid 1001 nodejs</span>
<span class="hljs-keyword">RUN</span><span class="bash"> adduser --system --uid 1001 nextjs</span>

<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/public ./public</span>

<span class="hljs-comment"># Set the correct permission for prerender cache</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir .next</span>
<span class="hljs-keyword">RUN</span><span class="bash"> chown nextjs:nodejs .next</span>
<span class="hljs-keyword">RUN</span><span class="bash"> chmod -R +x ./</span>

<span class="hljs-comment"># Automatically leverage output traces to reduce image size</span>
<span class="hljs-comment"># https://nextjs.org/docs/advanced-features/output-file-tracing</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder --chown=nextjs:nodejs /app/.next/standalone ./</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static</span>

<span class="hljs-keyword">USER</span> nextjs

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>

<span class="hljs-keyword">ENV</span> PORT=<span class="hljs-number">3000</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"server.js"</span>]</span>
</code></pre>
<h2 id="heading-step-2-configure-and-install-docker">Step 2: Configure and Install Docker</h2>
<p>Let's connect to the EC2 instance using SSH.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># connect through ssh</span>
ssh -i your-key-file.pem ec2-user2@ec2-YOUR-PUBLIC-IP.us-east-1.compute.amazonaws.com
</code></pre>
<p>Install tools</p>
<pre><code class="lang-bash"><span class="hljs-comment"># update the packages before installing</span>
sudo apt update

<span class="hljs-comment"># install prerequisite packages</span>
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

<span class="hljs-comment"># add the GPG key for the official Docker repo</span>
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

<span class="hljs-comment"># add repo to APT sources</span>
sudo add-apt-repository <span class="hljs-string">"deb [arch=amd64] https://download.docker.com/linux/ubuntu <span class="hljs-subst">$(lsb_release -cs)</span> stable"</span>

<span class="hljs-comment"># update packages again</span>
sudo apt update

<span class="hljs-comment"># reset apt cache to use the official repo instead of ubuntu sources</span>
apt-cache policy docker-ce

<span class="hljs-comment"># start the actual installation</span>
sudo apt install -y docker-ce

<span class="hljs-comment"># to verify the installation was successful, try</span>
docker -v <span class="hljs-comment"># prints the installed docker's version details</span>

<span class="hljs-comment"># check if the docker service is running</span>
sudo systemctl status docker.service

<span class="hljs-comment"># if not running, start it</span>
sudo systemctl start docker.service
</code></pre>
<p>In case you hate prefixing every docker command with <code>sudo</code> you can do the following:</p>
<pre><code class="lang-bash">sudo usermod -aG docker <span class="hljs-variable">$USER</span>

<span class="hljs-comment"># run this to refresh the current terminal session</span>
su - <span class="hljs-variable">$USER</span>
</code></pre>
<h2 id="heading-step-3-deploy-docker-image-to-docker-hub-or-github-containers">Step 3: Deploy Docker Image to Docker hub or Github containers</h2>
<p>To deploy the image of your dockerized project, you need to push it to a container repo like Docker Hub or Github containers. In this example, I'm using Github containers.</p>
<p>First we need to authenticate our local docker with our Github containers repo. We need an API token to use as password with <code>docker login</code>.</p>
<ul>
<li><p>Go to <a target="_blank" href="http://Gihtub.com"><strong>Gihtub.com</strong></a> Click on your profile picture on the top right.</p>
</li>
<li><p>Click <strong>Settings</strong></p>
</li>
<li><p>Click <strong>Developer settings</strong> at the bottom of the left sidebar</p>
</li>
<li><p>Click <strong>Personal access tokens</strong> to expand</p>
</li>
<li><p>Click <strong>Tokens (classic)</strong></p>
</li>
<li><p>Click <strong>Generate new token</strong> dropdown</p>
</li>
<li><p>Click <strong>Generate new token (classic)</strong> option</p>
</li>
<li><p>Type the name of your project in the Node input</p>
</li>
<li><p>Check <strong>write:packages</strong> and <strong>delete:packages</strong> checkboxes</p>
</li>
<li><p>Finally click <strong>Generate token</strong> button near the bottom to complete this</p>
</li>
</ul>
<pre><code class="lang-bash">sudo docker login ghcr.io <span class="hljs-comment"># then give your github </span>
Username: your-github-username
Password: api-token-generated-above
</code></pre>
<p>Note: the generated API key is only shown once, so you need to keep it in a safe place to use it later to login to your Github container repository.</p>
<p>Let's create the images to deploy to our container repo we setup above.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># the --platform linux/amd64 is needed in case your machine is arm based, like Apple M-series or Snapdragon laptops.</span>
docker build --platform linux/amd64 -t ghcr.io/your-github-username/project-name:v0.0.1 .
</code></pre>
<p>To use the image locally, let's stop if there any previous image running:</p>
<pre><code class="lang-bash">docker stop my-container-name <span class="hljs-comment"># skip if it's the first time</span>
docker rm my-container-name <span class="hljs-comment"># skip the first time</span>

<span class="hljs-comment"># to run</span>
docker run --rm --name my-container-name -d -p 3000:3000 ghcr.io/your-github-username/project-name:v0.0.1
</code></pre>
<p>After testing and making sure we have the container working as expected, let's push it to Github so we can pull from our EC2 server.</p>
<pre><code class="lang-bash">docker push ghcr.io/your-github-username/project-name:v0.0.1
</code></pre>
<p>Let's connect to the EC2 thourgh SSH again if not still connected:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># connect through ssh</span>
ssh -i your-key-file.pem ec2-user2@ec2-YOUR-PUBLIC-IP.us-east-1.compute.amazonaws.com
</code></pre>
<p>Let's pull the docker image from github. <strong>NOTE:</strong> you need to login using the above <code>docker login</code> step and use the same username and API-token as the password.</p>
<pre><code class="lang-bash">sudo docker pull ghcr.io/your-github-username/project-name:v0.0.1
</code></pre>
<p>Then let's run a container from the docker image we downloaded.</p>
<pre><code class="lang-bash">sudo docker run --rm --name my-container-name -d -p 3000:3000 ghcr.io/your-github-username/project-name:v0.0.1
</code></pre>
<h2 id="heading-step-4-implement-production-optimizations">Step 4: Implement Production Optimizations</h2>
<h4 id="heading-setup-nginx">Setup NGINX</h4>
<p>If you don't already have nginx installed, let’s quickly do that</p>
<pre><code class="lang-bash">sudo apt update
sudo apt install -y nginx
</code></pre>
<p>Let's create a config file for our site for nginx to the HTTP handle requests</p>
<pre><code class="lang-bash">sudo touch /etc/nginx/sites-available/my-site-domain.com
sudo nano /etc/nginx/sites-available/my-site-domain.com
</code></pre>
<p>Then copy the following content in the file you just opened.</p>
<pre><code class="lang-bash">server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _  www.my-site-domain.com.com my-site-domain.com.com;
    error_log /var/<span class="hljs-built_in">log</span>/nginx/error.log warn;
    access_log /var/<span class="hljs-built_in">log</span>/nginx/access.log;

    <span class="hljs-comment"># root /var/www/html;</span>
    root /var/www/html;

    location / {
          proxy_pass      http://127.0.0.1:3000;
          proxy_set_header Host <span class="hljs-variable">$http_host</span>;
          proxy_redirect off;
          proxy_set_header X-Forwarded-For <span class="hljs-variable">$proxy_add_x_forwarded_for</span>;
          <span class="hljs-comment"># try_files $uri $uri/ =404;</span>
    }
}
</code></pre>
<p>We need to link the config file to a file in <code>sites-enabled</code> folder. Creating a link file helps you keep the config file and be able to enable/disable that config by creating or removing the link file, while keeping the actual config file in <code>sites-available</code>.</p>
<pre><code class="lang-bash">sudo ln -s /etc/nginx/sites-available/my-site-domain.com /etc/nginx/sites-enabled/my-site-domain.com
</code></pre>
<h4 id="heading-generate-ssl-certificates-with-certbot">Generate SSL Certificates with Certbot</h4>
<p>There are other ways to setup SSL on AWS but I like a portable solution that can be used on any Linux VPS. So, we're going to use Let's Encrypt Certbot tool to generate SSL certificates.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install certbot</span>
sudo snap install --classic certbot

<span class="hljs-comment"># create a link for the certbot binary</span>
sudo ln -s /snap/bin/certbot /usr/bin/certbot

<span class="hljs-comment"># let's run the certbot command</span>
sudo certbot --nginx
</code></pre>
<h2 id="heading-common-troubleshooting">Common Troubleshooting</h2>
<p>The issue I always find is when forget to prefix <code>sudo</code> in docker commands, so if you didn't add docker user to the sudo group, you might get an issue like</p>
<pre><code class="lang-bash">permission denied <span class="hljs-keyword">while</span> trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:
</code></pre>
<p>The error doesn't give a suggestion to why this is happening so you might need to run the command again with <code>sudo</code> or follow last part of installing docker above.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Deploying Next.js apps isn't hard as we showed above but there are no clear guides available. This was the only way that worked for me and I thought I have to share with the community as it might save you sometime. If you have any suggestions of how we can improve the above guide, please feel free to share them.</p>
<h2 id="heading-additional-resources">Additional Resources</h2>
<ul>
<li><p><a target="_blank" href="https://docs.docker.com/engine/install/ubuntu/">Docker documentation</a></p>
</li>
<li><p><a target="_blank" href="https://nextjs.org/docs/pages/building-your-application/deploying">Next.js deployment guides</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/ec2/">AWS EC2 documentation</a></p>
</li>
<li><p><a target="_blank" href="https://certbot.eff.org/instructions?ws=nginx&amp;os=ubuntufocal">Certbot Ubuntu Nginx Guide</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Package your SaaS frontend for self-hosting using Golang]]></title><description><![CDATA[Sometimes you need to allow for self-hosted subscription of your SaaS whether it's because your client has their own air-gapped system that is not connected to the internet or they want to host it in their on-premise servers. Having the option is alw...]]></description><link>https://blog.mahad.dev/package-your-saas-frontend-for-self-hosting-using-golang</link><guid isPermaLink="true">https://blog.mahad.dev/package-your-saas-frontend-for-self-hosting-using-golang</guid><category><![CDATA[golang]]></category><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Static Website]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[#howtos]]></category><category><![CDATA[arm64]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Linux]]></category><category><![CDATA[macOS]]></category><category><![CDATA[ARM]]></category><category><![CDATA[executable-file]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Wed, 22 Jan 2025 14:01:58 GMT</pubDate><content:encoded><![CDATA[<p>Sometimes you need to allow for self-hosted subscription of your SaaS whether it's because your client has their own air-gapped system that is not connected to the internet or they want to host it in their on-premise servers. Having the option is always a good thing.</p>
<h3 id="heading-why-do-you-need-a-single-executable">Why do you need a single Executable?</h3>
<p>Why don't you share the whole dist folder as a zip so it can be hosted by the client? sharing the actual files increases exposure of your code to be reverse engineered. It will also be easier for people to change the code, for example, to avoid paying your subscription. An experienced hacker will still find a way but this will create a new layer of security because your javascript files are not visible in the host machine.</p>
<p>I'm using React as an example for this demo but you can use your favorite frontend stack by making the necessary go code changes. If you are interested on seeing your favorite stack deployed this way, please leave a comment below.</p>
<h3 id="heading-lets-setup-a-demo-react-app">Let's setup a demo React app</h3>
<p>Please skip to the next section if you already have your react app set up. In this example, we are using <code>vite</code> to create the react app.</p>
<pre><code class="lang-bash">npm create vite@latest react-example-app -- --template react
</code></pre>
<p>Let's go in the newly created react project and complete the setup.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react-example-app
npm install <span class="hljs-comment"># install dependencies</span>
npm run dev <span class="hljs-comment"># run the app locally</span>
</code></pre>
<p>Let's go on the default URL on <a target="_blank" href="http://localhost:5173">http://localhost:5173</a> to see if our react app is running.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737554191144/e7867afb-71c0-456e-ae7e-2308f170f74b.png" alt class="image--center mx-auto" /></p>
<p>Now we have the app running, let's setup a go package, this is only a single file go project so you can create it within your frontend project.</p>
<p>Let's create a <code>serve.go</code> file at the root of our project, then add the following content:</p>
<pre><code class="lang-go"><span class="hljs-comment">// serve.go</span>
<span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"embed"</span>
    <span class="hljs-string">"html/template"</span>
    <span class="hljs-string">"io/fs"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
)

<span class="hljs-comment">// this contains special comments that instruct the go compiler to embed the files within the executable.</span>
<span class="hljs-keyword">var</span> (
    <span class="hljs-comment">//go:embed dist/*</span>
    dist embed.FS

    <span class="hljs-comment">//go:embed dist/index.html</span>
    indexHTML <span class="hljs-keyword">string</span>

    <span class="hljs-comment">//go:embed dist/assets/*</span>
    assets embed.FS

    IndexTemplate *template.Template
)

<span class="hljs-comment">// this is called before any other code in this file runs</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// setup HTML renderer with the embedded index.html</span>
    IndexTemplate = template.New(<span class="hljs-string">"index.html"</span>)
    <span class="hljs-keyword">var</span> err error
    IndexTemplate, err = IndexTemplate.Parse(indexHTML)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// create gin router</span>
    router := gin.Default()
    <span class="hljs-comment">// set the embedded index.html as the go template</span>
    router.SetHTMLTemplate(IndexTemplate)

    <span class="hljs-comment">// this gives you the option the change the port after the executable is created</span>
    port := os.Getenv(<span class="hljs-string">"PORT"</span>)
    <span class="hljs-keyword">if</span> port == <span class="hljs-string">""</span> {
        port = <span class="hljs-string">"8080"</span>
    }

    <span class="hljs-comment">// static files in dist/assets</span>
    assetsFs, err := fs.Sub(assets, <span class="hljs-string">"dist/assets"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-comment">// serve the contents of assets folder in /assets URL path</span>
    router.StaticFS(<span class="hljs-string">"/assets"</span>, http.FS(assetsFs))

    <span class="hljs-comment">// handle all routes</span>
    router.NoRoute(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> if you're running an API alongside your frontend, you'll want to do the following to skip the /api endppoints</span>
        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(c.Request.URL.Path) &gt;= <span class="hljs-number">4</span> &amp;&amp; c.Request.URL.Path[:<span class="hljs-number">4</span>] == <span class="hljs-string">"/api"</span> {
            c.Next()
            <span class="hljs-keyword">return</span>
        }

        c.HTML(http.StatusOK, <span class="hljs-string">"index.html"</span>, gin.H{})
    })

    log.Printf(<span class="hljs-string">"Server is running on port %s"</span>, port)
    <span class="hljs-keyword">if</span> err := router.Run(<span class="hljs-string">":"</span> + port); err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Server failed to start: %v"</span>, err)
    }
}
</code></pre>
<p>Before we can run the go code, we need to initialize the go package. We named it <code>web-go-server</code> but you can call it anything you want.</p>
<pre><code class="lang-bash">go mod init web-go-server <span class="hljs-comment"># sets up the required files for t</span>
go mod tidy <span class="hljs-comment"># to initialize imported packages in go.sum, this is like the package.json for go projects.</span>
</code></pre>
<p>Let's test what we have so far.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># make sure you build the fontend, or there will be no dist folder to embed in the executable.</span>
<span class="hljs-comment"># you'll get this error if you skip this step:</span>
<span class="hljs-comment"># serve.go:15:13: pattern dist/*: no matching files found</span>
npm run build

<span class="hljs-comment"># then run the go code</span>
go run .
</code></pre>
<p>Now we have our frontend served through our go code, the static files are going to be embedded within the executable file so we'll leave the <code>dist</code> folder behind and don't need to share alongside the executable.</p>
<p>As you can see in the screenshot below, we are missing the <code>vite.svg</code> file this is from the public folder, we can't serve the contents of the whole dist folder as the root because that would conflict with the index.html we serve from the root path <code>/</code> to work around this we need to move the <code>public/vite.svg</code> to <code>src/assets/vite.svg</code> you do the same for any other static file you have in the public folder. In case you don't want to move any file to the assets place them in <code>public/static</code> so we can modify the go code to serve that as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737554301298/4db38d04-cae6-4797-9611-4461dc2ea10e.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-go"><span class="hljs-comment">// static files in dist/static</span>
staticFs, err := fs.Sub(dist, <span class="hljs-string">"dist/static"</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-built_in">panic</span>(err)
}

router.StaticFS(<span class="hljs-string">"/static"</span>, http.FS(staticFs))
</code></pre>
<p>Then edit anywhere the static file is referred to in your frontend code and make sure all images are showing (Screenshot below).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737554332459/e13c9934-4ac6-4511-9cc9-d1b6e0cb257f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-do-we-have">What do we have?</h3>
<p>This is how the whole project is structured:</p>
<pre><code class="lang-bash">.
├── README.md
├── eslint.config.js
├── go.mod
├── go.sum
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│   └── vite.svg
├── src
│   ├── App.css
│   ├── App.jsx
│   ├── assets
│   │   └── react.svg
│   ├── index.css
│   └── main.jsx
├── vite.config.js
└── serve.go
</code></pre>
<h3 id="heading-lets-create-the-executable-binary-file">Let's create the executable binary file</h3>
<p>After you have everything setup and everything is working when you run <code>go run .</code> you need to create the executable you share with your clients.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># run the fontend build again to make sure no changes left out</span>
npm run build

<span class="hljs-comment"># build the go executable</span>
go build -o your-app-name .
</code></pre>
<h3 id="heading-to-build-for-multiple-platforms">To build for multiple platforms:</h3>
<p>The above command creates an executable that runs on your current machine and any machine that is using the same CPU architecture but Golang allows you to cross-compile to all supported CPUs using your current machine.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># for windows</span>
GOOS=windows GOARCH=amd64 go build -o your-app-name.exe .

<span class="hljs-comment"># for linux x86</span>
GOOS=linux GOARCH=amd64 go build -o your-app-name .

<span class="hljs-comment"># for linux arm64 like rasbperry pi</span>
GOOS=linux GOARCH=arm64 go build -o your-app-name .

<span class="hljs-comment"># for macos intel x86 64</span>
GOOS=darwin GOARCH=amd64 go build -o your-app-name .

<span class="hljs-comment"># for macos Apple Silicon</span>
GOOS=darwin GOARCH=arm64 go build -o your-app-name .
</code></pre>
<p>I hope this helped you ship your SaaS fast using this method, if you like this kind of content please feel free to follow me and subscribe to my newsletter.</p>
]]></content:encoded></item><item><title><![CDATA[How to Change Windows 11 to Dark theme from CLI?]]></title><description><![CDATA[I'm running windows 11 on a virtual machine to do few tests and I wanted to change the theme but in order to do that you need to activate your copy of Windows.
I don't think it's feasible to buy a license to test few things in a virtual machine.
If y...]]></description><link>https://blog.mahad.dev/how-to-change-windows-11-to-dark-theme-from-cli</link><guid isPermaLink="true">https://blog.mahad.dev/how-to-change-windows-11-to-dark-theme-from-cli</guid><category><![CDATA[Windows]]></category><category><![CDATA[theme]]></category><category><![CDATA[change]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[cmd]]></category><category><![CDATA[Script]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Mon, 20 Jan 2025 21:12:20 GMT</pubDate><content:encoded><![CDATA[<p>I'm running windows 11 on a virtual machine to do few tests and I wanted to change the theme but in order to do that you need to activate your copy of Windows.</p>
<p>I don't think it's feasible to buy a license to test few things in a virtual machine.</p>
<p>If you are in the same situation as me and hate light mode because it hurts your eyes, here is a single command to change the system theme to dark.</p>
<h3 id="heading-step-1-open-windows-powershell">Step 1: Open Windows Powershell</h3>
<p>Click the start button or press Window key, then type powershell and click Window Powershell</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737407308281/ba19aeab-d024-484a-b585-7f8942d6ead5.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-2-copy-command-to-powershell">Step 2: Copy Command to Powershell</h3>
<p>Paste the command below in the Powershell Window and press Enter to run the command.</p>
<pre><code class="lang-powershell">rundll32.exe themecpl.dll,OpenThemeAction C:\Windows\resources\themes\dark.theme
</code></pre>
<h3 id="heading-changed-your-mind">Changed your mind?</h3>
<p>If you don't want this theme and need to use a different one, check the available themes using this command:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">dir</span> C:\Windows\resources\themes\*.theme
</code></pre>
<p>Then replace the last part of the previous command <code>dark.theme</code> with whatever theme you want. For example, you want to use themeC.theme instead:</p>
<pre><code class="lang-powershell">rundll32.exe themecpl.dll,OpenThemeAction C:\Windows\resources\themes\themeA.theme
</code></pre>
<p>NOTE: You might need to restart and rerun the command for changes to take effect.</p>
]]></content:encoded></item><item><title><![CDATA[It's Simple to Build Your Own Email/Password Authentication REST API (Express.js)]]></title><description><![CDATA[This is based on a previous blog post using Golang, so anyone that is interested how to do this in Go, please check it here
Sometimes, building your own authentication REST API sounds scary but in this post, I'll show you how it's done using Node.js ...]]></description><link>https://blog.mahad.dev/its-simple-to-build-your-own-emailpassword-authentication-rest-api-expressjs</link><guid isPermaLink="true">https://blog.mahad.dev/its-simple-to-build-your-own-emailpassword-authentication-rest-api-expressjs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[authentication]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Bcrypt ]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Mon, 20 Jan 2025 16:06:05 GMT</pubDate><content:encoded><![CDATA[<p>This is based on a previous blog post using Golang, so anyone that is interested how to do this in Go, please check it <a target="_blank" href="https://blog.mahad.dev/its-simple-to-build-your-own-emailpassword-authentication-rest-api">here</a></p>
<p>Sometimes, building your own authentication REST API sounds scary but in this post, I'll show you how it's done using Node.js and Express.js. In this post you'll see how to build your own email/password authentication system.</p>
<h3 id="heading-setting-up-your-nodejs-project">Setting up your Node.js project</h3>
<p>Let's initialize the express.js project</p>
<pre><code class="lang-bash">mkdir example-auth-api
<span class="hljs-built_in">cd</span> example-auth-api
npm init -y
npm install express bcrypt jsonwebtoken body-parser
</code></pre>
<h3 id="heading-setting-up-expressjs-endpoints">Setting up Express.js endpoints</h3>
<p>Let's import all the required libraries</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> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bcrypt'</span>);
<span class="hljs-keyword">const</span> bodyParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'body-parser'</span>);
</code></pre>
<p>Then let's instantiate our express app.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> app = express();
app.use(bodyParser.json());
</code></pre>
<p>Let's add <code>signUp</code>, <code>login</code> as public routes and <code>userProfile</code> protected endpoint.</p>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">'/signup'</span>, signUp);
app.post(<span class="hljs-string">'/login'</span>,login);
app.get(<span class="hljs-string">'/users'</span>, authMiddleware, userProfile);
</code></pre>
<p>Now, let's create the <code>signUp</code> endpoint handler.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> signUp = <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { email, password } = req.body;

        <span class="hljs-comment">// check if a user with the current email exists</span>
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> getUserByEmail(email);
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'User with this email already exists'</span> });
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-comment">// this means we can continue with signup</span>
        }

        <span class="hljs-comment">// hash the password</span>
        <span class="hljs-keyword">const</span> hashedPassword = <span class="hljs-keyword">await</span> bcrypt.hash(password, HASH_COST);

        <span class="hljs-comment">// save the user</span>
        <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> User(<span class="hljs-built_in">Date</span>.now(), email, hashedPassword);
        <span class="hljs-keyword">await</span> CreateUserInDb(user);

        res.status(<span class="hljs-number">201</span>).json({
            <span class="hljs-attr">message</span>: <span class="hljs-string">'User created successfully'</span>,
            <span class="hljs-attr">email</span>: user.email
        });
    } <span class="hljs-keyword">catch</span> (err) {
        res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Failed to create user'</span> });
    }
}
</code></pre>
<p>Now, let's create <code>login</code> endpoint handler.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> login = <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { email, password } = req.body;

        <span class="hljs-comment">// Get user by email</span>
        <span class="hljs-keyword">let</span> user;
        <span class="hljs-keyword">try</span> {
            user = <span class="hljs-keyword">await</span> getUserByEmail(email);
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid email or password'</span> });
        }

        <span class="hljs-comment">// Verify password</span>
        <span class="hljs-keyword">const</span> isValid = <span class="hljs-keyword">await</span> bcrypt.compare(password, user.password);
        <span class="hljs-keyword">if</span> (!isValid) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid email or password'</span> });
        }

        <span class="hljs-comment">// Generate tokens</span>
        <span class="hljs-keyword">const</span> { token, refreshToken } = generateToken(user);

        res.status(<span class="hljs-number">200</span>).json({
            <span class="hljs-attr">message</span>: <span class="hljs-string">'Login successful'</span>,
            token,
            refreshToken
        });
    } <span class="hljs-keyword">catch</span> (err) {
        res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid email or password'</span> });
    }
}
</code></pre>
<p>This endpoint verifies the user's credentials and generates a token. In production, use a library to create a secure JWT token and hashing passwords.</p>
<p>And finally, the <code>userProfile</code> endpoint handler.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userProfile = <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> userId = req.userId;
        <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> GetUserByID(userId);
        res.json({ user });
    } <span class="hljs-keyword">catch</span> (err) {
        res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Access Denied!'</span> });
    }
}
</code></pre>
<h3 id="heading-protecting-private-routes">Protecting Private Routes</h3>
<p>In order to protect our private routes we need to use a middleware that keeps non-authenticated HTTP calls to our protected rourtes. As an example, we have 1 protected route that returns the current user's details. Note: it's not recommended to return the full details of the user, this might include sensitive data such as password hash or other unintended leaks.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> authMiddleware = <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> authHeader = req.headers.authorization;
    <span class="hljs-keyword">if</span> (!authHeader) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authorization header missing'</span> });
    }

    <span class="hljs-keyword">const</span> tokenString = authHeader.replace(<span class="hljs-string">'Bearer '</span>, <span class="hljs-string">''</span>);
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> token = jwt.verify(tokenString, JWT_SECRET);

        <span class="hljs-keyword">if</span> (!token.sub || token.typ !== <span class="hljs-string">'authentication'</span>) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid token claims'</span> });
        }

        req.userId = token.sub;
        next();
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid token'</span> });
    }
};
</code></pre>
<h1 id="heading-complete-example">Complete example</h1>
<p>Here is the complete example code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// User model</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
    <span class="hljs-keyword">constructor</span>(id, email, password) {
        <span class="hljs-built_in">this</span>.id = id;
        <span class="hljs-built_in">this</span>.email = email;
        <span class="hljs-built_in">this</span>.password = password;
    }
}

<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> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bcrypt'</span>);
<span class="hljs-keyword">const</span> bodyParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'body-parser'</span>);

<span class="hljs-keyword">const</span> app = express();
app.use(bodyParser.json());

<span class="hljs-comment">// Constants, please use .env to securely store instead of hardcoding it here.</span>
<span class="hljs-keyword">const</span> JWT_SECRET = <span class="hljs-string">'your-secret-key'</span>;
<span class="hljs-keyword">const</span> JWT_AUD_NAME = <span class="hljs-string">'your-audience'</span>;
<span class="hljs-keyword">const</span> JWT_ISSUER_NAME = <span class="hljs-string">'your-issuer'</span>;
<span class="hljs-keyword">const</span> TOKEN_EXPIRATION_DURATION = <span class="hljs-string">'1h'</span>;
<span class="hljs-keyword">const</span> REFRESH_TOKEN_EXPIRATION_DURATION = <span class="hljs-string">'7d'</span>;
<span class="hljs-keyword">const</span> HASH_COST = <span class="hljs-number">10</span>;

<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> create your implementation of the database access function</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserByEmail</span>(<span class="hljs-params">email</span>) </span>{
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'User not found'</span>);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserByID</span>(<span class="hljs-params">id</span>) </span>{
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'User not found'</span>);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CreateUserInDb</span>(<span class="hljs-params">user</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">// Middleware</span>
<span class="hljs-keyword">const</span> authMiddleware = <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> authHeader = req.headers.authorization;
    <span class="hljs-keyword">if</span> (!authHeader) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authorization header missing'</span> });
    }

    <span class="hljs-keyword">const</span> tokenString = authHeader.replace(<span class="hljs-string">'Bearer '</span>, <span class="hljs-string">''</span>);
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> token = jwt.verify(tokenString, JWT_SECRET);

        <span class="hljs-keyword">if</span> (!token.sub || token.typ !== <span class="hljs-string">'authentication'</span>) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid token claims'</span> });
        }

        req.userId = token.sub;
        next();
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid token'</span> });
    }
};

<span class="hljs-comment">// Generate JWT tokens</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateToken</span>(<span class="hljs-params">user</span>) </span>{
    <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>);
    <span class="hljs-keyword">const</span> claims = {
        <span class="hljs-attr">sub</span>: user.id,
        <span class="hljs-attr">typ</span>: <span class="hljs-string">'authentication'</span>,
        <span class="hljs-attr">aud</span>: JWT_AUD_NAME,
        <span class="hljs-attr">exp</span>: now + (<span class="hljs-number">60</span> * <span class="hljs-number">60</span>), <span class="hljs-comment">// 1 hour</span>
        <span class="hljs-attr">nbf</span>: now,
        <span class="hljs-attr">iat</span>: now,
        <span class="hljs-attr">iss</span>: JWT_ISSUER_NAME
    };

    <span class="hljs-keyword">const</span> token = jwt.sign(claims, JWT_SECRET);

    claims.typ = <span class="hljs-string">'refresh'</span>;
    claims.exp = now + (<span class="hljs-number">7</span> * <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>); <span class="hljs-comment">// 7 days</span>
    <span class="hljs-keyword">const</span> refreshToken = jwt.sign(claims, JWT_SECRET);

    <span class="hljs-keyword">return</span> { token, refreshToken };
}

<span class="hljs-comment">// Routes</span>
app.post(<span class="hljs-string">'/signup'</span>, signUp);
app.post(<span class="hljs-string">'/login'</span>,login);
app.get(<span class="hljs-string">'/profile'</span>, authMiddleware, userProfile);

<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">8080</span>;
app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This simple example shows how to build an email/password authentication REST API. You can expand this by integrating database and implementing <code>getUserByEmail</code> and <code>getUserByID</code> functions. With these building blocks, you're well on your way to create a fully functional authentication system.</p>
<p>Warning: Whatever you do, never try to build your own hashing function. You can also secure your API by using multiple layers of protection, such as OS-level firewall that only allows access to ports you want access from outside.</p>
<p>Please comment below if you're interested in expanding this to also add social authentication providers such as Google or Facebook, etc.</p>
]]></content:encoded></item><item><title><![CDATA[It's Simple to Build Your Own Email/Password Authentication REST API]]></title><description><![CDATA[Building a REST API for authentication can sound intimidating, but with the right tools and some guidance, it becomes quite simple. In this post, we will walk through how to build an email/password authentication system using Go and the Gin framework...]]></description><link>https://blog.mahad.dev/its-simple-to-build-your-own-emailpassword-authentication-rest-api</link><guid isPermaLink="true">https://blog.mahad.dev/its-simple-to-build-your-own-emailpassword-authentication-rest-api</guid><category><![CDATA[authenticatio]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[golang]]></category><category><![CDATA[REST API]]></category><category><![CDATA[JWT]]></category><category><![CDATA[authorization]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Sat, 18 Jan 2025 19:47:22 GMT</pubDate><content:encoded><![CDATA[<p>Building a REST API for authentication can sound intimidating, but with the right tools and some guidance, it becomes quite simple. In this post, we will walk through how to build an email/password authentication system using Go and the Gin framework.</p>
<h3 id="heading-setting-up-the-gin-router">Setting Up the Gin Router</h3>
<p>Let's initialize the go project first</p>
<pre><code class="lang-bash">go mod init example-auth-api
go mod tidy
</code></pre>
<p>To start, we need to set up a basic Gin router with endpoints for user sign-up and login.</p>
<pre><code class="lang-go"><span class="hljs-comment">// main.go</span>
<span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    router := gin.Default()

    router.POST(<span class="hljs-string">"/signup"</span>, SignUpUser)
    router.POST(<span class="hljs-string">"/login"</span>, LoginUser)

    userGroup := router.Group(<span class="hljs-string">"/users"</span>)
    userGroup.Use(AuthMiddleware())
    {
        userGroup.GET(<span class="hljs-string">"/"</span>, UserProfile)
    }

    router.Run(<span class="hljs-string">":8080"</span>)
}
</code></pre>
<p>This code creates a simple web server that listens for incoming requests. Now let’s implement the sign-up and login logic.</p>
<h3 id="heading-sign-up-endpoint">Sign-Up Endpoint</h3>
<p>Here’s the function to handle user sign-ups:</p>
<pre><code class="lang-go"><span class="hljs-comment">// handlers.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SignUpUser</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-keyword">var</span> user User

    <span class="hljs-keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid input"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// check if a user with the current email exists</span>
    existingUser, err := GetUserByEmail(user.Email)
    exists := err == <span class="hljs-literal">nil</span> || (err != <span class="hljs-literal">nil</span> &amp;&amp; IsNotFoundError(err))
    <span class="hljs-keyword">if</span> exists {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"User with this email already exists"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// hash the password</span>
    bytes, err := bcrypt.GenerateFromPassword([]<span class="hljs-keyword">byte</span>(password), HASH_COST)
    <span class="hljs-keyword">if</span>  err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Failed to hash password"</span>})
        <span class="hljs-keyword">return</span>
    }

    user.Password = <span class="hljs-keyword">string</span>(bytes)

    <span class="hljs-comment">// save the user</span>
    <span class="hljs-keyword">if</span> err = CreateUserInDb(user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Failed to create user"</span>})
        <span class="hljs-keyword">return</span>
    }

    c.JSON(http.StatusCreated, gin.H{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"User created successfully"</span>,
        <span class="hljs-string">"email"</span>: user.Email,
        <span class="hljs-string">"password"</span>: hashedPassword,
    })
}
</code></pre>
<p>This function processes incoming JSON data, hashes the password, and returns a success message. Always remember to hash passwords securely in a real application.</p>
<h3 id="heading-login-endpoint">Login Endpoint</h3>
<p>Next, we implement the login functionality:</p>
<pre><code class="lang-go"><span class="hljs-comment">// handlers.go</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoginUser</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-keyword">var</span> user User

    <span class="hljs-keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid input"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Get user by email</span>
    user, err := GetUserByEmail(user.Email)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> success := user.Authenticate(); success {
        <span class="hljs-keyword">if</span> token, refresh, err := generateToken(user); err != <span class="hljs-literal">nil</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
        } <span class="hljs-keyword">else</span> {
            c.JSON(http.StatusOK, gin.H{
                <span class="hljs-string">"message"</span>: <span class="hljs-string">"Login successful"</span>,
                <span class="hljs-string">"token"</span>: token,
                <span class="hljs-string">"refreshToken"</span>: refresh,
            })
        }
    } <span class="hljs-keyword">else</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
    }
}
</code></pre>
<p>This endpoint verifies the user’s credentials and generates a token. In a production app, use a library to create secure JWT tokens.</p>
<h3 id="heading-protecting-private-routes">Protecting Private Routes</h3>
<p>In order to protect our private routes we need to use a middleware that keeps non-authenticated HTTP calls to protected routes. As an example, we have 1 protected route that returns the current user's details. Note: it's not recommended to return the full details in a user, this might include sensitive data such as password hash.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AuthMiddleware</span><span class="hljs-params">()</span> <span class="hljs-title">gin</span>.<span class="hljs-title">HandlerFunc</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        authHeader := c.GetHeader(<span class="hljs-string">"Authorization"</span>)
        <span class="hljs-keyword">if</span> authHeader == <span class="hljs-string">""</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Authorization header missing"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        tokenString := strings.TrimPrefix(authHeader, <span class="hljs-string">"Bearer "</span>)
        token, err := jwt.Parse(tokenString, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(token *jwt.Token)</span> <span class="hljs-params">(<span class="hljs-keyword">interface</span>{}, error)</span></span> {
            <span class="hljs-keyword">return</span> []<span class="hljs-keyword">byte</span>(JWT_SECRET), <span class="hljs-literal">nil</span>
        })

        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> || !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid token"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        claims, ok := token.Claims.(jwt.MapClaims)
        <span class="hljs-comment">// check if claims are not a valid jwt.MapClaims, the user id is missing or the token is not for authentication.</span>
        <span class="hljs-keyword">if</span> !ok || claims[<span class="hljs-string">"sub"</span>] == <span class="hljs-literal">nil</span> || claims[<span class="hljs-string">"typ"</span>] != <span class="hljs-string">"authentication"</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid token claims"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        c.Set(<span class="hljs-string">"user_id"</span>, claims[<span class="hljs-string">"sub"</span>])
        c.Next()
    }
}
</code></pre>
<h3 id="heading-complete-example">Complete Example</h3>
<p>Here’s the complete code for this simple API:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
)

<span class="hljs-keyword">type</span> User <span class="hljs-keyword">struct</span> {
    ID       <span class="hljs-keyword">uint64</span>   <span class="hljs-string">`json:"id"`</span>
    Email    <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"email"`</span>
    Password <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"password"`</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    router := gin.Default()

    router.POST(<span class="hljs-string">"/signup"</span>, SignUpUser)
    router.POST(<span class="hljs-string">"/login"</span>, LoginUser)

    userGroup := router.Group(<span class="hljs-string">"/users"</span>)
    userGroup.Use(AuthMiddleware())
    {
        userGroup.GET(<span class="hljs-string">"/"</span>, UserProfile)
    }

    router.Run(<span class="hljs-string">":8080"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SignUpUser</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-keyword">var</span> user User

    <span class="hljs-keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid input"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// check if a user with the current email exists</span>
    existingUser, err := GetUserByEmail(user.Email)
    exists := err == <span class="hljs-literal">nil</span> || (err != <span class="hljs-literal">nil</span> &amp;&amp; IsNotFoundError(err))
    <span class="hljs-keyword">if</span> exists {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"User with this email already exists"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// hash the password</span>
    bytes, err := bcrypt.GenerateFromPassword([]<span class="hljs-keyword">byte</span>(password), HASH_COST)
    <span class="hljs-keyword">if</span>  err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Failed to hash password"</span>})
        <span class="hljs-keyword">return</span>
    }

    user.Password = <span class="hljs-keyword">string</span>(bytes)

    <span class="hljs-comment">// save the user</span>
    <span class="hljs-keyword">if</span> err = CreateUserInDb(user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Failed to create user"</span>})
        <span class="hljs-keyword">return</span>
    }

    c.JSON(http.StatusCreated, gin.H{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"User created successfully"</span>,
        <span class="hljs-string">"email"</span>: user.Email,
        <span class="hljs-string">"password"</span>: hashedPassword,
    })
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoginUser</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-keyword">var</span> user User

    <span class="hljs-keyword">if</span> err := c.ShouldBindJSON(&amp;user); err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusBadRequest, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid input"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Get user by email</span>
    user, err := GetUserByEmail(user.Email)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> success := user.Authenticate(); success {
        <span class="hljs-keyword">if</span> token, refresh, err := generateToken(user); err != <span class="hljs-literal">nil</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
        } <span class="hljs-keyword">else</span> {
            c.JSON(http.StatusOK, gin.H{
                <span class="hljs-string">"message"</span>: <span class="hljs-string">"Login successful"</span>,
                <span class="hljs-string">"token"</span>: token,
                <span class="hljs-string">"refreshToken"</span>: refresh,
            })
        }
    } <span class="hljs-keyword">else</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid email or password"</span>})
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UserProfile</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-comment">// example of getting the user_id from the request context.</span>
    id, ok := c.MustGet(<span class="hljs-string">"user_id"</span>).(<span class="hljs-keyword">uint64</span>)
    <span class="hljs-keyword">if</span> !ok || id &lt; <span class="hljs-number">1</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Access Denied!"</span>})
        <span class="hljs-keyword">return</span>
    }

    user, err := GetUserByID(id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Access Denied!"</span>})
        <span class="hljs-keyword">return</span>
    }

    c.JSON(http.StatusOK, gin.H{<span class="hljs-string">"user"</span>: user})
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AuthMiddleware</span><span class="hljs-params">()</span> <span class="hljs-title">gin</span>.<span class="hljs-title">HandlerFunc</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        authHeader := c.GetHeader(<span class="hljs-string">"Authorization"</span>)
        <span class="hljs-keyword">if</span> authHeader == <span class="hljs-string">""</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Authorization header missing"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        tokenString := strings.TrimPrefix(authHeader, <span class="hljs-string">"Bearer "</span>)
        token, err := jwt.Parse(tokenString, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(token *jwt.Token)</span> <span class="hljs-params">(<span class="hljs-keyword">interface</span>{}, error)</span></span> {
            <span class="hljs-keyword">return</span> []<span class="hljs-keyword">byte</span>(JWT_SECRET), <span class="hljs-literal">nil</span>
        })

        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> || !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid token"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        claims, ok := token.Claims.(jwt.MapClaims)
        <span class="hljs-comment">// check if claims are not a valid jwt.MapClaims, the user id is missing or the token is not for authentication.</span>
        <span class="hljs-keyword">if</span> !ok || claims[<span class="hljs-string">"sub"</span>] == <span class="hljs-literal">nil</span> || claims[<span class="hljs-string">"typ"</span>] != <span class="hljs-string">"authentication"</span> {
            c.JSON(http.StatusUnauthorized, gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Invalid token claims"</span>})
            c.Abort()
            <span class="hljs-keyword">return</span>
        }

        c.Set(<span class="hljs-string">"user_id"</span>, claims[<span class="hljs-string">"sub"</span>])
        c.Next()
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">generateToken</span><span class="hljs-params">(u User)</span> <span class="hljs-params">(tokenStr <span class="hljs-keyword">string</span>, refreshStr <span class="hljs-keyword">string</span>, err error)</span></span> {
    now := time.Now()
    claims := jwt.MapClaims{
        <span class="hljs-string">"sub"</span>:  u.ID,
        <span class="hljs-string">"typ"</span>: <span class="hljs-string">"authentication"</span>,
        <span class="hljs-string">"aud"</span>:  JWT_AUD_NAME,
        <span class="hljs-string">"exp"</span>:  now.Add(TOKEN_EXPIRATION_DURATION).Unix(),
        <span class="hljs-string">"nbf"</span>:  now.Unix(),
        <span class="hljs-string">"iat"</span>:  now.Unix(),
        <span class="hljs-string">"iss"</span>:  JWT_ISSUER_NAME,
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
    tokenStr, err = token.SignedString([]<span class="hljs-keyword">byte</span>(JWT_SECRET))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, <span class="hljs-string">""</span>, err
    }

    claims[<span class="hljs-string">"typ"</span>] = <span class="hljs-string">"refresh"</span>
    claims[<span class="hljs-string">"exp"</span>] = now.Add(REFRESH_TOKEN_EXPIRATION_DURATION).Unix()

    refresh := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
    refreshStr, err = token.SignedString([]<span class="hljs-keyword">byte</span>(JWT_SECRET))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, <span class="hljs-string">""</span>, err
    }

    <span class="hljs-keyword">return</span> refreshStr, tokenStr, <span class="hljs-literal">nil</span>
}
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This simple example shows how to build an email/password authentication REST API. You can expand this by integrating a secure database and implement the <code>GetUserByID</code> and <code>GetUserByEmail</code> functions. With these building blocks, you're well on your way to creating a full-featured authentication system.</p>
<p>Warning: Whatever you do, never try to build your own hashing function. You can also secure your API by using multiple layers of security, such as an OS-level firewall that only allows ports you want accessed from outside. Also, you need to set up trusted proxies using gin's built-in function <code>router.SetTrustedProxies</code>; this will reject requests that are coming from URLs that are not yours. Someone can still bypass this spoofed headers, but still, it helps you protect from script kiddies.</p>
<p>Please comment below if you are interested in expanding this to also add social authentication providers such as Google or Facebook, etc.</p>
]]></content:encoded></item><item><title><![CDATA[Unlocking the Power of Coding: Your Path to a Digital Future]]></title><description><![CDATA[In today's fast-paced world, the ability to code is akin to possessing a superpower. While the prospect of learning to code might seem daunting, rest assured, it's not as challenging as it appears. If I can do it, so can you. So, if you've decided to...]]></description><link>https://blog.mahad.dev/unlocking-the-power-of-coding-your-path-to-a-digital-future</link><guid isPermaLink="true">https://blog.mahad.dev/unlocking-the-power-of-coding-your-path-to-a-digital-future</guid><category><![CDATA[coding]]></category><category><![CDATA[learn coding]]></category><category><![CDATA[Startups]]></category><category><![CDATA[Entrepreneurship]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Mon, 02 Oct 2023 14:53:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696262811109/f6d697cc-4d69-4937-ae30-95a9d4e2a1ef.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's fast-paced world, the ability to code is akin to possessing a superpower. While the prospect of learning to code might seem daunting, rest assured, it's not as challenging as it appears. If I can do it, so can you. So, if you've decided to embark on this transformative journey, skip ahead to the "<strong>Where to Start?</strong>" below. Let's delve into the many advantages of mastering the art of coding.</p>
<h2 id="heading-1-automate-your-tasks"><strong>1. Automate Your Tasks</strong></h2>
<p>Imagine automating repetitive and boring tasks, freeing up valuable time every day. Coding empowers you to do just that. By investing a little effort upfront, you can save precious hours, even days, annually.</p>
<h2 id="heading-2-make-money-while-coding"><strong>2. Make Money While Coding</strong></h2>
<p>Coding is not just a skill; it's a gateway to financial opportunities. Numerous companies and individuals are willing to pay for your coding expertise. Your skills can be monetized, turning your passion into a profession.</p>
<h2 id="heading-3-freedom-to-roam"><strong>3. Freedom to Roam</strong></h2>
<p>Coding liberates you from geographical constraints. You can work from anywhere in the world, breaking free from the traditional 9-to-5 grind. The digital realm knows no borders or time zones, providing you with unparalleled freedom.</p>
<h2 id="heading-4-ai-proof-your-career"><strong>4. AI-Proof Your Career</strong></h2>
<p>As artificial intelligence continues to advance, learning to code becomes a shield against redundancy. If you can code, you become indispensable. You possess a skillset that AI can't replicate entirely, ensuring your relevance and job security.</p>
<h2 id="heading-5-entrepreneurial-ventures"><strong>5. Entrepreneurial Ventures</strong></h2>
<p>Coding grants you the ability to transform your innovative ideas into reality. Entrepreneurs armed with coding skills can create startups, offering solutions that people are willing to pay for. Did you know that most billion-dollar companies started as software ventures? Your coding skills might be the foundation of the next big thing.</p>
<h2 id="heading-6-no-degree-no-problem"><strong>6. No Degree? No Problem</strong></h2>
<p>Unlike many other lucrative careers, coding doesn't demand a formal degree. Some of the best developers are self-taught, emphasizing the merit of practical skills and dedication over diplomas and bachelor's degrees.</p>
<h3 id="heading-where-to-start"><strong>Where to Start?</strong></h3>
<p>If you're intrigued and wondering where to begin your coding journey, consider these pathways:</p>
<ul>
<li><p><strong>Web Development:</strong> Dive into HTML, CSS, and JavaScript to create basic websites. Then progress to libraries and frameworks for advanced web applications.</p>
</li>
<li><p><strong>Data Science:</strong> Master Python if you're interested in exploring vast datasets and deriving meaningful insights.</p>
</li>
<li><p><strong>Game Development:</strong> Learn C++ for a deep understanding of programming concepts or opt for C# for a more accessible entry into game development.</p>
</li>
<li><p><strong>Desktop Applications:</strong> For cross-platform applications, embrace C++. If Windows is your focus, C# is your go-to language.</p>
</li>
<li><p><strong>Mobile App Development:</strong> For Android apps, focus on Kotlin or Java. Swift is the language of choice for iOS and iPad OS applications.</p>
</li>
<li><p><strong>Backend Development:</strong> If building APIs intrigues you, start your journey with Go (Golang), a powerful language that balances simplicity and capability.</p>
</li>
</ul>
<p>In conclusion, coding isn't just a skill; it's a gateway to endless possibilities. Whether you're looking to enhance your career prospects, create innovative solutions, or simply explore the digital landscape, coding is your key. So, if this journey appeals to you, start learning today and unlock the doors to a future shaped by your imagination and skills.</p>
<p>If you found this information helpful, please support us by liking this article. Your encouragement fuels our passion for sharing knowledge. If you have suggestions for improvement, don't hesitate to leave a comment. Thank you for joining us on this enlightening coding adventure!</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Next.JS and PocketBase for Authentication]]></title><description><![CDATA[If you are here, you most likely had a problem setting up PocketBase and Next.js 13 for authentication. Don't worry, I got you. You don't have to suffer the same pain I went through. If you prefer to watch instead of read, click here to watch this on...]]></description><link>https://blog.mahad.dev/setting-up-nextjs-and-pocketbase-for-authentication</link><guid isPermaLink="true">https://blog.mahad.dev/setting-up-nextjs-and-pocketbase-for-authentication</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Next13]]></category><category><![CDATA[React]]></category><category><![CDATA[ReactHooks]]></category><category><![CDATA[Pocketbase ]]></category><dc:creator><![CDATA[Mahad Ahmed]]></dc:creator><pubDate>Wed, 27 Sep 2023 08:09:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696262879544/b9ffed97-5653-4887-88aa-e9928c8fbbc5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are here, you most likely had a problem setting up PocketBase and Next.js 13 for authentication. Don't worry, I got you. You don't have to suffer the same pain I went through. If you prefer to watch instead of read, <a target="_blank" href="https://youtu.be/1XYfzJyMVAE">click here</a> to watch this on YouTube.</p>
<p>This article is not intended as a Next.js tutorial for beginners if that is what you are looking for. This article is for those who are familiar with Next.js but want a simple and easy way to add authentication.</p>
<p>PocketBase is an open-source Firebase alternative that you can self-host or put on VPS (Virtual Private Server) and access it everywhere on the internet.</p>
<h2 id="heading-setup-project">Setup Project</h2>
<p>We need to create the Next.js and we use the <code>create-next-app</code> command.</p>
<pre><code class="lang-bash">npx create-next-app@latest
</code></pre>
<p>This command will prompt you to provide the details of the project, you can type your own or select the default options.</p>
<p>We also need to install the following dependencies.</p>
<pre><code class="lang-typescript">yarn add pocketbase cookies-next
</code></pre>
<p>We'll use <code>cookies-next</code> to clear the cookies when the user logging out.</p>
<h2 id="heading-create-db-wrapper">Create DB Wrapper</h2>
<p>In this section, we create a wrapper class that will simplify access to PocketBase. The comments in the code explain each part and what it does.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/index.ts</span>

<span class="hljs-keyword">import</span> PocketBase <span class="hljs-keyword">from</span> <span class="hljs-string">'pocketbase'</span>;
<span class="hljs-keyword">import</span> { ReadonlyRequestCookies } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/dist/server/web/spec-extension/adapters/request-cookies'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> POCKET_BASE_URL = <span class="hljs-string">"http://127.0.0.1:8090"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DatabaseClient {
    <span class="hljs-comment">// the instance of PocketBase</span>
    client: PocketBase;

    <span class="hljs-keyword">constructor</span> (<span class="hljs-params"></span>) {
        <span class="hljs-comment">// instantiate PocketBase before we use</span>
        <span class="hljs-built_in">this</span>.client = <span class="hljs-keyword">new</span> PocketBase(POCKET_BASE_URL);
    }

    <span class="hljs-comment">// authenticate handles the authentication of the user</span>
    <span class="hljs-keyword">async</span> authenticate (email: <span class="hljs-built_in">string</span>, password: <span class="hljs-built_in">string</span>) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.collection(<span class="hljs-string">"users"</span>).authWithPassword(email, password);
            <span class="hljs-comment">// If there is no token in the result, it means something went wrong</span>
            <span class="hljs-keyword">if</span> (!result?.token) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Invalid email or password"</span>);
            }
            <span class="hljs-keyword">return</span> result;
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.error(err);
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Invalid email or password"</span>);
        }
    }

    <span class="hljs-comment">// register handles the creation of a new user</span>
    <span class="hljs-keyword">async</span> register (email: <span class="hljs-built_in">string</span>, password: <span class="hljs-built_in">string</span>) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// We provide only the minimum required fields by user create method</span>
            <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.collection(<span class="hljs-string">"users"</span>).create({
                email,
                password,
                passwordConfirm: password,
            });

            <span class="hljs-keyword">return</span> result;
        } <span class="hljs-keyword">catch</span> (err) {

        }
    }

    <span class="hljs-comment">// isAuthenticated takes cookieStore from the request to check for the required tokens in the cookie</span>
    <span class="hljs-keyword">async</span> isAuthenticated(cookieStore: ReadonlyRequestCookies) {
        <span class="hljs-keyword">const</span> cookie = cookieStore.get(<span class="hljs-string">'pb_auth'</span>);
        <span class="hljs-keyword">if</span> (!cookie) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        <span class="hljs-comment">// loadFromCookie applies the cookie data before checking the user is authenticated</span>
        <span class="hljs-built_in">this</span>.client.authStore.loadFromCookie(cookie?.value || <span class="hljs-string">''</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.client.authStore.isValid || <span class="hljs-literal">false</span>
    }

    <span class="hljs-comment">// getUser is similar to isAuthenticated, the only difference is the returned data type</span>
    <span class="hljs-keyword">async</span> getUser(cookieStore: ReadonlyRequestCookies) {
        <span class="hljs-keyword">const</span> cookie = cookieStore.get(<span class="hljs-string">'pb_auth'</span>);
        <span class="hljs-keyword">if</span> (!cookie) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        <span class="hljs-built_in">this</span>.client.authStore.loadFromCookie(cookie?.value || <span class="hljs-string">''</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.client.authStore.model ;
    }
}

<span class="hljs-comment">// We create an instance of the DatabaseClient that can be used throughout the app.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">new</span> DatabaseClient();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> db;
</code></pre>
<h2 id="heading-enforce-protected-routes">Enforce Protected Routes</h2>
<p>To keep the users from accessing protected routes, we need to add those rules in the <code>middleware.ts</code> file. This file is a special file used by Next.js and is called before each request to all the URL paths.</p>
<p>We can filter the paths we need to ignore in the <code>matcher</code> field of the <code>config</code> we export from the <code>middleware.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
    matcher: [
        <span class="hljs-string">'/((?!api|_next/static|_next/image|favicon.ico).*)'</span>,
    ],
};
</code></pre>
<p>In the matcher above, we ignore any URL route that starts with <code>api</code>, <code>_next/static</code>, <code>_next/image</code> and <code>favicon.ico</code>.</p>
<p>The most important part of the <code>middleware.ts</code> file is the <code>middleware</code> function which takes the HTTP request as the first argument. That provides us with all the details we need to make decisions of whether to let the request continue or redirect to the login page for authentication.</p>
<p>Here is the full <code>middleware.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> db <span class="hljs-keyword">from</span> <span class="hljs-string">"./db"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">middleware</span>(<span class="hljs-params">request: NextRequest</span>) </span>{
    <span class="hljs-comment">// We print the request method and URL in the logs to see what's happening</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`[middleware] <span class="hljs-subst">${request.method}</span> <span class="hljs-subst">${request.url}</span>`</span>);
    <span class="hljs-comment">// To see more about db.isAuthenticated, check file src/db/index.ts</span>
    <span class="hljs-keyword">const</span> isLoggedIn = <span class="hljs-keyword">await</span> db.isAuthenticated(request.cookies <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>);
    <span class="hljs-keyword">if</span> (request.nextUrl.pathname &amp;&amp; request.nextUrl.pathname.startsWith(<span class="hljs-string">"/auth"</span>)) {
        <span class="hljs-comment">// If already logged in and the request is to go to the login page,</span>
        <span class="hljs-comment">// Skip it and redirect to the home page.</span>
        <span class="hljs-keyword">if</span> (isLoggedIn) {
            <span class="hljs-keyword">return</span> NextResponse.redirect(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"/"</span>, request.url));
        }
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Anything after this is for protected routes, in our case,</span>
    <span class="hljs-comment">// Only the routes that start with /auth are not protected.</span>
    <span class="hljs-comment">// If you have other pages that are not protected you can</span>
    <span class="hljs-comment">// Handle them before the isLoggedIn check below.</span>

    <span class="hljs-comment">// Check if the user is logged in</span>
    <span class="hljs-keyword">if</span> (!isLoggedIn) {
        <span class="hljs-comment">// If not logged in, redirect them to the login page.</span>
        <span class="hljs-keyword">return</span> NextResponse.redirect(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"/auth/login"</span>, request.url));
    }

    <span class="hljs-comment">// Continue without any request changes.</span>
    <span class="hljs-keyword">return</span> NextResponse.next();
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
    matcher: [
        <span class="hljs-string">'/((?!api|_next/static|_next/image|favicon.ico).*)'</span>,
    ],
};
</code></pre>
<h2 id="heading-login-and-sign-up-pages">Login and Sign Up Pages</h2>
<p>The login page is a simple React component, but on Next 13 and forward we need to use <code>'use client'</code> at the beginning of the file to have the ability to use normal React hooks, such as <code>useState</code>, here are the full contents of the login page component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/auth/login/page.tsx</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LoginPage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> route = useRouter();
    <span class="hljs-keyword">const</span> [email, setEmail] = React.useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [password, setPassword] = React.useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [error, setError] = React.useState(<span class="hljs-string">''</span>);

    <span class="hljs-keyword">const</span> onSubmit = <span class="hljs-keyword">async</span> (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        event.preventDefault();

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> form = {email, password};
            <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/api/auth/login'</span>, {
                method: <span class="hljs-string">'POST'</span>,
                headers: {<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>},
                body: <span class="hljs-built_in">JSON</span>.stringify(form)
            });
            <span class="hljs-keyword">if</span> (!response.ok) {
                setError(<span class="hljs-string">'Failed to authenticate user'</span>);
                <span class="hljs-keyword">return</span>;
            };
            <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
            <span class="hljs-keyword">if</span> (data?.token) {
                route.push(<span class="hljs-string">'/'</span>);
            } <span class="hljs-keyword">else</span> {
                setError(<span class="hljs-string">'Failed to authenticate user'</span>);
            }
        } <span class="hljs-keyword">catch</span> (err) {
            setEmail(<span class="hljs-string">'Failed to authenticate user'</span>);
        }
    };

    <span class="hljs-keyword">return</span> (
        &lt;div&gt;
            &lt;h1&gt;Login&lt;/h1&gt;

            &lt;form onSubmit={onSubmit}&gt;
                &lt;div&gt;
                    &lt;label htmlFor=<span class="hljs-string">"email"</span>&gt;Email&lt;/label&gt;
                    &lt;input
                        <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
                        id=<span class="hljs-string">"email"</span>
                        value={email}
                        onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setEmail(e.target.value || <span class="hljs-string">''</span>)}
                    /&gt;
                &lt;/div&gt;

                &lt;div&gt;
                    &lt;label htmlFor=<span class="hljs-string">"password"</span>&gt;Password&lt;/label&gt;
                    &lt;input
                        <span class="hljs-keyword">type</span>=<span class="hljs-string">"password"</span>
                        id=<span class="hljs-string">"password"</span>
                        value={password}
                        onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setPassword(e.target.value || <span class="hljs-string">''</span>)}
                    /&gt;
                &lt;/div&gt;

                &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Login&lt;/button&gt;

                {error &amp;&amp; &lt;p className=<span class="hljs-string">'error'</span>&gt;{error}&lt;/p&gt;}
            &lt;/form&gt;

            &lt;div className=<span class="hljs-string">"row"</span>&gt;
                &lt;div className=<span class="hljs-string">"col-12"</span>&gt;
                    &lt;p&gt;Don<span class="hljs-string">'t have an account? &lt;a href="/auth/signup"&gt;Sign up&lt;/a&gt;&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}

export default LoginPage</span>
</code></pre>
<p>The Signup page content is similar, except for the API URL in the onSubmit event handler.</p>
<p>We need to get to the API routes to create both <code>/api/auth/login</code> and <code>/api/auth/signup</code>.</p>
<h2 id="heading-api-routes">API Routes</h2>
<p>Next.js 13 has a new way of creating API endpoints by creating <code>api</code> folder within the <code>app</code> folder. We created nested folders inside the <code>api</code> folder to be able to create the endpoints we need. At the end, we need a file with the name <code>route.ts</code> which has a bunch of functions with the HTTP verbs as their names.</p>
<p>The signup endpoint is very simple because of the wrapper we created for the database.</p>
<p>Here is the full content of the Signup endpoint:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/api/auth/signup/route.ts</span>
<span class="hljs-keyword">import</span> db <span class="hljs-keyword">from</span> <span class="hljs-string">"@/db"</span>;
<span class="hljs-keyword">import</span> { cookies } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/headers'</span>;
<span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">request: Request</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { email, password } = <span class="hljs-keyword">await</span> request.json();
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> db.register(email, password);

        <span class="hljs-keyword">return</span> NextResponse.json(result);
    } <span class="hljs-keyword">catch</span> (err: <span class="hljs-built_in">any</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
            <span class="hljs-built_in">JSON</span>.stringify({ error: err.message || err.toString() }),
            {
                status: <span class="hljs-number">500</span>,
                headers: {
                    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
                },
            }
        )
    }
}
</code></pre>
<p>The Login endpoint is a little bit different and here is how it looks:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/api/auth/login/route.ts</span>
<span class="hljs-keyword">import</span> db <span class="hljs-keyword">from</span> <span class="hljs-string">"@/db"</span>;
<span class="hljs-keyword">import</span> { cookies } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/headers'</span>;
<span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">request: Request</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { email, password } = <span class="hljs-keyword">await</span> request.json();
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> db.authenticate(email, password);
        <span class="hljs-keyword">const</span> {record, token} = result;
        record.token = token;
        cookies().set(<span class="hljs-string">'pb_auth'</span>, db.client.authStore.exportToCookie());

        <span class="hljs-keyword">return</span> NextResponse.json(record);
    } <span class="hljs-keyword">catch</span> (err: <span class="hljs-built_in">any</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
            <span class="hljs-built_in">JSON</span>.stringify({ error: err.message || err.toString() }),
            {
                status: <span class="hljs-number">500</span>,
                headers: {
                    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
                },
            }
        )
    }
}
</code></pre>
<p>In this endpoint, we only handle the POST method, then we get the email and password from the JSON form we submitted from the Login form by calling the <code>request.json()</code> async function.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We have learned in this article, how we can use PocketBase as an authentication backend with NextJS 13, we also built simple signup and login pages and the endpoints that handle those tasks.</p>
<p>The full code is on GitHub and here is the <a target="_blank" href="https://github.com/tech-with-mahad/next-pocketbase-tutorial-1">link</a>.</p>
]]></content:encoded></item></channel></rss>