<?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[CodeBook]]></title><description><![CDATA[CodeBook]]></description><link>https://blog.mazedul.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1690535830829/EVxNSIvBt.png</url><title>CodeBook</title><link>https://blog.mazedul.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 18:12:18 GMT</lastBuildDate><atom:link href="https://blog.mazedul.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to deploy NodeJS API on AWS EC2]]></title><description><![CDATA[In this post we will create an EC2 (Elastic Compute Cloud) instance with Ubuntu AMI (Amazon Machine Image) and then deploy a NodeJS/ExpressJS project from GitHub repository. We will use PM2 for running the project and Nginx as a reverse proxy.
Prereq...]]></description><link>https://blog.mazedul.dev/how-to-deploy-nodejs-api-on-aws-ec2</link><guid isPermaLink="true">https://blog.mazedul.dev/how-to-deploy-nodejs-api-on-aws-ec2</guid><category><![CDATA[Node.js]]></category><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[EC2 instance]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Mon, 10 Jun 2024 19:47:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718047947326/d3b88699-8410-466c-a900-816036aa6793.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post we will create an EC2 (Elastic Compute Cloud) instance with Ubuntu AMI (Amazon Machine Image) and then deploy a NodeJS/ExpressJS project from GitHub repository. We will use <a target="_blank" href="https://pm2.keymetrics.io/">PM2</a> for running the project and <a target="_blank" href="https://nginx.org/en/">Nginx</a> as a reverse proxy.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ol>
<li><p>AWS account</p>
</li>
<li><p>Github account</p>
</li>
</ol>
<h3 id="heading-launch-ec2-instance">Launch EC2 Instance</h3>
<p>Steps to follow:</p>
<ol>
<li><p>Launch EC2 instance with Ubuntu AMI.</p>
</li>
<li><p>Select a Key Pair or generate a new one for ssh access.</p>
</li>
<li><p>Create a Security Group or select an existing one. You must allow ssh and http traffic.</p>
</li>
<li><p>Under the Advanced Details &gt; User Data, add scripts for installing Nginx.</p>
</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Update the package list</span>
sudo apt update
sudo apt upgrade -y
<span class="hljs-comment"># Install nginx</span>
sudo apt install nginx -y
<span class="hljs-comment"># Start and enable nginx</span>
sudo systemctl start nginx
sudo systemctl <span class="hljs-built_in">enable</span> nginx
</code></pre>
<p>Once the instance goes to running state, copy the public IPV4 address and open in a browser tab. You should be able to see default Nginx page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717964855298/15361b7d-623a-4be0-bfb3-7f34d9e54c53.png" alt="EC2 instance details" class="image--center mx-auto" /></p>
<h3 id="heading-connect-to-ec2-instance">Connect to EC2 Instance</h3>
<p>You can connect to EC2 instance either using <code>ssh</code> or EC2 instance connect (3 in the image above). For simplicity, I recommend using EC2 instance connect. Click connect button in the next page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717965392528/b8f00bbd-c432-446c-bad0-5017089c59cc.png" alt="EC2 Instance Connect" class="image--center mx-auto" /></p>
<h3 id="heading-install-nodejs-and-pm2">Install Node.js and PM2</h3>
<p>Run these command to install Node.js and PM2.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Node.js and npm</span>
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
<span class="hljs-built_in">source</span> ~/.bashrc
nvm install --lts

<span class="hljs-comment"># Install pm2 globally</span>
npm i pm2 -g
</code></pre>
<h3 id="heading-clone-project-from-github">Clone Project from GitHub</h3>
<p>If you clone any public repository then you can simply clone using https web URL without any issue.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/&lt;username&gt;/&lt;project&gt;.git
</code></pre>
<p>If your repository is private, then you need to use username and password for authentication. However, Github do not support password based authentication. Therefore you will need to create a personal access token.</p>
<p>Follow this link to learn <a target="_blank" href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token">how to create github personal access token</a>.</p>
<p>Once you create your token, you can use that token instead of your password.</p>
<h3 id="heading-run-the-project">Run The Project</h3>
<p>Now move to the project folder, install dependencies, build the project, and run the project using PM2.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> your-project
<span class="hljs-comment"># Install dependencies using ci command</span>
npm ci
<span class="hljs-comment"># Optional build step</span>
npm run build
<span class="hljs-comment"># Run project using pm2</span>
pm2 start dist/index.js --name my-nodejs-api
</code></pre>
<p><code>pm2</code> command will run your project at the specified port for example 8080. Though the API is running at 8080 port, we can not use it yet. Because, our security group only allows requests at port 80 for http and 22 for ssh. Therefore, we update our Nginx configuration to forward all the requests at port 80 to port 8080.</p>
<h3 id="heading-configure-nginx">Configure NGINX</h3>
<p>Move to the nginx <code>sites-available</code> directory. Then duplicate the <code>default</code> file and give a custom name. This custom file will be the configuration file for our nodejs API.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> /etc/nginx/sites-available
<span class="hljs-comment"># Copy default file and name it my-nodejs-api.conf</span>
sudo cp default my-nodejs-api.conf
<span class="hljs-comment"># Open configuration file for editing</span>
sudo nano my-nodejs-api.conf
</code></pre>
<p>Replace the <code>location</code> configuration with the following one and save the file.</p>
<pre><code class="lang-bash">location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade <span class="hljs-variable">$http_upgrade</span>;
        proxy_set_header Connection <span class="hljs-string">'upgrade'</span>;
        proxy_set_header Host <span class="hljs-variable">$host</span>;
        proxy_cache_bypass <span class="hljs-variable">$http_upgrade</span>;
}
</code></pre>
<p>Now enable this configuration and disable the default configuration using the following commands.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Disable default config</span>
sudo unlink /etc/nginx/sites-enabled/default
<span class="hljs-comment"># Enable our custom config</span>
sudo ln -s /etc/nginx/sites-available/my-nodejs-api.conf /etc/nginx/sites-enabled/my-nodejs-api.conf
</code></pre>
<p>Now test the nginx configuration using the following command and look for the success message.</p>
<pre><code class="lang-bash">sudo nginx -t
</code></pre>
<p>If the configuration test passed then restart the nginx to start with the updated configuration.</p>
<pre><code class="lang-bash">sudo service nginx restart
</code></pre>
<p>Now test your API endpoint at the public IPV4 address using curl or postman.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717999292391/60df89ba-6c10-459e-97e0-3580e7229a24.png" alt class="image--center mx-auto" /></p>
<p>Congratulations, you have successfully deployed your Node.js API in the EC2 instance.</p>
<h3 id="heading-deploy-new-builds">Deploy New Builds</h3>
<p>After pushing new codes into github repository, you have to connect to your EC2 instance either by using EC2 Connect or ssh. Then navigate to your project directory on the server and run below commands.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Pull new codes from github (use your username and access token)</span>
git pull
<span class="hljs-comment"># Install dependencies</span>
npm ci
<span class="hljs-comment"># Optional build step</span>
npm run build
<span class="hljs-comment"># Restart your project with new build</span>
pm2 restart my-nodejs-api
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We have successfully deployed a NodeJS API project to AWS EC2 instance.</p>
<p>This deployment process is fully manual. In the next article of this series I will show how to setup CI/CD using Github Actions.</p>
]]></content:encoded></item><item><title><![CDATA[Full featured form using NextJS Server Actions]]></title><description><![CDATA[We are going to build a contact form using Nextjs and Server Actions. When the user submit the form it should send an email to your personal email account.

Bootstrap The Project
Generate a new Nextjs project and accept all the default settings, then...]]></description><link>https://blog.mazedul.dev/full-featured-form-using-nextjs-server-actions</link><guid isPermaLink="true">https://blog.mazedul.dev/full-featured-form-using-nextjs-server-actions</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[NextJS Server Actions]]></category><category><![CDATA[server actions]]></category><category><![CDATA[nodemailer]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Mon, 01 Apr 2024 06:34:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GTJNxRG4QJw/upload/846e66b0f988b65ddb4b2057242bf7bf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We are going to build a contact form using Nextjs and Server Actions. When the user submit the form it should send an email to your personal email account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711774655855/89f18839-5b1f-49c0-a61d-a4ebc423ad5b.png" alt="Contact form" class="image--center mx-auto" /></p>
<h3 id="heading-bootstrap-the-project">Bootstrap The Project</h3>
<p>Generate a new Nextjs project and accept all the default settings, then install necessary dependencies.</p>
<pre><code class="lang-bash">npx create-next-app@latest contact-form
<span class="hljs-built_in">cd</span> contact-form
yarn add nodemailer zod
yarn add -D @types/nodemailer
</code></pre>
<p>We have installed <a target="_blank" href="https://www.nodemailer.com/">nodemailer</a> for sending email and <a target="_blank" href="https://zod.dev/">zod</a> for validation.</p>
<p>Now install <a target="_blank" href="https://ui.shadcn.com/docs/installation/next">shadcn-ui</a> and add some necessary UI components using their CLI.</p>
<pre><code class="lang-bash">npx shadcn-ui@latest init
npx shadcn-ui@latest add button card input label textarea toast
</code></pre>
<p>Shadcn should already generate its UI components into <code>/src/components/ui</code> directory. Now we are ready to implement our form.</p>
<h3 id="heading-implement-contactform-component">Implement ContactForm component</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/forms/ContactForm.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Label } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/label"</span>;
<span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/button"</span>;
<span class="hljs-keyword">import</span> { Input } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/input"</span>;
<span class="hljs-keyword">import</span> { Textarea } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/textarea"</span>;
<span class="hljs-keyword">import</span> { sendEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/sendEmail"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;form action={sendEmail} className=<span class="hljs-string">"flex flex-col space-y-4"</span>&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"name"</span>&gt;Name&lt;/Label&gt;
        &lt;Input id=<span class="hljs-string">"name"</span> name=<span class="hljs-string">"name"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> placeholder=<span class="hljs-string">"John Doe"</span> /&gt;
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"email"</span>&gt;Email&lt;/Label&gt;
        &lt;Input
          id=<span class="hljs-string">"email"</span>
          name=<span class="hljs-string">"email"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
          placeholder=<span class="hljs-string">"john@example.com"</span>
        /&gt;
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"message"</span>&gt;Message&lt;/Label&gt;
        &lt;Textarea
          id=<span class="hljs-string">"message"</span>
          name=<span class="hljs-string">"message"</span>
          placeholder=<span class="hljs-string">"Your message here..."</span>
          className=<span class="hljs-string">"input"</span>
        /&gt;
      &lt;/div&gt;
      &lt;Button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Submit&lt;/Button&gt;
    &lt;/form&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ContactForm;
</code></pre>
<p>Notice, in the <code>form</code> element we are using <code>action</code> attribute instead of <code>onSubmit</code>. Another important thing is, we do not need any <code>useState</code> hooks for storing form field values. Instead, we are using built-in html features for handling forms. Therefore the code is much simpler and less verbose.</p>
<p>Now look at the form element, <code>&lt;form action={sendEmail}&gt;</code>. Here, <code>sendEmail</code> is our Server Action (async function that runs on the server only) which is not implemented yet. Let's do implement that.</p>
<h3 id="heading-implement-sendemail-server-action">Implement sendEmail Server Action</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/actions/sendEmail.ts</span>
<span class="hljs-string">"use server"</span>;
<span class="hljs-keyword">import</span> { createTransport } <span class="hljs-keyword">from</span> <span class="hljs-string">"nodemailer"</span>;

<span class="hljs-comment">// read environment variables</span>
<span class="hljs-keyword">const</span> RECEIVING_EMAIL = process.env.RECEIVING_EMAIL <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
<span class="hljs-keyword">const</span> SENDING_EMAIL = process.env.SENDING_EMAIL <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
<span class="hljs-keyword">const</span> SENDING_EMAIL_PASSWORD = process.env.SENDING_EMAIL_PASSWORD <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

<span class="hljs-comment">// create nodemailer transport for sending email</span>
<span class="hljs-keyword">const</span> transporter = createTransport({
  host: <span class="hljs-string">"smtp.gmail.com"</span>,
  port: <span class="hljs-number">465</span>,
  secure: <span class="hljs-literal">true</span>,
  auth: {
    user: SENDING_EMAIL,
    pass: SENDING_EMAIL_PASSWORD,
  },
});

<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">sendEmail</span>(<span class="hljs-params">formData: FormData</span>) </span>{
  <span class="hljs-keyword">const</span> name = formData.get(<span class="hljs-string">"name"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> email = formData.get(<span class="hljs-string">"email"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> message = formData.get(<span class="hljs-string">"message"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

  <span class="hljs-keyword">const</span> mailOptions = {
    <span class="hljs-keyword">from</span>: { name: <span class="hljs-string">"Contact Me"</span>, address: SENDING_EMAIL },
    to: RECEIVING_EMAIL,
    replyTo: email,
    subject: <span class="hljs-string">`<span class="hljs-subst">${name}</span> &lt;<span class="hljs-subst">${email}</span>&gt;`</span>,
    text: message,
    html: <span class="hljs-string">`
      &lt;div&gt;<span class="hljs-subst">${message.replace(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">"&lt;br&gt;"</span>)}</span>&lt;/div&gt;
    `</span>,
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> sentMessageInfo = <span class="hljs-keyword">await</span> transporter.sendMail(mailOptions);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message sent: %s"</span>, <span class="hljs-built_in">JSON</span>.stringify(sentMessageInfo, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
    <span class="hljs-keyword">return</span> {
      success: <span class="hljs-literal">true</span>, <span class="hljs-comment">// let the client know the action was succeeded</span>
      message: <span class="hljs-string">"Thank you for the message!"</span>, <span class="hljs-comment">// message to display in the UI</span>
    };
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
    <span class="hljs-keyword">return</span> {
      success: <span class="hljs-literal">false</span>, <span class="hljs-comment">// let the client know the action was failed</span>
      error: <span class="hljs-string">"Something went wrong!"</span>, <span class="hljs-comment">// error description</span>
      message: <span class="hljs-string">"Something went wrong!"</span>, <span class="hljs-comment">// message to display in the UI</span>
    };
  }
}
</code></pre>
<details><summary>use server</summary><div data-type="detailsContent">In this example code, in the first line we have used <code>"use server"</code> directive. This will ensure this code will run in the server only, and nothing will be added into the client side javascript bundle. Therefore this file is secured for using secrets like email and password.</div></details>

<p>We have read three environment variables in the top.</p>
<ul>
<li><p><code>RECEIVING_EMAIL</code>: your primary email address where you want to receive all emails.</p>
</li>
<li><p><code>SENDING_EMAIL</code>: this one is a <code>no-reply</code> email address, which is used only for sending emails.</p>
</li>
<li><p><code>SENDING_EMAIL_PASSWORD</code>: app specific password for your sending email.</p>
</li>
</ul>
<blockquote>
<p>App password is different from your regular password that you use for logging into gmail account. App password is used for accessing your gmail account programmatically.</p>
<p>To generate an app password go to <a target="_blank" href="https://myaccount.google.com">https://myaccount.google.com</a> and search for <code>App Passwords</code>. Then follow the instructions in the screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711863777761/9a91ec0a-723b-4d77-9d85-f3442901f557.png" alt="Generate gmail app password" class="image--center mx-auto" /></p>
</blockquote>
<p>Insert all these three environment variables into the <code>.env</code> file.</p>
<pre><code class="lang-plaintext">SENDING_EMAIL=no-reply@gmail.com
SENDING_EMAIL_PASSWORD=secret_app_password
RECEIVING_EMAIL=primaryemail@example.com
</code></pre>
<p>Notice how we created <a target="_blank" href="https://www.nodemailer.com">nodemailer</a> transport and use that to send the email. We always return <code>success</code>, <code>message</code> and <code>error</code> from the server action. So, the client component can use these values to update its UI, i.e. display the <code>message</code> using a toast or alert.</p>
<h3 id="heading-show-the-form-in-the-page">Show the form in the page</h3>
<p>Now our <code>ContactForm</code> and the Server Action is ready to use. So, update the page component and display the contact form there.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/page.tsx</span>
<span class="hljs-keyword">import</span> ContactForm <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/forms/ContactForm"</span>;
<span class="hljs-keyword">import</span> {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/card"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;main className=<span class="hljs-string">"flex  flex-col items-center justify-center p-24"</span>&gt;
      &lt;h1 className=<span class="hljs-string">"text-4xl mb-24"</span>&gt;Contact Me&lt;/h1&gt;
      &lt;Card&gt;
        &lt;CardHeader&gt;
          &lt;CardTitle&gt;Get <span class="hljs-keyword">in</span> touch&lt;/CardTitle&gt;
          &lt;CardDescription&gt;
            Fill out the form below to get <span class="hljs-keyword">in</span> touch <span class="hljs-keyword">with</span> me.
          &lt;/CardDescription&gt;
        &lt;/CardHeader&gt;
        &lt;CardContent&gt;
          &lt;ContactForm /&gt;
        &lt;/CardContent&gt;
      &lt;/Card&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>If you run the project now, hopefully you'll be able to see the contact form working. Good thing is, our form component is very concise, because we do not need to use <code>useState</code> hooks for storing form values. Another good thing is, we do not need to setup API route for Server Action. However, there are few things to consider,</p>
<ul>
<li><p>Submit button does not have a loading state.</p>
</li>
<li><p>No success/error message showing in the UI.</p>
</li>
<li><p>Our form does not cleanup when the mail is sent successfully.</p>
</li>
<li><p>There is no form validation.</p>
</li>
</ul>
<p>We will fix all these in next few steps.</p>
<h3 id="heading-submit-button-loading-state">Submit button loading state</h3>
<p>We can use the <a target="_blank" href="https://react.dev/reference/react-dom/hooks/useFormStatus"><code>useFormStatus</code></a> hook to monitor current form submission status. The hook returns information like the <code>pending</code> property which tells you if the form is actively submitting.</p>
<p>However, there is a gotcha, the hook only works if it is called from a component which is rendered inside a <code>&lt;form&gt;</code>. This means, it will not work if we call the hook from our <code>ContactForm</code> component, because it is not rendered inside <code>&lt;form&gt;</code>, rather it is rendering the form. To solve this problem, we will extract the Submit button into a separate component and call the hook from there.</p>
<p>Create <code>SubmitButton</code> component like below:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/forms/SubmitButton.tsx</span>
<span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useFormStatus } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>;
<span class="hljs-keyword">import</span> { ReloadIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@radix-ui/react-icons"</span>;
<span class="hljs-keyword">import</span> { Button, ButtonProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/button"</span>;

<span class="hljs-keyword">type</span> SubmitButtonProps = ButtonProps &amp; { children?: React.ReactNode };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitButton</span>(<span class="hljs-params">{ children, ...rest }: SubmitButtonProps</span>) </span>{
  <span class="hljs-keyword">const</span> { pending } = useFormStatus();

  <span class="hljs-keyword">return</span> (
    &lt;Button {...rest} disabled={pending}&gt;
      {pending &amp;&amp; &lt;ReloadIcon className=<span class="hljs-string">"animate-spin mr-2"</span> /&gt;}
      {children}
    &lt;/Button&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SubmitButton;
</code></pre>
<p>Now, use this <code>SubmitButton</code> component inside the <code>ContactForm</code> component.</p>
<pre><code class="lang-diff">// src/components/forms/ContactForm.tsx
...
<span class="hljs-deletion">-import { Button } from "../ui/button";</span>
<span class="hljs-addition">+import SubmitButton from "./SubmitButton";</span>
...
function ContactForm() {
  return (
    &lt;form action={sendEmail} className="flex flex-col space-y-4"&gt;
      ...
<span class="hljs-deletion">-      &lt;Button type="submit"&gt;Submit&lt;/Button&gt;</span>
<span class="hljs-addition">+      &lt;SubmitButton type="submit"&gt;Submit&lt;/SubmitButton&gt;</span>
    &lt;/form&gt;
  );
}
...
</code></pre>
<p>You should be able to see beautiful loading state when you submit the form now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711870223896/1ef7e326-774e-4fd4-8115-71a1de80d16a.gif" alt="Loading state animation" class="image--center mx-auto" /></p>
<h3 id="heading-display-toast-message">Display toast message</h3>
<p>Our server action returns a response with <code>success</code> status, <code>error</code> if any, and <code>message</code> to display in the UI. But we are not using any of these informations yet.</p>
<p>We can use the <a target="_blank" href="https://react.dev/reference/react-dom/hooks/useFormState"><code>useFormState</code></a> hook to update the UI based on the result from our Server Action. Let's update the <code>ContactForm</code> component and show toast message based on the result from the Server Action.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/forms/ContactForm.tsx</span>
...
<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/use-toast"</span>;
<span class="hljs-keyword">import</span> { useFormState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>;

<span class="hljs-comment">// initial state for the useFormState hook</span>
<span class="hljs-keyword">const</span> initialState = {
  success: <span class="hljs-literal">null</span>,
  error: <span class="hljs-literal">null</span>,
  message: <span class="hljs-literal">null</span>,
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [{ success, message, error }, formAction] = useFormState(
    sendEmail,
    initialState
  );

  <span class="hljs-comment">// display toast based on the success status</span>
  <span class="hljs-keyword">if</span> (success === <span class="hljs-literal">true</span>) {
    toast({ title: <span class="hljs-string">"Success"</span>, description: message, variant: <span class="hljs-string">"default"</span> });
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(success ===<span class="hljs-literal">false</span>) {
    toast({ title: <span class="hljs-string">"Error"</span>, description: message, variant: <span class="hljs-string">"destructive"</span> });
  }

  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// instead of using the sendEmail function,</span>
    <span class="hljs-comment">// we use the formAction function as the action</span>
    &lt;form action={formAction} className=<span class="hljs-string">"flex flex-col space-y-4"</span>&gt;
    ...
    &lt;/form&gt;
  );
}
</code></pre>
<p>As we are passing the <code>sendEmail</code> action to the <code>useFormState</code> hook, the <code>sendMail</code> action will get a new first argument called <code>current state</code>. We need to make a small change into our action to allow this first argument.</p>
<pre><code class="lang-diff">// src/actions/sendEmail.ts
<span class="hljs-deletion">-export async function sendEmail(formData: FormData) {</span>
<span class="hljs-addition">+export async function sendEmail(currentState: any, formData: FormData) {</span>
</code></pre>
<p>To make this <code>toast</code> function work properly we need to render the <code>Toaster</code> component from <a target="_blank" href="https://ui.shadcn.com/"><code>shadcn</code></a> in a parent component (typically in the root layout component). Let's update the root layout component like below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/layout.tsx</span>
...
<span class="hljs-keyword">import</span> { Toaster } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/toaster"</span>;
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;html lang=<span class="hljs-string">"en"</span>&gt;
      &lt;body className={inter.className}&gt;
        {children}
        &lt;Toaster /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>If you try sending a message now, you should be able to see a nice toast message.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711887053070/56c2f46f-4dfa-4926-8d95-a14152488b2d.png" alt="Success toast" class="image--center mx-auto" /></p>
<h3 id="heading-cleanup-the-form">Cleanup the form</h3>
<p>Form is submitted successfully but it is still remains populated with the entered data. To cleanup the form, we need a reference to the <code>&lt;form&gt;</code> element. Update the <code>ContactForm</code> component like below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/forms/ContactForm.tsx</span>
<span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> React, { useRef } <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">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> ref = useRef&lt;HTMLFormElement&gt;(<span class="hljs-literal">null</span>);
  ...
  <span class="hljs-keyword">if</span> (success === <span class="hljs-literal">true</span>) {
    toast({ title: <span class="hljs-string">"Success"</span>, description: message, variant: <span class="hljs-string">"default"</span> });
    ref.current?.reset(); <span class="hljs-comment">// cleanup the form here</span>
  }
  ...
  <span class="hljs-keyword">return</span> (
    &lt;form action={formAction} ref={ref} className=<span class="hljs-string">"flex flex-col space-y-4"</span>&gt;
    ...
  )
}
...
</code></pre>
<p>If the form action succeeded then the form will be cleaned up from now.</p>
<h3 id="heading-form-validation">Form validation</h3>
<p>Validation can be done in two places, client side and server side validation. We are going to focus on the server side validation in this article. Our goal is to display error message for each fields separately.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711947518228/2203c452-7c80-4184-952b-d03d8b71cec1.png" alt="Form validation example" class="image--center mx-auto" /></p>
<p>At first, install <a target="_blank" href="https://zod.dev/">zod</a> for validation.</p>
<pre><code class="lang-bash">yarn add zod
</code></pre>
<p>Now create a validator using zod.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/actions/validators/ContactFormValidator.ts</span>
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-comment">// zod schema for contact form</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ContactFormSchema = z.object({
  name: z.string().min(<span class="hljs-number">2</span>, { message: <span class="hljs-string">"Name must be at least 2 characters"</span> }),
  email: z.string().email({ message: <span class="hljs-string">"Please provide a valid email address"</span> }),
  message: z.string().min(<span class="hljs-number">10</span>, { message: <span class="hljs-string">"Please elaborate your message"</span> }),
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ContactFormFlattenedErrors = z.inferFlattenedErrors&lt;
  <span class="hljs-keyword">typeof</span> ContactFormSchema
&gt;;

<span class="hljs-comment">// error types we are going to return from the sendEmail server action</span>
<span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> ContactFormErrorType {
  Internal = <span class="hljs-string">"Internal"</span>,
  ZodFieldErrors = <span class="hljs-string">"ZodFieldErrors"</span>,
}

<span class="hljs-comment">// return type of sendEmail server action</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> SendEmailResponse = {
  success: <span class="hljs-built_in">boolean</span> | <span class="hljs-literal">null</span>; <span class="hljs-comment">// Whether the email was sent successfully</span>
  error?: <span class="hljs-built_in">string</span> | ContactFormFlattenedErrors[<span class="hljs-string">"fieldErrors"</span>] | <span class="hljs-literal">null</span>; <span class="hljs-comment">// If success is false, this will be the error object or string</span>
  errorType?: ContactFormErrorType | <span class="hljs-literal">null</span>; <span class="hljs-comment">// If success is false, this will be the type of error</span>
  message?: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>; <span class="hljs-comment">// Message (success/failure) should be displayed to the user</span>
};
</code></pre>
<p>Let's update our <code>sendEmail</code> action and utilise the zod validator there.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/actions/sendEmail.ts</span>
<span class="hljs-string">"use server"</span>;
<span class="hljs-keyword">import</span> { createTransport } <span class="hljs-keyword">from</span> <span class="hljs-string">"nodemailer"</span>;
<span class="hljs-keyword">import</span> {
  SendEmailResponse,
  ContactFormSchema,
  ContactFormFlattenedErrors,
  ContactFormErrorType,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"./validators/ContactFormValidator"</span>;

<span class="hljs-comment">// read environment variables</span>
<span class="hljs-keyword">const</span> RECEIVING_EMAIL = process.env.RECEIVING_EMAIL <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
<span class="hljs-keyword">const</span> SENDING_EMAIL = process.env.SENDING_EMAIL <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
<span class="hljs-keyword">const</span> SENDING_EMAIL_PASSWORD = process.env.SENDING_EMAIL_PASSWORD <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

<span class="hljs-comment">// create nodemailer transport for sending email</span>
<span class="hljs-keyword">const</span> transporter = createTransport({
  host: <span class="hljs-string">"smtp.gmail.com"</span>,
  port: <span class="hljs-number">465</span>,
  secure: <span class="hljs-literal">true</span>,
  auth: {
    user: SENDING_EMAIL,
    pass: SENDING_EMAIL_PASSWORD,
  },
});

<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">sendEmail</span>(<span class="hljs-params">
  currentState: SendEmailResponse,
  formData: FormData
</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">SendEmailResponse</span>&gt; </span>{
  <span class="hljs-keyword">const</span> name = formData.get(<span class="hljs-string">"name"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> email = formData.get(<span class="hljs-string">"email"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> message = formData.get(<span class="hljs-string">"message"</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

  <span class="hljs-comment">// validate using zod schema here</span>
  <span class="hljs-keyword">const</span> validatedData = ContactFormSchema.safeParse({ name, email, message });

  <span class="hljs-keyword">if</span> (!validatedData.success) {
    <span class="hljs-comment">// return early if validation failed</span>
    <span class="hljs-keyword">const</span> flattenedErrors: ContactFormFlattenedErrors =
      validatedData.error.flatten();
    <span class="hljs-keyword">return</span> {
      success: <span class="hljs-literal">false</span>,
      error: flattenedErrors.fieldErrors,
      errorType: ContactFormErrorType.ZodFieldErrors,
    };
  }

  <span class="hljs-keyword">const</span> mailOptions = {
    <span class="hljs-keyword">from</span>: { name: <span class="hljs-string">"Contact Me"</span>, address: SENDING_EMAIL },
    to: RECEIVING_EMAIL,
    replyTo: email,
    subject: <span class="hljs-string">`<span class="hljs-subst">${name}</span> &lt;<span class="hljs-subst">${email}</span>&gt;`</span>,
    text: message,
    html: <span class="hljs-string">`
      &lt;div&gt;<span class="hljs-subst">${message.replace(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">"&lt;br&gt;"</span>)}</span>&lt;/div&gt;
    `</span>,
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> sentMessageInfo = <span class="hljs-keyword">await</span> transporter.sendMail(mailOptions);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message sent: %s"</span>, <span class="hljs-built_in">JSON</span>.stringify(sentMessageInfo, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
    <span class="hljs-keyword">return</span> {
      success: <span class="hljs-literal">true</span>,
      message: <span class="hljs-string">"Thank you for the message!"</span>,
    };
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
    <span class="hljs-keyword">return</span> {
      success: <span class="hljs-literal">false</span>,
      error: <span class="hljs-string">"Something went wrong!"</span>,
      errorType: ContactFormErrorType.Internal,
      message: <span class="hljs-string">"Something went wrong!"</span>,
    };
  }
}
</code></pre>
<p>Notice that, when zod validation failed, we are sending</p>
<p><code>errorType = ContactFormErrorType.ZodFieldErrors</code> and attach the flattened error fields in the <code>error</code> property of the returned object. This flattened field errors will be used to display error messages under corresponding input element in the <code>ContactForm</code> component.</p>
<p>For any other errors we are sending <code>errorType = ContactFormErrorType.Internal</code> with a generic <code>error</code> i.e. <code>Something went wrong!</code>. This generic error will be displayed in the toast message.</p>
<p>Our goal is to display error messages in red colour under corresponding input element. Let's create a separate component for this.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/ui/typography.tsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { VariantProps, cva } <span class="hljs-keyword">from</span> <span class="hljs-string">"class-variance-authority"</span>;
<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>;

<span class="hljs-keyword">const</span> typographySmallVariants = cva(<span class="hljs-string">"text-sm font-light leading-none"</span>, {
  variants: {
    variant: {
      <span class="hljs-keyword">default</span>: <span class="hljs-string">"text-primary"</span>,
      destructive: <span class="hljs-string">"text-destructive"</span>,
    },
  },
  defaultVariants: {
    variant: <span class="hljs-string">"default"</span>,
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> TypographySmallProps
  <span class="hljs-keyword">extends</span> React.HTMLAttributes&lt;HTMLSpanElement&gt;,
    VariantProps&lt;typeof typographySmallVariants&gt; {}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> TypographySmall = React.forwardRef&lt;
  HTMLSpanElement,
  TypographySmallProps
&gt;(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TypographySmall</span>(<span class="hljs-params">{ className, variant, ...props }, ref</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;small
      className={cn(typographySmallVariants({ variant, className }))}
      ref={ref}
      {...props}
    /&gt;
  );
});
</code></pre>
<p>We exported <code>TypographySmall</code> component which renders HTML <code>&lt;small&gt;</code> element. This component also have two variants, <code>default</code> and <code>destructive</code>. We'll use the <code>destructive</code> variant for displaying error message in red colour.</p>
<p>Now, let's update the <code>ContactForm</code> component. Here is the updated <code>ContactForm</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/components/forms/ContactForm.tsx</span>
<span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> React, { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Label } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/label"</span>;
<span class="hljs-keyword">import</span> { Input } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/input"</span>;
<span class="hljs-keyword">import</span> { Textarea } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/textarea"</span>;
<span class="hljs-keyword">import</span> { sendEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/sendEmail"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> SendEmailResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/validators/ContactFormValidator"</span>;
<span class="hljs-keyword">import</span> SubmitButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./SubmitButton"</span>;
<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/use-toast"</span>;
<span class="hljs-keyword">import</span> { useFormState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>;
<span class="hljs-keyword">import</span> { TypographySmall } <span class="hljs-keyword">from</span> <span class="hljs-string">"../ui/typography"</span>;
<span class="hljs-keyword">import</span> { isObject } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/isObject"</span>;
<span class="hljs-keyword">import</span> clsx <span class="hljs-keyword">from</span> <span class="hljs-string">"clsx"</span>;

<span class="hljs-comment">// initial state of the form</span>
<span class="hljs-keyword">const</span> initialState: SendEmailResponse = {
  success: <span class="hljs-literal">null</span>,
  error: <span class="hljs-literal">null</span>,
  errorType: <span class="hljs-literal">null</span>,
  message: <span class="hljs-literal">null</span>,
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> ref = useRef&lt;HTMLFormElement&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-comment">// success, message, error, errorType will get the returned values from the sendEmail action</span>
  <span class="hljs-keyword">const</span> [{ success, message, error, errorType }, formAction] = useFormState(
    sendEmail,
    initialState
  );

  <span class="hljs-keyword">if</span> (success === <span class="hljs-literal">true</span>) {
    <span class="hljs-comment">// show success toast</span>
    toast({ title: <span class="hljs-string">"Success"</span>, description: message, variant: <span class="hljs-string">"default"</span> });
    <span class="hljs-comment">// cleanup the form</span>
    ref.current?.reset();
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (success === <span class="hljs-literal">false</span> &amp;&amp; errorType === <span class="hljs-string">"Internal"</span>) {
    <span class="hljs-comment">// if error type is Internal then show toast message</span>
    toast({
      title: <span class="hljs-string">"Error"</span>,
      description: message,
      variant: <span class="hljs-string">"destructive"</span>,
    });
  }

  <span class="hljs-keyword">return</span> (
    &lt;form action={formAction} ref={ref} className=<span class="hljs-string">"flex flex-col space-y-4"</span>&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"name"</span>&gt;Name&lt;/Label&gt;
        &lt;Input
          id=<span class="hljs-string">"name"</span>
          name=<span class="hljs-string">"name"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
          placeholder=<span class="hljs-string">"John Doe"</span>
          className={clsx({
            <span class="hljs-string">"border-destructive"</span>: isObject(error) &amp;&amp; error.name,
          })}
        /&gt;
        {isObject(error) &amp;&amp; error.name &amp;&amp; (
          &lt;TypographySmall variant=<span class="hljs-string">"destructive"</span>&gt;{error.name}&lt;/TypographySmall&gt;
        )}
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"email"</span>&gt;Email&lt;/Label&gt;
        &lt;Input
          id=<span class="hljs-string">"email"</span>
          name=<span class="hljs-string">"email"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
          placeholder=<span class="hljs-string">"john@example.com"</span>
          className={clsx({
            <span class="hljs-string">"border-destructive"</span>: isObject(error) &amp;&amp; error.email,
          })}
        /&gt;
        {isObject(error) &amp;&amp; error.email &amp;&amp; (
          &lt;TypographySmall variant=<span class="hljs-string">"destructive"</span>&gt;{error.email}&lt;/TypographySmall&gt;
        )}
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"flex flex-col space-y-1.5"</span>&gt;
        &lt;Label htmlFor=<span class="hljs-string">"message"</span>&gt;Message&lt;/Label&gt;
        &lt;Textarea
          id=<span class="hljs-string">"message"</span>
          name=<span class="hljs-string">"message"</span>
          placeholder=<span class="hljs-string">"Your message here..."</span>
          className={clsx({
            input: <span class="hljs-literal">true</span>,
            <span class="hljs-string">"border-destructive"</span>: isObject(error) &amp;&amp; error.message,
          })}
        /&gt;
        {isObject(error) &amp;&amp; error.message &amp;&amp; (
          &lt;TypographySmall variant=<span class="hljs-string">"destructive"</span>&gt;
            {error.message}
          &lt;/TypographySmall&gt;
        )}
      &lt;/div&gt;
      &lt;SubmitButton <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Submit&lt;/SubmitButton&gt;
    &lt;/form&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ContactForm;
</code></pre>
<p>We have conditionally render <code>&lt;TypographySmall&gt;</code> component under each input/textarea emelents. You might be noticed that we are using <code>isObject</code> utility function. It basically checks whether the error field is object or not. Code of this function is given below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/lib/isObject.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isObject</span>(<span class="hljs-params">value: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">value</span> <span class="hljs-title">is</span> <span class="hljs-title">Record</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">unknown</span>&gt; </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">typeof</span> value === <span class="hljs-string">"object"</span> &amp;&amp; value !== <span class="hljs-literal">null</span>;
}
</code></pre>
<p>That's it. Hopefully you'll be able to see field level errors now.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Complete code of this project can be found here: <a target="_blank" href="https://github.com/mazid1/contact-form-with-server-actions">https://github.com/mazid1/contact-form-with-server-actions</a></p>
<p>If you have any concern, let me know in the comment section.</p>
<hr />
<p>I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at <a target="_blank" href="mailto:mazidmailbox@gmail.com"><strong>mazidmailbox@gmail.com</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[How I wasted hours debugging a silly mistake]]></title><description><![CDATA[Instead of telling the real story, I'll walk you through a case where the silly mistake can happen very easily.
Imagine, we have a nodejs server and we have got a requirement, the API needs to send a welcome message to the logged-in user. This is ver...]]></description><link>https://blog.mazedul.dev/how-i-wasted-hours-debugging-a-silly-mistake</link><guid isPermaLink="true">https://blog.mazedul.dev/how-i-wasted-hours-debugging-a-silly-mistake</guid><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Wed, 17 Jan 2024 04:52:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/2925e3b826573657f9943d047f8b6541.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Instead of telling the real story, I'll walk you through a case where the silly mistake can happen very easily.</p>
<p>Imagine, we have a nodejs server and we have got a requirement, the API needs to send a welcome message to the logged-in user. This is very simple, right?</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Message = { title: <span class="hljs-built_in">string</span>; body: <span class="hljs-built_in">string</span>; };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params"></span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-keyword">const</span> welcomeMessage: Message = {
        title: <span class="hljs-string">"Welcome"</span>,
        body: <span class="hljs-string">"Welcome to the Fun World!"</span>
    };
    <span class="hljs-keyword">return</span> welcomeMessage;
}
</code></pre>
<p>Now we can call this <code>getWelcomeMessage</code> function from the <code>/login</code> route handler when login succeeded.</p>
<pre><code class="lang-typescript">app.post(<span class="hljs-string">'/login'</span>, authMiddleware, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-comment">// Run other logics here, maybe set auth token in cookie.</span>
    <span class="hljs-keyword">const</span> message = getWelcomeMessage();
    res.send(message);
});
</code></pre>
<p>The client will want to change the message once they get a better welcome message for sure. Instead of hardcoding the message in the source code, it will be better to move it to a file or environment variable.</p>
<pre><code class="lang-plaintext"># .env file
WELCOME_MESSAGE_TITLE="Welcome"
WELCOME_MESSAGE_BODY="Welcome to the Fun World!"
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Message = { title: <span class="hljs-built_in">string</span>; body: <span class="hljs-built_in">string</span>; };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params"></span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-keyword">const</span> welcomeMessage: Message = {
        title: process.env.WELCOME_MESSAGE_TITLE,
        body: process.env.WELCOME_MESSAGE_BODY
    };
    <span class="hljs-keyword">return</span> welcomeMessage;
}
</code></pre>
<p>Now the client do not need to change the source code just for changing the welcome message. Cool!</p>
<p>Very soon another requirement comes, we need to support another language. For this case, we need to support Bangla and English. So, lets update the .env file and getWelcomeMessage function like below.</p>
<pre><code class="lang-plaintext"># .env file
WELCOME_MESSAGE_TITLE_EN="Welcome"
WELCOME_MESSAGE_BODY_EN="Welcome to the Fun World!"
WELCOME_MESSAGE_TITLE_BN="স্বাগতম"
WELCOME_MESSAGE_BODY_BN="মজার বিশ্বে স্বাগতম!"
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Language = <span class="hljs-string">'en'</span> | <span class="hljs-string">'bn'</span>;
<span class="hljs-keyword">type</span> Message = { title: <span class="hljs-built_in">string</span>; body: <span class="hljs-built_in">string</span>; };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params">language: Language</span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-keyword">let</span> welcomeMessage: Message;
    <span class="hljs-keyword">switch</span>(language) {
        <span class="hljs-keyword">case</span> <span class="hljs-string">'en'</span>:
            welcomeMessage = {
                title: process.env.WELCOME_MESSAGE_TITLE_EN,
                body: process.env.WELCOME_MESSAGE_BODY_EN
            }
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> <span class="hljs-string">'bn'</span>:
            welcomeMessage = {
                title: process.env.WELCOME_MESSAGE_TITLE_BN,
                body: process.env.WELCOME_MESSAGE_BODY_BN
            }
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">default</span>:
            <span class="hljs-comment">// Set default message in English</span>
            welcomeMessage = {
                title: process.env.WELCOME_MESSAGE_TITLE_EN,
                body: process.env.WELCOME_MESSAGE_BODY_EN
            }
    }
    <span class="hljs-keyword">return</span> welcomeMessage;
}
</code></pre>
<p>This function works fine. However, notice that, this function will read from environment each time it will be executed. Reading from file or environment is costly. It is a good practice to load environment variables in memory. Let's do that.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Language = <span class="hljs-string">'en'</span> | <span class="hljs-string">'bn'</span>;
<span class="hljs-keyword">type</span> Message = { title: <span class="hljs-built_in">string</span>; body: <span class="hljs-built_in">string</span>; };
<span class="hljs-keyword">type</span> WelcomeMessages = Record&lt;Language, Message&gt;;

<span class="hljs-comment">// When the file first executed the environment variables will be</span>
<span class="hljs-comment">// loaded and stored into "welcomeMessages" variable.</span>
<span class="hljs-keyword">const</span> welcomeMessages: WelcomeMessages = {
    en: {
        title: process.env.WELCOME_MESSAGE_TITLE_EN,
        body: process.env.WELCOME_MESSAGE_BODY_EN
    },
    bn: {
        title: process.env.WELCOME_MESSAGE_TITLE_BN,
        body: process.env.WELCOME_MESSAGE_BODY_BN
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params">language: Language</span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-comment">// Read from the cached variable, no need to read from .env again</span>
    <span class="hljs-keyword">return</span> welcomeMessages[language];
}
</code></pre>
<p>Looks very simple and easy right? But we have already made a mistake. If you did not notice that, don't worry, we'll figure it out soon.</p>
<p>A few weeks later, a new requirement came. Client wanted to display current user's name in the welcome message body. That means, we need a placeholder in the welcome message body and we need to be able to replace the placeholder with the name of the current user. To do that, I added a placeholder like this <code>&lt;name&gt;</code>.</p>
<pre><code class="lang-plaintext"># .env file
WELCOME_MESSAGE_TITLE_EN="Welcome"
WELCOME_MESSAGE_BODY_EN="Hi &lt;name&gt;, welcome to the Fun World!"
WELCOME_MESSAGE_TITLE_BN="স্বাগতম"
WELCOME_MESSAGE_BODY_BN="&lt;name&gt;, মজার বিশ্বে স্বাগতম!"
</code></pre>
<p>Now, once we get the message, we can replace the placeholder in our route handler.</p>
<pre><code class="lang-typescript">app.post(<span class="hljs-string">'/login'</span>, authMiddleware, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> message = getWelcomeMessage(req.user.language);
    <span class="hljs-comment">// You may argue we should implement this replace logic inside</span>
    <span class="hljs-comment">// getWelcomeMessage function. For the sake of this example we</span>
    <span class="hljs-comment">// are doing this here.</span>
    message.body.replace(<span class="hljs-string">'&lt;name&gt;'</span>, req.user.name);
    res.send(message);
});
</code></pre>
<p>Did you notice the mistake this time? Let's say we test our project locally with our test user account. We will get expected response from our login route. However, once we deploy this project in server, and many users started to login, everyone will get only one name is there. Seems like the name is hardcoded.</p>
<p>So the summary is, when the login route handler function executed for the first time it will work fine. But it will not work for the subsequent executions.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> message = getWelcomeMessage(<span class="hljs-string">'en'</span>);
message.body.replace(<span class="hljs-string">'&lt;name&gt;'</span>, <span class="hljs-string">'Mazedul'</span>);
<span class="hljs-comment">// message.body == "Hi Mazedul, welcome to the Fun World!"</span>
<span class="hljs-keyword">const</span> message2 = getWelcomeMessage(<span class="hljs-string">'en'</span>);
message.body.replace(<span class="hljs-string">'&lt;name&gt;'</span>, <span class="hljs-string">'Hashnode'</span>);
<span class="hljs-comment">// message2.body == "Hi Mazedul, welcome to the Fun World!"</span>
<span class="hljs-comment">// Name did not changed for the second execution!!!</span>
</code></pre>
<p>Why it is not working for the subsequent executions? Well the bug is very simple, our <code>getWelcomeMessage</code> function is buggy. Let's have a closer look.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// When the file first executed the environment variables will be</span>
<span class="hljs-comment">// loaded and stored into "welcomeMessages" variable.</span>
<span class="hljs-keyword">const</span> welcomeMessages: WelcomeMessages = {
    en: {
        title: process.env.WELCOME_MESSAGE_TITLE_EN,
        body: process.env.WELCOME_MESSAGE_BODY_EN
    },
    bn: {
        title: process.env.WELCOME_MESSAGE_TITLE_BN,
        body: process.env.WELCOME_MESSAGE_BODY_BN
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params">
    language: Language,
    userName: <span class="hljs-built_in">string</span>
</span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-comment">// Read from the cached variable, no need to read from .env again.</span>
    <span class="hljs-keyword">const</span> message = welcomeMessages[language];
    <span class="hljs-comment">// message variable is now pointing to the cached variable</span>
    <span class="hljs-comment">// welcomeMessages.en or welcomeMessages.bn depending on the</span>
    <span class="hljs-comment">// language.</span>
    <span class="hljs-keyword">return</span> message;
}
</code></pre>
<p>When we replace the placeholder in the route handler function it mutate the cached <code>welcomeMessages</code> variable. Therefore, subsequent executions do not get any <code>&lt;name&gt;</code> placeholder to replace.</p>
<p>In this small example project it is very easy to understand the bug. However, in a complex large project with several level of abstractions it can be very difficult to debug this issue.</p>
<p>The fix is very easy. Instead of mutating the cached object, we need to create a copy and then modify it. The <code>getWelcomeMessage</code> function should not return the reference to the cached object, instead it should return a copy object.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWelcomeMessage</span>(<span class="hljs-params">
    language: Language,
    userName: <span class="hljs-built_in">string</span>
</span>): <span class="hljs-title">Message</span> </span>{
    <span class="hljs-keyword">const</span> message = { ...welcomeMessages[language] };
    <span class="hljs-keyword">return</span> message;
}
</code></pre>
<p>Now the function creates a new object each time it is executed. So the bug is fixed. You can also consider using <a target="_blank" href="https://lodash.com/docs/4.17.15#cloneDeep">Lodash cloneDeep function</a> for a complex object.</p>
<p>Moral: Never return reference of a private object to the function caller.</p>
]]></content:encoded></item><item><title><![CDATA[How to seed database using TypeORM in a NestJS project]]></title><description><![CDATA[Motivation
It is often required to seed a database with initial data during development. In a NestJS + TypeORM project, we can do that with the help of typeorm-extension.
Initial project setup
Let's initialize our project using Nest CLI.
nest new nes...]]></description><link>https://blog.mazedul.dev/how-to-seed-database-using-typeorm-in-a-nestjs-project</link><guid isPermaLink="true">https://blog.mazedul.dev/how-to-seed-database-using-typeorm-in-a-nestjs-project</guid><category><![CDATA[nestjs]]></category><category><![CDATA[typeorm]]></category><category><![CDATA[seed]]></category><category><![CDATA[seeder]]></category><category><![CDATA[database]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Thu, 03 Aug 2023 14:20:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691071909516/6705ab91-e7ed-4811-a6ab-48758ec6b149.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-motivation">Motivation</h3>
<p>It is often required to seed a database with initial data during development. In a <a target="_blank" href="https://nestjs.com/">NestJS</a> + <a target="_blank" href="https://typeorm.io/">TypeORM</a> project, we can do that with the help of <a target="_blank" href="https://typeorm-extension.tada5hi.net/">typeorm-extension</a>.</p>
<h3 id="heading-initial-project-setup">Initial project setup</h3>
<p>Let's initialize our project using <a target="_blank" href="https://docs.nestjs.com/cli/overview"><strong>Nest CLI</strong></a>.</p>
<pre><code class="lang-bash">nest new nest-typeorm
</code></pre>
<p>Navigate to the project folder and install the necessary dependencies. In this example, I am using <a target="_blank" href="https://www.postgresql.org/"><strong>PostgreSQL</strong></a>, therefore I have added <code>pg</code> as a dependency.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> nest-typeorm
npm install --save @nestjs/typeorm typeorm pg
</code></pre>
<h3 id="heading-database-connection"><strong>Database connection</strong></h3>
<p>Let's create a separate Nest module for handling database-related tasks.</p>
<pre><code class="lang-bash">nest g module db
</code></pre>
<p>Create the <code>data-source.ts</code> file inside the <code>src/db</code> directory. This file will contain database connection configurations.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
<span class="hljs-keyword">import</span> { config } <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
<span class="hljs-keyword">import</span> { DataSourceOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;

config();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions = {
  <span class="hljs-keyword">type</span>: <span class="hljs-string">'postgres'</span>,
  host: process.env.DB_HOST,
  port: +process.env.DB_PORT,
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [<span class="hljs-string">'dist/resources/**/*.entity.js'</span>],
  synchronize: <span class="hljs-literal">true</span>, <span class="hljs-comment">// do not set it true in production application</span>
};
</code></pre>
<blockquote>
<p>Setting <code>synchronize: true</code> shouldn't be used in production - otherwise you can lose production data. You should use migration instead. I have an article on <a target="_blank" href="https://blog.mazedulislam.com/how-to-setup-typeorm-migrations-in-a-nestjs-project">How to setup TypeORM migrations in a NestJS project</a>.</p>
</blockquote>
<p>Use this <code>dataSourceOptions</code> for setting up TypeOrmModule inside the DbModule that we generated using Nest CLI.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/db.module.ts</span>
<span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { TypeOrmModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
<span class="hljs-keyword">import</span> { dataSourceOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'./data-source'</span>;

<span class="hljs-meta">@Module</span>({
  imports: [TypeOrmModule.forRoot(dataSourceOptions)],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DbModule {}
</code></pre>
<p>Now if you run the project using <code>npm run start:dev</code> command, it should be able to connect to the database. However, you have to make sure the database server is up and running and the database with the name that we specified in the <code>data-source</code> file is already created.</p>
<h3 id="heading-generate-an-entity"><strong>Generate an entity</strong></h3>
<p>Generate <code>users</code> resource using the CLI.</p>
<pre><code class="lang-bash">nest g resource resource/users
</code></pre>
<p>It will generate the <code>users</code> module, controller, service, DTOs and entity file inside <code>src/resource/users</code> directory.</p>
<p>Let's update the <code>user.entity.ts</code> file and add some columns there.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/resources/users/entities/user.entity.ts</span>
<span class="hljs-keyword">import</span> { Column, Entity, PrimaryGeneratedColumn } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;

<span class="hljs-meta">@Entity</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> User {
  <span class="hljs-meta">@PrimaryGeneratedColumn</span>()
  id: <span class="hljs-built_in">number</span>;

  <span class="hljs-meta">@Column</span>({ name: <span class="hljs-string">'first_name'</span> })
  firstName: <span class="hljs-built_in">string</span>;

  <span class="hljs-meta">@Column</span>({ name: <span class="hljs-string">'last_name'</span> })
  lastName: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>Since we set <code>synchronize: true</code> in our datasource options, if we run this project using <code>npm start</code> command, it should create the <code>user</code> table in our database.</p>
<blockquote>
<p>The database should already be running in the specified host and port before running the app.</p>
</blockquote>
<p>If the <code>user</code> table is created successfully then we can start working on the seeding part.</p>
<h3 id="heading-seeding">Seeding</h3>
<p>We are going to use <a target="_blank" href="https://typeorm-extension.tada5hi.net/">typeorm-extension</a> for seeding our database. Let's install it first.</p>
<pre><code class="lang-bash">npm install typeorm-extension --save
</code></pre>
<p>Now we need a seeder class for seeding the <code>user</code> entity. <code>Seeder</code> interface is exported from <code>typeorm-extension</code>, we just need to implement it. Let's create the class in <code>src/db/seeds/user.seeder.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/seeds/user.seeder.ts</span>
<span class="hljs-keyword">import</span> { Seeder, SeederFactoryManager } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm-extension'</span>;
<span class="hljs-keyword">import</span> { DataSource } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../resources/users/entities/user.entity'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> UserSeeder <span class="hljs-keyword">implements</span> Seeder {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager,
  ): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">await</span> dataSource.query(<span class="hljs-string">'TRUNCATE "user" RESTART IDENTITY;'</span>);

    <span class="hljs-keyword">const</span> repository = dataSource.getRepository(User);
    <span class="hljs-keyword">await</span> repository.insert({
      firstName: <span class="hljs-string">'Mazedul'</span>,
      lastName: <span class="hljs-string">'Islam'</span>,
    });
  }
}
</code></pre>
<p>We have implemented the <code>run</code> method from the <code>Seeder</code> interface. When we execute the seed command using the <code>typeorm-extension</code> CLI, this run function will be executed. The <code>run</code> method has access to the <code>DataSource</code> and <code>SeederFactoryManager</code> instances, as they are passed as arguments.</p>
<p>At first, we truncate the <code>user</code> table and restart the identity sequence to make sure the first record will get ID 1. Then we get the user repository from the <code>dataSource</code> and insert our first user object into the table using the repository.</p>
<p>Now we need to tell the dataSource that we have this seeder class. To do that, let's update our <code>src/db/data-source.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
<span class="hljs-keyword">import</span> { SeederOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm-extension'</span>;
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions &amp; SeederOptions = {
  ...
  seeds: [<span class="hljs-string">'dist/db/seeds/**/*.js'</span>],
};
</code></pre>
<p>We have added <code>seeds</code> array into our <code>dataSourceOptions</code> object. Notice, we are using the glob pattern for pointing to the <code>seeds</code> directory inside the <code>dist/db/</code> directory. The glob pattern will not work if you point to the typescript files inside <code>src/db/seeds/</code> directory. Since our build command will generate the dist directory anyway, there is no harm using the <code>.js</code> files from there.</p>
<p>Now, add a script for seeding in the <code>package.json</code>.</p>
<pre><code class="lang-json"><span class="hljs-comment">// package.json</span>
{
  ...
  <span class="hljs-attr">"scripts"</span>: {
    ...
    <span class="hljs-attr">"seed"</span>: <span class="hljs-string">"npm run build &amp;&amp; ts-node ./node_modules/typeorm-extension/dist/cli/index.js seed -d ./src/db/data-source.ts"</span>
  }
}
</code></pre>
<p>In this command, we build the project first, then run the seed command from <code>typeorm-extension</code> with the help of <code>ts-node</code>.</p>
<blockquote>
<p>Note: using the <code>-d ./src/db/data-source.ts</code> we are passing the path to the datasource file to the seed command.</p>
</blockquote>
<p>Let's invoke the script.</p>
<pre><code class="lang-bash">npm run seed
</code></pre>
<p>Once we execute the command the <code>user</code> table in the database should be populated with one user.</p>
<h3 id="heading-factories-to-generate-bulk-data">Factories to generate bulk data</h3>
<p>To generate random data for an entity, create a factory for the desired entity.</p>
<p>The factory callback provides an instance of the <a target="_blank" href="https://fakerjs.dev/guide/"><strong>faker</strong></a> library as a function argument, to populate the entity with random data.</p>
<p>Create a factory function in <code>src/db/factories/user.factory.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/factories/user.factory.ts</span>
<span class="hljs-keyword">import</span> { setSeederFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm-extension'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../resources/users/entities/user.entity'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> setSeederFactory(User, <span class="hljs-function">(<span class="hljs-params">faker</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> User();

  <span class="hljs-keyword">const</span> sexFlag = faker.number.int(<span class="hljs-number">1</span>);
  <span class="hljs-keyword">const</span> sex: <span class="hljs-string">'male'</span> | <span class="hljs-string">'female'</span> = sexFlag ? <span class="hljs-string">'male'</span> : <span class="hljs-string">'female'</span>;

  user.firstName = faker.person.firstName(sex);
  user.lastName = faker.person.lastName(sex);

  <span class="hljs-keyword">return</span> user;
});
</code></pre>
<p>Now update the <code>user.seeder.ts</code> file and utilize this factory function for generating random users.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/seeds/user.seeder.ts</span>
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> UserSeeder <span class="hljs-keyword">implements</span> Seeder {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager,
  ): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    ...
    <span class="hljs-keyword">const</span> userFactory = factoryManager.get(User);
    <span class="hljs-comment">// save 1 factory generated entity, to the database</span>
    <span class="hljs-keyword">await</span> userFactory.save();

    <span class="hljs-comment">// save 5 factory generated entities, to the database</span>
    <span class="hljs-keyword">await</span> userFactory.saveMany(<span class="hljs-number">5</span>);
  }
}
</code></pre>
<p>Since we are using <code>SeederFactoryManager</code> instance to get the user factory, we need to update our <code>dataSourceOptions</code> and make it aware of the factory files.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions &amp; SeederOptions = {
  ...
  factories: [<span class="hljs-string">'dist/db/factories/**/*.js'</span>],
};
</code></pre>
<p>Our factory setup is done. Run the <code>seed</code> command again.</p>
<pre><code class="lang-bash">npm run seed
</code></pre>
<p>Check the <code>user</code> table, it should have 1 hardcoded user and 6 more random users.</p>
<h3 id="heading-seed-programmatically">Seed programmatically</h3>
<p>It is also possible to seed the database from the codebase, without using the CLI commands. To do that, we need to get access to the <code>DataSource</code> instance. There is a function called <a target="_blank" href="https://typeorm-extension.tada5hi.net/guide/seeding-api-reference.html#runseeder"><code>runSeeders</code></a> exported from <code>typeorm-extension</code> which can be used to seed data programmatically. This is especially useful in e2e tests. You can seed your in-memory test database on-the-fly before each test. Probably I'll cover this in another article for testing a NestJS application.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We have successfully configured SQL database seeding functionality using TypeORM and typeorm-extension package. We also get some insights on how to seed programmatically using <code>runSeeders</code> function provided by typeorm-extension.</p>
<p>You can find the full project here: <a target="_blank" href="https://github.com/mazid1/nestjs-typeorm/tree/feature/seed">https://github.com/mazid1/nestjs-typeorm/tree/feature/seed</a></p>
<p>In the next article, I'll write about how to test a NestJS application.</p>
<hr />
<p>I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at <a target="_blank" href="mailto:mazidmailbox@gmail.com"><strong>mazidmailbox@gmail.com</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[How to setup TypeORM migrations in a NestJS project]]></title><description><![CDATA[Motivation
TypeORM is the most mature Object Relational Mapper (ORM) for TypeScript and JavaScript. Since it's written in TypeScript, it integrates well with the NestJS framework. For integrating with SQL and NoSQL databases, Nest provides the @nestj...]]></description><link>https://blog.mazedul.dev/how-to-setup-typeorm-migrations-in-a-nestjs-project</link><guid isPermaLink="true">https://blog.mazedul.dev/how-to-setup-typeorm-migrations-in-a-nestjs-project</guid><category><![CDATA[nestjs]]></category><category><![CDATA[typeorm]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[migration]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Thu, 27 Jul 2023 17:19:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1690506161614/c4d0df11-d264-4f41-956d-0282c3f1ba38.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-motivation">Motivation</h3>
<p><a target="_blank" href="https://typeorm.io/">TypeORM</a> is the most mature Object Relational Mapper (ORM) for TypeScript and JavaScript. Since it's written in TypeScript, it integrates well with the <a target="_blank" href="https://nestjs.com/">NestJS</a> framework. For integrating with SQL and NoSQL databases, Nest provides the <code>@nestjs/typeorm</code> package. <a target="_blank" href="https://docs.nestjs.com/techniques/database#typeorm-integration">Here</a> the official documentation of NestJS has clear instructions on how to integrate TypeORM with it. However, they skipped the details on <a target="_blank" href="https://docs.nestjs.com/techniques/database#migrations">how to setup the migrations</a> and forwarded them to the TypeORM documentation for <a target="_blank" href="https://typeorm.io/migrations#creating-a-new-migration">migration</a>, which is very generic and not aligned with NestJS. That is the motivation of this article. In this article, I'll show how to setup migrations in a NestJS project using TypeORM. Buckle up and let's get started.</p>
<h3 id="heading-initial-project-setup">Initial project setup</h3>
<p>Let's initialize our project using <a target="_blank" href="https://docs.nestjs.com/cli/overview">Nest CLI</a>.</p>
<pre><code class="lang-bash">nest new nest-typeorm
</code></pre>
<p>Navigate to the project folder and install the necessary dependencies. In this example, I am using <a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a>, therefore I have added <code>pg</code> as a dependency.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> nest-typeorm
npm install --save @nestjs/typeorm typeorm pg
</code></pre>
<h3 id="heading-database-connection">Database connection</h3>
<p>Let's create a separate Nest module for handling database-related tasks.</p>
<pre><code class="lang-bash">nest g module db
</code></pre>
<p>Create the <code>data-source.ts</code> file inside the <code>src/db</code> directory. This file will contain database connection configurations.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
<span class="hljs-keyword">import</span> { config } <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
<span class="hljs-keyword">import</span> { DataSourceOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;

config();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions = {
  <span class="hljs-keyword">type</span>: <span class="hljs-string">'postgres'</span>,
  host: process.env.DB_HOST,
  port: +process.env.DB_PORT,
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [],
};
</code></pre>
<p>Use this <code>dataSourceOptions</code> for setting up TypeOrmModule inside the DbModule that we generated using Nest CLI.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/db.module.ts</span>
<span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { TypeOrmModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/typeorm'</span>;
<span class="hljs-keyword">import</span> { dataSourceOptions } <span class="hljs-keyword">from</span> <span class="hljs-string">'./data-source'</span>;

<span class="hljs-meta">@Module</span>({
  imports: [TypeOrmModule.forRoot(dataSourceOptions)],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DbModule {}
</code></pre>
<p>Now if you run the project using <code>npm run start:dev</code> command, it should be able to connect to the database. However, you have to make sure the database server is up and running and the database with the name that we specified in the <code>data-source</code> file is already created.</p>
<h3 id="heading-generate-an-entity">Generate an entity</h3>
<p>Generate <code>users</code> resource using the CLI.</p>
<pre><code class="lang-bash">nest g resource resource/users
</code></pre>
<p>It will generate the <code>users</code> module, controller, service, DTOs and entity file inside <code>src/resource/users</code> directory.</p>
<p>Let's update the <code>user.entity.ts</code> file and add some columns there.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/resources/users/entities/user.entity.ts</span>
<span class="hljs-keyword">import</span> { Column, Entity, PrimaryGeneratedColumn } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;

<span class="hljs-meta">@Entity</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> User {
  <span class="hljs-meta">@PrimaryGeneratedColumn</span>()
  id: <span class="hljs-built_in">number</span>;

  <span class="hljs-meta">@Column</span>({ name: <span class="hljs-string">'first_name'</span> })
  firstName: <span class="hljs-built_in">string</span>;

  <span class="hljs-meta">@Column</span>({ name: <span class="hljs-string">'last_name'</span> })
  lastName: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>Now we need to create the migration for creating the user table with the id, first_name, last_name columns.</p>
<h3 id="heading-generate-the-migration">Generate the migration</h3>
<p>Migration in TypeORM is just a class that implements <code>MigrationInterface</code> from the <code>typeorm</code> package. It needs to implement the <code>up</code> and <code>down</code> methods. The <code>up</code> method executes the query for applying new changes in the database. On the other hand, <code>down</code> method executes the query to revert the changes that was applied by the <code>up</code> method.</p>
<p>It is possible to implement a migration by manually implementing the <code>MigrationInterface</code>. However, TypeORM provides a nice <a target="_blank" href="https://typeorm.io/using-cli">CLI</a> to generate that automatically based on the data-source and entity.</p>
<p>Now the first thing is, we need to make TypeORM aware that we have an entity called <code>user</code>. To do that, we need to update the <code>entities</code> array in the data-source file. The entities array accepts both entity classes and directories from where entities need to be loaded. Directories support glob pattern. In our case, we want every entity from our resources directory to be loaded.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions = {
  ...
  entities: [<span class="hljs-string">'src/resources/**/*.entity.ts'</span>],
};
</code></pre>
<p>Though we are working with typescript, the above code will throw <code>Cannot use import statement outside a module</code> error. To make it work smoothly it is safe to use <code>.js</code> files. Our build command will generate the javascript files during build time and put them into <code>/dist</code> directory. We can simply use that path here.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions = {
  ...
  entities: [<span class="hljs-string">'dist/resources/**/*.entity.js'</span>],
};
</code></pre>
<p>At this point, our project is ready for generating the first migration. We will use <a target="_blank" href="https://typeorm.io/using-cli">TypeORM CLI</a> to generate the migration file. The command for generating a migration file from a data-source is like this.</p>
<pre><code class="lang-bash">npx typeorm migration:generate -d path/to/datasource path/to/Migration
</code></pre>
<p>However, we are using entity path from <code>dist</code> directory. That means, we need to run the build command before using the typeorm command. We can combined those commands like this.</p>
<pre><code class="lang-bash">npm run build &amp;&amp; npx typeorm migration:generate -d path/to/datasource path/to/Migration
</code></pre>
<p>Let's add a script in our <code>package.json</code> file combining these commands.</p>
<pre><code class="lang-json"><span class="hljs-comment">// package.json</span>
{
  ...
  <span class="hljs-attr">"scripts"</span>: {
    ...
    <span class="hljs-attr">"migration:generate"</span>: <span class="hljs-string">"npm run build &amp;&amp; npx typeorm migration:generate -d ./dist/db/data-source.js"</span>,
  }
}
</code></pre>
<p>Notice that, we have added the path to data-soure file in the script but we omitted the path to migration file. We want to pass that dynamically from the command line when we invoke the script.</p>
<p>Let's invoke the script.</p>
<pre><code class="lang-bash">npm run migration:generate src/db/migrations/create-user-table
</code></pre>
<p>Once we execute this command in the terminal it will generate the migration file in the <code>src/db/migrations</code> directory. The file name will be something like <code>&lt;timestamp&gt;-create-user-table.ts</code></p>
<p>If you open the migration file you can see the code like below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/migrations/&lt;timestamp&gt;-create-user-table.ts</span>
<span class="hljs-keyword">import</span> { MigrationInterface, QueryRunner } <span class="hljs-keyword">from</span> <span class="hljs-string">"typeorm"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CreateUserTable1690462207991 <span class="hljs-keyword">implements</span> MigrationInterface {
    name = <span class="hljs-string">'CreateUserTable1690462207991'</span>

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> up(queryRunner: QueryRunner): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
        <span class="hljs-keyword">await</span> queryRunner.query(<span class="hljs-string">`CREATE TABLE "user" ("id" SERIAL NOT NULL, "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> down(queryRunner: QueryRunner): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
        <span class="hljs-keyword">await</span> queryRunner.query(<span class="hljs-string">`DROP TABLE "user"`</span>);
    }

}
</code></pre>
<h3 id="heading-run-the-migration">Run the migration</h3>
<p>The migration file is generated. Now we need to execute it to update our database structure.</p>
<p>Before using the <code>migration:run</code> command from TypeORM CLI we need to update the <code>dataSourceOptions</code> and add <code>migrations</code> array to tell TypeORM from where it should read the migration files.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/db/data-source.ts</span>
...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dataSourceOptions: DataSourceOptions &amp; SeederOptions = {
  ...
  migrations: [<span class="hljs-string">'dist/db/migrations/*.js'</span>],
};
</code></pre>
<p>We have used the glob pattern to pick migration files from the <code>dist</code> directory (similar to <code>entities</code> array that we added before). Therefore, we must build the project before running the migration command. Same as before, we can combine the build and migration commands in the <code>package.json</code> file.</p>
<p>Let's add the script to run the migration in our <code>package.json</code> file.</p>
<pre><code class="lang-json"><span class="hljs-comment">// package.json</span>
{
  ...
  <span class="hljs-attr">"scripts"</span>: {
    ...
    <span class="hljs-attr">"migration:run"</span>: <span class="hljs-string">"npm run build &amp;&amp; npx typeorm migration:run -d ./dist/db/data-source.js"</span>,
  }
}
</code></pre>
<p>Run the script through the terminal.</p>
<pre><code class="lang-bash">npm run migration:run
</code></pre>
<p>If you check your database now, you can see the <code>user</code> table is created.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We have learned how to set up migration in a NestJS project using TypeORM.</p>
<p>You can find the full project here:<br /><a target="_blank" href="https://github.com/mazid1/nestjs-typeorm/tree/feature/migration">https://github.com/mazid1/nestjs-typeorm/tree/feature/migration</a></p>
<p>In the next article, I will write about <a target="_blank" href="https://blog.mazedulislam.com/how-to-seed-database-using-typeorm-in-a-nestjs-project">how to seed test data using TypeORM.</a></p>
<hr />
<p>I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at <strong>mazidmailbox@gmail.com</strong></p>
]]></content:encoded></item><item><title><![CDATA[Generate bootstrap-like spacing classes using SASS]]></title><description><![CDATA[Motivation
Last month we initialized a new project with Angular and Angular Material. As time goes on, I have noticed that some of my colleagues are often writing inline styles for margin and padding.
<div style="margin-top: 5px; margin-bottom: 5px">...]]></description><link>https://blog.mazedul.dev/generate-bootstrap-like-spacing-classes-using-sass</link><guid isPermaLink="true">https://blog.mazedul.dev/generate-bootstrap-like-spacing-classes-using-sass</guid><category><![CDATA[Bootstrap]]></category><category><![CDATA[Sass]]></category><category><![CDATA[scss]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Mazedul Islam]]></dc:creator><pubDate>Fri, 04 Feb 2022 12:09:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1690632392145/720fe158-3cdd-4095-a66a-527a5ef4d248.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-motivation">Motivation</h3>
<p>Last month we initialized a new project with <a target="_blank" href="https://angular.io/">Angular</a> and <a target="_blank" href="https://material.angular.io/">Angular Material</a>. As time goes on, I have noticed that some of my colleagues are often writing inline styles for margin and padding.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"margin-top: 5px; margin-bottom: 5px"</span>&gt;</span>
  With inline style
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Some of them (including me) are creating similar CSS classes for almost every component wherever needed.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.set-padding</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">0</span>;
}
<span class="hljs-selector-class">.left-spacer</span> {
  <span class="hljs-attribute">margin-left</span>: <span class="hljs-number">10px</span>;
}
</code></pre>
<p>Then I realized that we are missing bootstrap-like margin and padding classes. One of my colleagues suggested using <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a>. However, as we are already using Angular Material for our component library, installing a CSS framework only for some spacing classes will be overkill for sure. So, I decided to write these classes on my own.</p>
<h3 id="heading-bootstrap-classes-a-closer-look">Bootstrap Classes: A Closer Look</h3>
<p>According to the documentation of <a target="_blank" href="https://getbootstrap.com/docs/4.1/utilities/spacing/">Bootstrap 4</a> its spacing classes have the following format:</p>
<p>The classes are named using the format <code>{property}{sides}-{size}</code> for <code>xs</code> and <code>{property}{sides}-{breakpoint}-{size}</code> for <code>sm</code>, <code>md</code>, <code>lg</code>, and <code>xl</code>.</p>
<p>Where <code>property</code> is one of the:</p>
<ul>
<li><p><code>m</code> - for classes that set <code>margin</code></p>
</li>
<li><p><code>p</code> - for classes that set <code>padding</code></p>
</li>
</ul>
<p>Where <code>sides</code> is one of the:</p>
<ul>
<li><p><code>t</code> - for classes that set <code>margin-top</code> or <code>padding-top</code></p>
</li>
<li><p><code>b</code> - for classes that set <code>margin-bottom</code> or <code>padding-bottom</code></p>
</li>
<li><p><code>l</code> - for classes that set <code>margin-left</code> or <code>padding-left</code></p>
</li>
<li><p><code>r</code> - for classes that set <code>margin-right</code> or <code>padding-right</code></p>
</li>
<li><p><code>x</code> - for classes that set both <code>*-left</code> and <code>*-right</code></p>
</li>
<li><p><code>y</code> - for classes that set both <code>*-top</code> and <code>*-bottom</code></p>
</li>
<li><p>blank - for classes that set a <code>margin</code> or <code>padding</code> on all 4 sides of the element</p>
</li>
</ul>
<p>Where <code>size</code> is one of the:</p>
<ul>
<li><p><code>0</code> - for classes that eliminate the <code>margin</code> or <code>padding</code> by setting it to <code>0</code></p>
</li>
<li><p><code>1</code> - (by default) for classes that set the <code>margin</code> or <code>padding</code> to <code>$spacer * .25</code></p>
</li>
<li><p><code>2</code> - (by default) for classes that set the <code>margin</code> or <code>padding</code> to <code>$spacer * .5</code></p>
</li>
<li><p><code>3</code> - (by default) for classes that set the <code>margin</code> or <code>padding</code> to <code>$spacer</code></p>
</li>
<li><p><code>4</code> - (by default) for classes that set the <code>margin</code> or <code>padding</code> to <code>$spacer * 1.5</code></p>
</li>
<li><p><code>5</code> - (by default) for classes that set the <code>margin</code> or <code>padding</code> to <code>$spacer * 3</code></p>
</li>
<li><p><code>auto</code> - for classes that set the <code>margin</code> to auto</p>
</li>
</ul>
<h3 id="heading-define-our-requirements">Define Our Requirements</h3>
<p>Our focus is to generate all the CSS classes of the format <code>{property}{sides}-{size}</code>.</p>
<p>For example:</p>
<ol>
<li><p><code>m-0</code> to <code>m-5</code> and <code>m-auto</code></p>
</li>
<li><p><code>p-0</code> to <code>p-5</code></p>
</li>
<li><p><code>mt-0</code>, <code>mb-0</code>, <code>ml-0</code>, <code>mr-0</code> to <code>mt-5</code>, <code>mb-5</code>, <code>ml-5</code>, <code>mr-5</code> and <code>mt-auto</code>, <code>mb-auto</code>, <code>ml-auto</code>, <code>mr-auto</code></p>
</li>
<li><p><code>pt-0</code>, <code>pb-0</code>, <code>pl-0</code>, <code>pr-0</code> to <code>pt-5</code>, <code>pb-5</code>, <code>pl-5</code>, <code>pr-5</code></p>
</li>
<li><p><code>mx-0</code> to <code>mx-5</code> and <code>my-0</code> to <code>my-5</code> and <code>mx-auto</code>, <code>my-auto</code></p>
</li>
<li><p><code>px-0</code> to <code>px-5</code> and <code>py-0</code> to <code>my-5</code></p>
</li>
</ol>
<p>Notice, we are omitting <code>{property}{sides}-{breakpoint}-{size}</code> pattern, which is not in the scope of this article.</p>
<h3 id="heading-sass-implementation">SASS Implementation</h3>
<p>I am going to use <a target="_blank" href="https://sass-lang.com/documentation/syntax#scss">SCSS</a> syntax, you can also use the original <a target="_blank" href="https://sass-lang.com/documentation/syntax#the-indented-syntax">SASS</a> if you find that easier to use.</p>
<p>Let's create a new file <code>_spaces.scss</code>. The filename starts with <code>_</code> because I want to make it a partial sass file. You can check the <a target="_blank" href="https://sass-lang.com/guide">Sass guide</a> if you do not know what a partial Sass file means.</p>
<p>Create a variable <code>$spacer</code> with the default value for space.</p>
<pre><code class="lang-scss"><span class="hljs-comment">// _spaces.scss</span>
<span class="hljs-variable">$spacer</span>: <span class="hljs-number">1rem</span> !default;
</code></pre>
<p>Then create a <a target="_blank" href="https://sass-lang.com/documentation/modules/map"><code>sass:map</code></a> with keys from <code>0</code> to <code>5</code> and <code>auto</code> and set the calculated value for each key.</p>
<pre><code class="lang-scss"><span class="hljs-variable">$spacers</span>: (
  <span class="hljs-number">0</span>: <span class="hljs-number">0</span>,
  <span class="hljs-number">1</span>: <span class="hljs-variable">$spacer</span> * <span class="hljs-number">0.25</span>,
  <span class="hljs-number">2</span>: <span class="hljs-variable">$spacer</span> * <span class="hljs-number">0.5</span>,
  <span class="hljs-number">3</span>: <span class="hljs-variable">$spacer</span>,
  <span class="hljs-number">4</span>: <span class="hljs-variable">$spacer</span> * <span class="hljs-number">1.5</span>,
  <span class="hljs-number">5</span>: <span class="hljs-variable">$spacer</span> * <span class="hljs-number">3</span>,
  auto: auto,
) !default;
</code></pre>
<p>Now, let's loop through the map and generate classes from <code>m-0</code> to <code>m-5</code> and <code>m-auto</code>.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-comment">// generate m-* classes</span>
  <span class="hljs-selector-class">.m-</span>#{<span class="hljs-variable">$key</span>} {
    <span class="hljs-attribute">margin</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
  }
}
</code></pre>
<p>We can also generate padding classes inside this loop. Let's generate classes from <code>p-0</code> to <code>p-5</code>.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-comment">// generate m-* classes</span>
  <span class="hljs-selector-class">.m-</span>#{<span class="hljs-variable">$key</span>} {
    <span class="hljs-attribute">margin</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
  }

  <span class="hljs-comment">// generate p-* classes</span>
  <span class="hljs-selector-class">.p-</span>#{<span class="hljs-variable">$key</span>} {
    <span class="hljs-attribute">padding</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
  }
}
</code></pre>
<p>However, this code will also generate <code>.p-auto { padding: auto !important; }</code> which is incorrect. So, we need to exclude the <code>auto</code> key when generating <code>.p-*</code> classes.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-comment">// generate m-* classes</span>
  <span class="hljs-selector-class">.m-</span>#{<span class="hljs-variable">$key</span>} {
    <span class="hljs-attribute">margin</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
  }

  <span class="hljs-comment">// generate p-* classes excluding key = auto</span>
  <span class="hljs-keyword">@if</span> <span class="hljs-variable">$key</span> != auto {
    <span class="hljs-selector-class">.p-</span>#{<span class="hljs-variable">$key</span>} {
      <span class="hljs-attribute">padding</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
    }
  }
}
</code></pre>
<p>Till now, we have covered the following cases:</p>
<ol>
<li><p><code>m-0</code> to <code>m-5</code> and <code>m-auto</code></p>
</li>
<li><p><code>p-0</code> to <code>p-5</code></p>
</li>
</ol>
<p>Let's focus on the next two cases:</p>
<ol>
<li><p><code>mt-0</code>, <code>mb-0</code>, <code>ml-0</code>, <code>mr-0</code> to <code>mt-5</code>, <code>mb-5</code>, <code>ml-5</code>, <code>mr-5</code> and <code>mt-auto</code>, <code>mb-auto</code>, <code>ml-auto</code>, <code>mr-auto</code></p>
</li>
<li><p><code>pt-0</code>, <code>pb-0</code>, <code>pl-0</code>, <code>pr-0</code> to <code>pt-5</code>, <code>pb-5</code>, <code>pl-5</code>, <code>pr-5</code></p>
</li>
</ol>
<p>Now we need to generate classes that can target specific sides. Therefore create a <a target="_blank" href="https://sass-lang.com/documentation/modules/list"><code>sass:list</code></a> containing all the sides.</p>
<pre><code class="lang-scss"><span class="hljs-variable">$sides</span>: (top, bottom, left, right);
</code></pre>
<p>For each key presents in the <code>$spacers</code> map we need to generate classes combining each direction/side. Therefore, we need a nested loop like this.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-keyword">@each</span> <span class="hljs-variable">$side</span> in <span class="hljs-variable">$sides</span> {
    <span class="hljs-comment">// generate m* classes</span>
    <span class="hljs-selector-class">.m</span>#{str-slice(<span class="hljs-variable">$side</span>, 0, 1)}-#{<span class="hljs-variable">$key</span>} {
      <span class="hljs-attribute">margin</span>-#{<span class="hljs-variable">$side</span>}: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
    }
  }
}
</code></pre>
<p>Same as before, we can generate padding classes (excluding <code>auto</code> key) inside this loop.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-keyword">@each</span> <span class="hljs-variable">$side</span> in <span class="hljs-variable">$sides</span> {
    <span class="hljs-comment">// generate m* classes</span>
    <span class="hljs-selector-class">.m</span>#{str-slice(<span class="hljs-variable">$side</span>, 0, 1)}-#{<span class="hljs-variable">$key</span>} {
      <span class="hljs-attribute">margin</span>-#{<span class="hljs-variable">$side</span>}: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
    }

    <span class="hljs-comment">// generate p* classes excluding key = auto</span>
    <span class="hljs-keyword">@if</span> <span class="hljs-variable">$key</span> != auto {
      <span class="hljs-selector-class">.p</span>#{str-slice(<span class="hljs-variable">$side</span>, 0, 1)}-#{<span class="hljs-variable">$key</span>} {
        <span class="hljs-attribute">padding</span>-#{<span class="hljs-variable">$side</span>}: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
      }
    }
  }
}
</code></pre>
<p>Now we are only left with the following two cases to generate:</p>
<ol>
<li><p><code>mx-0</code> to <code>mx-5</code> and <code>my-0</code> to <code>my-5</code> and <code>mx-auto</code>, <code>my-auto</code></p>
</li>
<li><p><code>px-0</code> to <code>px-5</code> and <code>py-0</code> to <code>my-5</code></p>
</li>
</ol>
<p>To generate classes for <code>x</code> and <code>y</code> axes let's create a new <a target="_blank" href="https://sass-lang.com/documentation/modules/list"><code>sass:list</code></a>.</p>
<pre><code class="lang-scss"><span class="hljs-variable">$axises</span>: (x, y);
</code></pre>
<p>Same as before, for each key value present in <code>$spacers</code> combining each axis present in <code>$axis</code> list we need to generate classes. So, we need to use nested loops again.</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span> in <span class="hljs-variable">$spacers</span> {
  <span class="hljs-keyword">@each</span> <span class="hljs-variable">$axis</span> in <span class="hljs-variable">$axises</span> {
    <span class="hljs-keyword">@if</span> <span class="hljs-variable">$axis</span> == x {
      <span class="hljs-comment">// generate classes for x axis</span>

      <span class="hljs-comment">// generate mx-* classes</span>
      <span class="hljs-selector-class">.m</span>#{<span class="hljs-variable">$axis</span>}-#{<span class="hljs-variable">$key</span>} {
        <span class="hljs-attribute">margin-left</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
        <span class="hljs-attribute">margin-right</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
      }

      <span class="hljs-comment">// generate px-* classes excluding key = auto</span>
      <span class="hljs-keyword">@if</span> <span class="hljs-variable">$key</span> != auto {
        <span class="hljs-selector-class">.p</span>#{<span class="hljs-variable">$axis</span>}-#{<span class="hljs-variable">$key</span>} {
          <span class="hljs-attribute">padding-left</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
          <span class="hljs-attribute">padding-right</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
        }
      }
    } <span class="hljs-keyword">@else</span> if <span class="hljs-variable">$axis</span> == y {
      <span class="hljs-comment">// generate classes for y axis</span>

      <span class="hljs-comment">// generate my-* classes</span>
      <span class="hljs-selector-class">.m</span>#{<span class="hljs-variable">$axis</span>}-#{<span class="hljs-variable">$key</span>} {
        <span class="hljs-attribute">margin-top</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
        <span class="hljs-attribute">margin-bottom</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
      }

      <span class="hljs-comment">// generate py-* classes excluding key = auto</span>
      <span class="hljs-keyword">@if</span> <span class="hljs-variable">$key</span> != auto {
        <span class="hljs-selector-class">.p</span>#{<span class="hljs-variable">$axis</span>}-#{<span class="hljs-variable">$key</span>} {
          <span class="hljs-attribute">padding-top</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
          <span class="hljs-attribute">padding-bottom</span>: #{<span class="hljs-variable">$value</span>} <span class="hljs-meta">!important</span>;
        }
      }
    } <span class="hljs-keyword">@else</span> {
      <span class="hljs-keyword">@error</span> <span class="hljs-string">"Unknown axis #{$axis}."</span>;
    }
  }
}
</code></pre>
<h3 id="heading-usage">Usage</h3>
<p>If your project setup supports a Scss file, then you can import this file into your root style file using <a target="_blank" href="https://sass-lang.com/documentation/at-rules/use"><code>@use</code></a> rule.</p>
<pre><code class="lang-scss"><span class="hljs-comment">// styles.scss file, in the same directory of _spaces.scss</span>
<span class="hljs-keyword">@use</span> <span class="hljs-string">"./spaces"</span>;
</code></pre>
<p>If you need compiled css, then you can install <a target="_blank" href="https://sass-lang.com/guide">SASS</a> on your machine and use the following command to generate <code>spaces.css</code> file.</p>
<pre><code class="lang-bash">sass _spaces.scss spaces.css
</code></pre>
<p>After adding this, now we can use bootstrap-like CSS classes in our HTML code.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"my-2 px-2"</span>&gt;</span>
  Styled with bootstrap like classes!
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>The complete source code is available <a target="_blank" href="https://github.com/mazid1/bs-spacing/blob/main/src/_spacing.scss">here</a>.</p>
<hr />
<p>Thank you for reading. Your appreciation is my motivation!</p>
<p>Follow me on social media:</p>
<ul>
<li><p>Linkedin: <a target="_blank" href="https://www.linkedin.com/in/mazedul-islam/">mazedul-islam</a></p>
</li>
<li><p>Twitter: <a target="_blank" href="https://twitter.com/mazedul__islam">@mazedul__islam</a></p>
</li>
<li><p>Github: <a target="_blank" href="https://github.com/mazid1">mazid1</a></p>
</li>
<li><p>Website: <a target="_blank" href="http://mazedulislam.com">mazedulislam.com</a></p>
</li>
</ul>
<hr />
<p>I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at <a target="_blank" href="http://mailto:mazidmailbox@gmail.com">mazidmailbox@gmail.com</a></p>
]]></content:encoded></item></channel></rss>