<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Madhav Pandey</title>
    <description>The latest articles on DEV Community by Madhav Pandey (@pmadhav82).</description>
    <link>https://dev.to/pmadhav82</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F848708%2F36b606b8-f196-4237-bb31-fc7eaf03033e.jpeg</url>
      <title>DEV Community: Madhav Pandey</title>
      <link>https://dev.to/pmadhav82</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pmadhav82"/>
    <language>en</language>
    <item>
      <title>How I Deployed My Node.js App on a VPS With a Custom Domain &amp; Free SSL</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Fri, 13 Feb 2026 09:34:40 +0000</pubDate>
      <link>https://dev.to/pmadhav82/how-i-deployed-my-nodejs-app-on-a-vps-with-a-custom-domain-free-ssl-55a2</link>
      <guid>https://dev.to/pmadhav82/how-i-deployed-my-nodejs-app-on-a-vps-with-a-custom-domain-free-ssl-55a2</guid>
      <description>&lt;p&gt;I deployed my personal project &lt;a href="https://pblog.online/" rel="noopener noreferrer"&gt;PBlog&lt;/a&gt; to a real VPS, connected it to my custom domain &lt;a href="https://pblog.online/" rel="noopener noreferrer"&gt;pblog.online&lt;/a&gt;, and secured it with free HTTPS.&lt;br&gt;
This post documents the exact steps I followed, from a blank server to a fully production-ready app.&lt;/p&gt;

&lt;p&gt;If you're new to VPS deployments, this guide will walk you through everything I learned along the way.&lt;/p&gt;

&lt;p&gt;### 1. Creating VPS&lt;br&gt;
I used Kamatera to create an Ubuntu server.&lt;br&gt;
Once the server was ready, I connected via SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@YOUR_SERVER_IP

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo49xus2f3a5ypse2hg3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo49xus2f3a5ypse2hg3c.png" alt=" " width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Installing Node.js and Git
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install nodejs git -y

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Cloning and running the app on a VPS
&lt;/h3&gt;

&lt;p&gt;I cloned my project from GitHub and ran it on that server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/pmadhav82/P_blog.git
cd P_blog
npm install

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Running the app with &lt;a href="https://pm2.keymetrics.io/docs/usage/quick-start/" rel="noopener noreferrer"&gt;pm2&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To keep the app running 24/7, I installed pm2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo npm install -g pm2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;started the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm2 start app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the app is running on port 8000 and can be accessed by visiting &lt;code&gt;publicIP:8000&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Installing and Configuring &lt;a href="https://nginx.org/en/docs/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;I installed Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install nginx -y

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Usefull Nginx Commands
&lt;/h5&gt;




&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Check Nginx status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl status nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start Nginx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl start nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop Nginx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl stop nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart Nginx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reload Nginx (safe reload after config changes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl reload nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test Nginx configuration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View error logs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo tail -f /var/log/nginx/error.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List enabled sites&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ls /etc/nginx/sites-enabled/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List available sites&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ls /etc/nginx/sites-available/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable a site (create symlink)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/yourfile.conf /etc/nginx/sites-enabled/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disable a site (remove symlink)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo rm /etc/nginx/sites-enabled/yourfile.conf&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;At this point, visiting the server's IP address showed the default Nginx welcome page. In order to see the app when visiting the server IP, we have to remove the default Nginx site and create a reverse proxy config.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Reverse Proxy Config
&lt;/h4&gt;

&lt;p&gt;I created a new config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/pblog.conf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enabled it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/pblog.conf /etc/nginx/sites-enabled/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tested and reloaded Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nginx -t
sudo systemctl reload nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this moment, visiting the server IP will show the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Connecting Domain
&lt;/h3&gt;

&lt;p&gt;I bought a domain from Namecheap. Inside Namecheap -&amp;gt; Advanced DNS, I added two A records where the value is my server's public IP:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9v7wc67yabywoyrtjpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9v7wc67yabywoyrtjpg.png" alt=" " width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I updated the &lt;code&gt;pblog.conf&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/pblog.conf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updated it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80;
    server_name pblog.online www.pblog.online;

    location / {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and reloaded Nginx;&lt;br&gt;
&lt;code&gt;sudo systemctl reload nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After a few minutes, &lt;a href="https://pblog.online/" rel="noopener noreferrer"&gt;pblog.online&lt;/a&gt; pointed to my app, but it wasn't secured.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Adding Free SSL With Certbot
&lt;/h3&gt;

&lt;p&gt;I installed Certbot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install certbot python3-certbot-nginx -y

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requested SSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot --nginx -d pblog.online -d www.pblog.online

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I followed the prompt and Certbot automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Generated certificates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updated my Nginx config&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reloaded Nginx&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now my site is live at:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://pblog.online&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos1vhqi2oi107z69yseb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos1vhqi2oi107z69yseb.png" alt=" " width="581" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5meukfi6npebr7cleu3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5meukfi6npebr7cleu3w.png" alt=" " width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deploying my app to a VPS taught me a lot about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Linux servers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nginx reverse proxies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DNS configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSL certificates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production Node.js setups&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now my project is fully live, secure, and running on my own infrastructure.&lt;/p&gt;

&lt;p&gt;If you're deploying your first app, I hope this guide helps you avoid the confusion I had at the beginning.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to upload image to Firebase Storage from your expressjs app.</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Fri, 28 Nov 2025 09:17:31 +0000</pubDate>
      <link>https://dev.to/pmadhav82/how-to-upload-image-to-firebase-storage-from-your-expressjs-app-35j3</link>
      <guid>https://dev.to/pmadhav82/how-to-upload-image-to-firebase-storage-from-your-expressjs-app-35j3</guid>
      <description>&lt;p&gt;This write-up outlines how we implemented image uploading, listing, and deletion using Firebase Storage (via the Firebase Admin SDK) in the P_Blog project. The post documents the code, how it works, potential issues, and suggestions for improvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Key components and files&lt;/li&gt;
&lt;li&gt;Upload middleware (Multer) setup&lt;/li&gt;
&lt;li&gt;Upload route (save file to Firebase Storage)&lt;/li&gt;
&lt;li&gt;Listing images (getFiles)&lt;/li&gt;
&lt;li&gt;Deleting images (file.delete)&lt;/li&gt;
&lt;li&gt;Frontend confirmation UX&lt;/li&gt;
&lt;li&gt;Security &amp;amp; Best practices&lt;/li&gt;
&lt;li&gt;Testing and verification&lt;/li&gt;
&lt;li&gt;Suggested improvements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The process implemented in P_Blog is simple and robust for a small to medium-scale project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User uploads an image via a form.&lt;/li&gt;
&lt;li&gt;The server receives the image buffer using Multer (memory storage).&lt;/li&gt;
&lt;li&gt;The server saves the image to Firebase Storage using the Admin SDK.&lt;/li&gt;
&lt;li&gt;The uploaded file is made public (via &lt;code&gt;file.makePublic()&lt;/code&gt;), so a public URL can be used in the site.&lt;/li&gt;
&lt;li&gt;Admins can list and delete images using the Admin UI.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Key files and their roles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;utils/firebase-admin-config.js&lt;/code&gt; — Initializes Firebase Admin SDK (service account). Exports the firebase object.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils/firebaseImageUpload.js&lt;/code&gt; — Multer configuration for image uploads (max size, memory storage, and fileFilter).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;routes/firebaseUploadImageRoute.js&lt;/code&gt; — Upload route that saves the image buffer into Firebase Storage and returns a public URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;routes/adminManageMediaRoute.js&lt;/code&gt; — Admin route for listing images (&lt;code&gt;GET /admin/manage-media&lt;/code&gt;) and deleting images (&lt;code&gt;POST /admin/manage-media/delete&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;views/firebase-media.handlebars&lt;/code&gt; — Admin HTML that lists images, displays an image modal, and a delete confirmation modal.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Firebase Admin Setup
&lt;/h2&gt;

&lt;p&gt;The Firebase Admin SDK is initialized in &lt;code&gt;utils/firebase-admin-config.js&lt;/code&gt; using a service account JSON placed outside source control (&lt;code&gt;service-account-key.json&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase-admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../service-account-key.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceAccount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This allows server-side operations (creating, deleting files and generating signed URLs) using your Firebase project credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Upload middleware (Multer)
&lt;/h2&gt;

&lt;p&gt;We use Multer with memory storage because the Firebase Admin SDK accepts a Buffer as input. It's configured to limit file size and filter file types.&lt;/p&gt;

&lt;p&gt;Key snippet (&lt;code&gt;utils/firebaseImageUpload.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseUpload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 12MB&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryStorage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;fileFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedFileType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowedFileType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please select a valid image file (PNG, JPG, JPEG, GIF)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Note: For very large files, or to reduce memory usage, consider streaming uploads or a temporary storage hook. For the &lt;code&gt;memoryStorage()&lt;/code&gt; approach this project uses, make sure your server memory can handle the largest eligible file and the concurrent requests you expect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Upload route — Saving to Firebase Storage
&lt;/h2&gt;

&lt;p&gt;The upload route at &lt;code&gt;/firebase-image-upload&lt;/code&gt; accepts a &lt;code&gt;POST&lt;/code&gt; request with a &lt;code&gt;firebase-image&lt;/code&gt; field (single file). It performs the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the file exists in &lt;code&gt;req.file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a random filename using &lt;code&gt;crypto.randomBytes&lt;/code&gt; and append the original extension (via &lt;code&gt;path.extname&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Save the buffer using &lt;code&gt;file.save(req.file.buffer, ...)&lt;/code&gt; with proper metadata.&lt;/li&gt;
&lt;li&gt;Make the file public using &lt;code&gt;file.makePublic()&lt;/code&gt; and return a public URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Key snippet (&lt;code&gt;routes/firebaseUploadImageRoute.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/firebase-admin-config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseUpload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/firebaseImageUpload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/loginHandeler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseImageUploadRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BUCKET_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pblog-5795d.firebasestorage.app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;firebaseImageUploadRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;firebaseUpload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase-image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No image selected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileExt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Keep leading dot if any.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}${&lt;/span&gt;&lt;span class="nx"&gt;fileExt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// NOTE: Avoid double dot.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makePublic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://storage.googleapis.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicUrl&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important improvement: use &lt;code&gt;fileName&lt;/code&gt; without an extra &lt;code&gt;.&lt;/code&gt; in the template. The code above demonstrates the corrected string concatenation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on &lt;code&gt;makePublic()&lt;/code&gt;:&lt;/strong&gt; Calling &lt;code&gt;file.makePublic()&lt;/code&gt; sets object ACL to be readable by anyone, making the file publicly accessible. If you want the image to be private, consider using signed URLs generated by Firebase: &lt;code&gt;file.getSignedUrl({ action: 'read', expires: '03-09-2491' })&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  List images (Admin) — Reading the bucket
&lt;/h2&gt;

&lt;p&gt;To list images the admin created a route at &lt;code&gt;GET /admin/manage-media&lt;/code&gt; that calls &lt;code&gt;bucket.getFiles()&lt;/code&gt; and passes the results to a view.&lt;/p&gt;

&lt;p&gt;Key snippet (&lt;code&gt;routes/adminManageMediaRoute.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;adminManageMediaRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://storage.googleapis.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase-media&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getFiles()&lt;/code&gt; returns an array for the first page of results by default. If you expect many files, implement pagination with &lt;code&gt;pageToken&lt;/code&gt; and &lt;code&gt;maxResults&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We URL-encode the file name with &lt;code&gt;encodeURIComponent&lt;/code&gt; for safe use in the URL.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Delete images — Server-side logic
&lt;/h2&gt;

&lt;p&gt;The admin view posts a delete form to &lt;code&gt;/admin/manage-media/delete&lt;/code&gt; with &lt;code&gt;imageName&lt;/code&gt; in the request body. The route deletes the file from the Firebase bucket.&lt;/p&gt;

&lt;p&gt;Key snippet (&lt;code&gt;routes/adminManageMediaRoute.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;adminManageMediaRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;imageName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Image deleted successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to delete the image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/manage-media&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Safety notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;code&gt;req.body.imageName&lt;/code&gt; rather than a path parameter because filenames may contain &lt;code&gt;/&lt;/code&gt; characters and a request body avoids path parsing errors when filenames include slashes.&lt;/li&gt;
&lt;li&gt;The route is protected (only admins can reach it), but you might also validate that the &lt;code&gt;imageName&lt;/code&gt; indeed exists and is not used by active posts — see suggested improvements.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frontend — Admin UI and confirmation UX
&lt;/h2&gt;

&lt;p&gt;The admin UI template (&lt;code&gt;views/firebase-media.handlebars&lt;/code&gt;) displays images in a gallery, provides a small preview modal, and a delete form for each image. On submit, a JS handler intercepts the form to show a delete confirmation modal. Confirming submits the original form — cancel closes the modal.&lt;/p&gt;

&lt;p&gt;Key elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/admin/manage-media/delete"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"imageName"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-secondary"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our JS we intercept &lt;code&gt;submit&lt;/code&gt; to show a modal with the filename and (optionally) an image preview. When an admin confirms, the form is submitted. This prevents accidental deletion and improves UX.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing the flow locally
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start your server (make sure &lt;code&gt;service-account-key.json&lt;/code&gt; is configured):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;span class="c"&gt;# or&lt;/span&gt;
node app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Log in as an admin and upload an image via the upload form. The server should return a &lt;code&gt;publicUrl&lt;/code&gt; you can paste and verify.&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;/admin/manage-media&lt;/code&gt; and verify the image appears.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Delete&lt;/code&gt; and confirm: image should be removed and the admin redirected back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also test the upload with &lt;code&gt;curl&lt;/code&gt; (include the correct session cookie for authentication):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"firebase-image=@/path/to/img.jpg"&lt;/span&gt; http://localhost:8000/firebase-image-upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete using a POST form (again, this example requires the session cookie for admin):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"imageName=uploaded-file.jpg"&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"connect.sid=&amp;lt;SID&amp;gt;"&lt;/span&gt; http://localhost:8000/admin/manage-media/delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Security and Best practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Authentication/Authorization: The upload and management routes are protected with &lt;code&gt;islogin&lt;/code&gt; and &lt;code&gt;isAdmin&lt;/code&gt; middleware — keep this thorough.&lt;/li&gt;
&lt;li&gt;Public vs private: Check your application needs. If files should be private, use &lt;code&gt;file.getSignedUrl(...)&lt;/code&gt; instead of &lt;code&gt;file.makePublic()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Validate file names and prevent deletion of images used by posts. Introduce a DB check preventing delete if referenced.&lt;/li&gt;
&lt;li&gt;Limit file types and file sizes (done via Multer). Consider antivirus scanning if your project accepts user uploads from unknown sources.&lt;/li&gt;
&lt;li&gt;Use environment variables and secure storage for service account keys. Keep keys out of source control.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Suggested improvements and future work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use signed URLs for private access.&lt;/li&gt;
&lt;li&gt;Add pagination for listing images when a bucket grows large.&lt;/li&gt;
&lt;li&gt;Add an image preview in the delete confirmation modal (improves UX and reduces accidental deletion).&lt;/li&gt;
&lt;li&gt;Convert the delete action to AJAX to avoid full page reloads and make the admin page more responsive.&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;trash&lt;/code&gt; mechanism: soft-deletes or move images to a &lt;code&gt;trash/&lt;/code&gt; prefix and perform a permanent delete later.&lt;/li&gt;
&lt;li&gt;Protect soft-deleted images from being re-used and provide an audit trail for deletes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;Implementing files in Firebase Storage via the Admin SDK is straightforward and integrates well with Node/Express via Multer. The P_Blog implementation is production-ready for small projects, and with a few of the improvements suggested above you can scale safely and add more robust functionality (privacy, pagination, auditing).&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>cloudstorage</category>
      <category>express</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Upload Images to AWS S3 from Your Web App with Node.js and Multer</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Mon, 16 Sep 2024 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmadhav82/from-app-to-cloud-how-to-upload-images-to-aws-s3-from-your-web-app-with-nodejs-and-multer-27ji</link>
      <guid>https://dev.to/pmadhav82/from-app-to-cloud-how-to-upload-images-to-aws-s3-from-your-web-app-with-nodejs-and-multer-27ji</guid>
      <description>&lt;p&gt;I recently added another feature in my NodeJS app &lt;a href="https://p-blog.online/" rel="noopener noreferrer"&gt;P_Blog&lt;/a&gt; to enhance the user experience. Now, users can directly upload images to an AWS S3 bucket from the app and copy the image link to use within their blog posts. This is especially convenient because &lt;a href="https://p-blog.online/" rel="noopener noreferrer"&gt;P_Blog&lt;/a&gt; supports Markdown, making it easy for users to embed images by simply pasting the link.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwaxx0a8gcyxpqiffuzk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwaxx0a8gcyxpqiffuzk.gif" alt="Image description" width="426" height="240"&gt;&lt;/a&gt;            &lt;/p&gt;

&lt;p&gt;Before implementing this feature, users had to rely on third-party platforms to host their images, which added unnecessary steps to the process. By allowing direct uploads to S3, the image management process becomes seamless, reducing friction and making content creation faster and more enjoyable for users. Now, they can focus more on writing and less on managing image links or worrying about image hosting.&lt;/p&gt;

&lt;p&gt;In this blog post, I want to walk you through the process of uploading images to an AWS S3 bucket directly from your web app. I’ll explain how I implemented this feature using Node.js, Express.js, and Multer to handle file uploads, and how I integrated the AWS SDK to securely store images in the cloud. By the end of this guide, you’ll be able to seamlessly add image upload functionality to your own app, improving the user experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up AWS S3
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Login in to the &lt;a href="https://aws.amazon.com/console/" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;S3&lt;/code&gt; and  click on &lt;code&gt;Create bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Give the bucket a unique name and make sure to uncheck &lt;code&gt;Block all public access&lt;/code&gt; because we will make our bucket public so that images within the bucket become public&lt;/li&gt;
&lt;li&gt;Now, we need to add a &lt;code&gt;Bucket policy&lt;/code&gt; that allows anyone to view images located inside the bucket. For that go to the &lt;code&gt;Permissions&lt;/code&gt; tab and edit the bucket policy. You can use &lt;code&gt;Policy generator&lt;/code&gt; to get the bucket policy and copy and paste the policy
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wygpngwfbuey3wjqk93.png" alt="Image description" width="800" height="612"&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Id": "Policy1725365190218",
    "Statement": [
        {
            "Sid": "Stmt1725365183685",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Creating an IAM User and Policy for S3
&lt;/h3&gt;

&lt;p&gt;In this process, we’ll create an IAM user that will give programmatic access to AWS resources via an access key and a secret access key. These credentials allow our application to interact with AWS services, such as S3. We’ll also attach a policy to this user, specifically allowing them to put objects into the S3 bucket—meaning they can upload images. By restricting permissions in this way, we ensure that the IAM user has only the necessary access to upload files, enhancing the security of our app.&lt;/p&gt;
&lt;h4&gt;
  
  
  Creating Policy for S3
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;IAM Dashboard&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to the &lt;code&gt;Policies&lt;/code&gt; section and click on &lt;code&gt;Create policy&lt;/code&gt; and you will see &lt;code&gt;Policy editor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Select a service&lt;/code&gt; section choose &lt;code&gt;S3&lt;/code&gt; and search and check for &lt;code&gt;PutObject&lt;/code&gt; in &lt;code&gt;Actions allowed&lt;/code&gt; section&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Resources&lt;/code&gt; section choose &lt;code&gt;Specific&lt;/code&gt; and click on &lt;code&gt;Add ARNs&lt;/code&gt; Specify ARN box will open&lt;/li&gt;
&lt;li&gt;In the Speify ARN box enter your bucket name, Resource ARN check &lt;code&gt;Any object name&lt;/code&gt; for &lt;code&gt;Resource object name&lt;/code&gt; lastly click on &lt;code&gt;Add ARNs&lt;/code&gt; button
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdy3ri5to1p3wkmms3xpe.png" alt="Image description" width="800" height="447"&gt;
&lt;/li&gt;
&lt;li&gt;Now we are in &lt;code&gt;Review and create&lt;/code&gt; section. In this section give a meaningful name for this policy and click on &lt;code&gt;Create policy&lt;/code&gt; button&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Creating an IAM User
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;Users&lt;/code&gt; section of &lt;code&gt;IAM Dashboard&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Create user&lt;/code&gt; and give a meaningful name and click on &lt;code&gt;Next&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Set permissions&lt;/code&gt; section choose &lt;code&gt;Attach policies directly&lt;/code&gt; and search and choose for the policy that we have created above. Lastly, click on &lt;code&gt;Next&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Review and create&lt;/code&gt; section just need to click on &lt;code&gt;Create user&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;Go the the newly created user dashboard, click on &lt;code&gt;Create access key&lt;/code&gt; and you can choose &lt;code&gt;Other&lt;/code&gt; in &lt;code&gt;Access key best practices &amp;amp; alternatives&lt;/code&gt; section. Lastly, click on &lt;code&gt;Next&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Set description tag&lt;/code&gt; tag you may write description of this access key but it is optional and click on &lt;code&gt;Create access key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This is the final and important part, where we will get the &lt;code&gt;Access key&lt;/code&gt; and &lt;code&gt;Secret access key&lt;/code&gt;.  You will see both keys in this &lt;code&gt;Retrieve access key&lt;/code&gt; section. Make sure you have saved those keys somewhere safe.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Backend Setup
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Multer setup to handle file uploads
&lt;/h4&gt;

&lt;p&gt;First, we will set up a basic &lt;a href="https://www.npmjs.com/package/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; configuration for handling file uploads. This configuration is crucial for managing file zise limits and filtering which types of files are allowed to be uploaded. Install &lt;code&gt;multer&lt;/code&gt; by running &lt;code&gt;npm install multer&lt;/code&gt; command in your terminal.&lt;br&gt;
I have created a file named &lt;code&gt;s3ImageUpload.js&lt;/code&gt; inside the folder &lt;code&gt;utils&lt;/code&gt; and the following code goes in that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const multer = require("multer");

const s3Upload = multer({
  limits: {
    fileSize: 12000000,
  },

  fileFilter: (req, file, cb) =&amp;gt; {
    const allowedFileType = ["image/png", "image/jpg", "image/jpeg"];

    if (!allowedFileType.includes(file.mimetype)) {
      return cb(
        new Error("Please select a valid image file (PNG, JPG, JPEG)"),
        false
      );
    }

    cb(null, true);
  },
});

module.exports = s3Upload;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we have imported &lt;code&gt;multer&lt;/code&gt; set the file file size limit of 12MB. The &lt;code&gt;fileFilter&lt;/code&gt; function allows us to control what types of files can be uploaded. This ensures that users cannot upload excessively large files and files that are not included in &lt;code&gt;allowedFileType&lt;/code&gt;. In case of that &lt;code&gt;multer&lt;/code&gt; will throw an error and our custom error handler will catch and send this error to the frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const errorHandler = (error, req,res,next) =&amp;gt;{

const errorStatusCode = res.statusCode || 500;
let errorMessage = error.message || "Something went wrong!";
res.status(errorStatusCode).json({message: errorMessage, success: false});

}

module.exports = errorHandler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Integrating AWS SDK for Image Uploads
&lt;/h4&gt;

&lt;p&gt;In this section, we will defines an Express.js route for uploading images to an AWS S3 bucket. It uses the AWS SDK, along with several utility modules for authentication and file uploads. I have created a file named &lt;code&gt;s3UploadImageRoute.js&lt;/code&gt; inside &lt;code&gt;routes&lt;/code&gt; folder. First, let's import the required utility modules and setup aws-sdk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const s3UploadImageRoute = require("express").Router();
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const crypto = require("crypto");
const path = require("path");
const s3Upload = require("../utils/s3ImageUpload");
const {islogin} = require("../utils/loginHandeler");
const bucketURL = "https://pblog-images-bucket.s3.ap-southeast-2.amazonaws.com";

const client = new S3Client({
  region: "ap-southeast-2",
  credentials: {
    accessKeyId: process.env.AWS_S3_ACCESS_KEY,
    secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
  },
});



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above code, first we have set up an &lt;code&gt;Express Router&lt;/code&gt; for handling routes related to S3 image uploads. The &lt;code&gt;S3Client&lt;/code&gt; is used to communicate with the S3 service, while &lt;code&gt;PutObjectCommand&lt;/code&gt; represents the command to upload an object (in this case, an image) to the S3 bucket.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crypto&lt;/code&gt;: Used for generating unique filenames for the images by creating a random string (to avoid overwriting files with the same name).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;path&lt;/code&gt;: Used for extracting file extensions to properly name the uploaded image files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;s3Upload&lt;/code&gt;: Imports a pre-configured Multer instance for handling the file upload (defined earlier).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;islogin&lt;/code&gt;: A custom utility function that checks if the user is logged in before allowing access to the image upload route. This ensures that only authenticated users can upload images.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bucketURL&lt;/code&gt;: This constant stores the base URL of the S3 bucket where images will be uploaded. Once an image is successfully uploaded, you can append the image’s unique filename to this URL to create the full link for accessing the image.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client&lt;/code&gt;: an &lt;code&gt;S3Client&lt;/code&gt; instance to interact with the S3 bucket configured with &lt;code&gt;Region&lt;/code&gt;, &lt;code&gt;Credentials&lt;/code&gt; where we have used previously created IAM user's &lt;code&gt;accessKeyId&lt;/code&gt; and &lt;code&gt;secretAccessKey&lt;/code&gt; stored in environment variables.&lt;/p&gt;

&lt;p&gt;Now, let's define the &lt;code&gt;POST&lt;/code&gt; route for uploading an image to an &lt;code&gt;S3 bucket&lt;/code&gt; that processes the uploaded file, and send it to AWS S3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
s3UploadImageRoute.post("/", islogin, s3Upload.single("s3Image"), async (req, res) =&amp;gt; {
  if (!req.file) {
    return res.status(400).json({
      message: "No image selected",
      success: false,
    });
  }


const fileExt = path.extname(req.file.originalname);
  const fileName = `${crypto.randomBytes(16).toString("hex")}.${fileExt}`;

  const input = {
    Bucket: "pblog-images-bucket",
    Key: fileName,
    Body: req.file.buffer,
  };

  const command = new PutObjectCommand(input);
  try {
    const response = await client.send(command);
    if (response.$metadata.httpStatusCode !== 200) {
      throw new Error("Failed to upload image to s3 bucket");

    }

    res.json({ imageURL: `${bucketURL}/${fileName}`, success: true });
  } catch (er) {
    console.log(er);
    res.status(500).json({ message: er.message, success: false });
  }
});

module.exports = s3UploadImageRoute;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;s3Upload.single("s3Image")&lt;/code&gt; uses Multer to handle the file upload, expecting a single file with the field name &lt;code&gt;s3Image&lt;/code&gt;. If no file is provided, the server responds with a 400 Bad Request status and an error message stating, &lt;code&gt;No image selected&lt;/code&gt;. &lt;code&gt;fileExt&lt;/code&gt; extracts the file extension from the uploaded file (e.g., .jpg, .png) using &lt;code&gt;path.extname()&lt;/code&gt;. &lt;code&gt;fileName&lt;/code&gt; generates a unique filename by combining a random 16-byte hexadecimal string (via &lt;code&gt;crypto.randomBytes()&lt;/code&gt;) with the original file extension. This ensures that every file has a unique name, preventing conflicts if two users upload files with the same name. &lt;code&gt;command&lt;/code&gt; use &lt;code&gt;PutObjectCommand&lt;/code&gt; to create a command with the &lt;code&gt;input&lt;/code&gt; data. This command is responsible for telling AWS S3 to upload the file. &lt;code&gt;client.send(command)&lt;/code&gt; sends the upload command to AWS S3 which returns a &lt;code&gt;response&lt;/code&gt; object.&lt;br&gt;
If the upload is successful, the server responds with a JSON object containing the &lt;code&gt;URL&lt;/code&gt; of the uploaded image (constructed by appending the &lt;code&gt;fileName&lt;/code&gt; to the &lt;code&gt;bucketURL&lt;/code&gt;) and a success flag (&lt;code&gt;success: true&lt;/code&gt;). &lt;br&gt;
Lastly, we need to configure our &lt;code&gt;s3UploadImageRoute&lt;/code&gt; in &lt;code&gt;app.js&lt;/code&gt; file as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const s3UploadImageRoute = require("./routes/s3UploadImageRoute");
const errorHandler = require("./utils/errorHandler");
app.use(express.json());

app.use("/aws-s3-upload-image", s3UploadImageRoute);
app.use("/", rootRoute);

app.use("/*", (req,res)=&amp;gt;{
  res.render("404");
})

app.use(errorHandler);

app.listen(PORT, () =&amp;gt; console.log(`Server is running on ${PORT} `));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frontend Setup for Image Uploads
&lt;/h3&gt;

&lt;p&gt;In this section, we will create a UI where user can choose image to upload. Once the upload is successful, user will get the image link in markdown format (&lt;code&gt;[alt text](image_url)&lt;/code&gt;) and button to copy the URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="image-upload-toolbar"&amp;gt;
    &amp;lt;label title="Upload image" class="upload-image"&amp;gt;&amp;lt;svg class="icons"
            style="width:20px;height:20px; margin:3px 0 0 2px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"&amp;gt;
            &amp;lt;path fill="currentColor"
                d="M20 5H4v14l9.292-9.294a1 1 0 011.414 0L20 15.01V5zM2 3.993A1 1 0 012.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 01-.992.993H2.992A.993.993 0 012 20.007V3.993zM8 11a2 2 0 110-4 2 2 0 010 4z"&amp;gt;
            &amp;lt;/path&amp;gt;
        &amp;lt;/svg&amp;gt;
        &amp;lt;input type="file" id="image-upload-field" accept="image/*"&amp;gt;&amp;lt;/label&amp;gt;
    &amp;lt;div style="width: 90%; display:flex; align-items: center" aria-live="polite"&amp;gt;
        &amp;lt;input data-testid="markdown-copy-link" type="text" style="width:80%; max-width:360px;" id="image-url"
            readonly="" placeholder="Select image to get image url" value=""&amp;gt;
        &amp;lt;button type="button" id="copy-btn" title="Copy" class="btn btn-outline" style="display: none;"&amp;gt;
            &amp;lt;svg class="icons" style="width:20px;height:20px; margin-right:5px;" id="copy-icon" viewBox="0 0 24 24"
                xmlns="http://www.w3.org/2000/svg" aria-hidden="true"&amp;gt;
                &amp;lt;path fill="currentColor"
                    d="M7 6V3a1 1 0 011-1h12a1 1 0 011 1v14a1 1 0 01-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 013 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2zm-2 5v2h6v-2H7zm0 4v2h6v-2H7z"&amp;gt;
                &amp;lt;/path&amp;gt;
            &amp;lt;/svg&amp;gt;
        &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above code, we have&lt;code&gt;&amp;lt;input type="file"&amp;gt;&lt;/code&gt; with &lt;code&gt;SVG&lt;/code&gt; image icon that allows users to upload an image,  &lt;code&gt;input&lt;/code&gt; field to display the URL of the uploaded image or error message if there is any and a &lt;code&gt;button&lt;/code&gt; with copy icon for coping the image URL to the clipboard once the image is uploaded.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeheijbac20axomi52yc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeheijbac20axomi52yc.png" alt="Image description" width="569" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we will write some javascript code for uploading the selected image to the backend, displaying the image URL in the text input field, allowing users to copy the URL to the clipboard and handling errors if there is any.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    const copyButton = document.getElementById("copy-btn");
    const url = document.getElementById("image-url");
    const imageInput = document.getElementById("image-upload-field");
    const uploadImage = async (URL, file) =&amp;gt; {
        const formData = new FormData();
        formData.append("s3Image", file);
        const res = await fetch(URL, {
            method: "post",
            body: formData
        })
        const result = await res.json();
        return result;
    }
    imageInput.addEventListener("change", async (e) =&amp;gt; {
        copyButton.style.display = "none";
        const selectedImage = e.target.files[0];
        if (!selectedImage) {
            url.value = "No image selected";
            return
        }
        url.value = "Uploading...";
        try {
            const result = await uploadImage("/aws-s3-upload-image", selectedImage);
            if (!result?.success) {
                throw new Error(result?.message || "Failed to upload")
            }
            url.value = `![Image description](${result?.imageURL})`
            copyButton.style.display = "block";
        } catch (error) {
            url.value = error.message
        }
    })
    copyButton.addEventListener("click", async () =&amp;gt; {
        const urlValue = url?.value;
        try {
            await navigator.clipboard.writeText(urlValue);
            url.select();
        } catch (er) {
            console.log(er.message)
        }
    })
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Adding image upload functionality to a web app is a powerful feature that enhances user experience and allows for richer content creation. By integrating AWS S3 with Node.js, Multer, and a user-friendly front end, I've streamlined the process of uploading images, generating URLs, and embedding them into blog posts. This approach not only simplifies image handling but also makes your web app more versatile and user-friendly. I hope this guide helps you implement similar functionality in your projects. Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find me on &lt;a href="https://www.linkedin.com/in/pmadhav/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>How to upload image using multer and Express.js</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Sat, 13 Jul 2024 23:02:16 +0000</pubDate>
      <link>https://dev.to/pmadhav82/how-to-upload-image-using-multer-and-expressjs-5a57</link>
      <guid>https://dev.to/pmadhav82/how-to-upload-image-using-multer-and-expressjs-5a57</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--VkMbj7ZW--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_420%2Cq_auto%2Cw_1000%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdpyzj1o6s790s2tplpzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--VkMbj7ZW--%2Fc_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_420%2Cq_auto%2Cw_1000%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdpyzj1o6s790s2tplpzp.png" alt="blog-pic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this blog post we will learn to upload image using &lt;code&gt;multer&lt;/code&gt;. &lt;code&gt;multer&lt;/code&gt; is a middleware of NodeJS to handle file upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalation
&lt;/h2&gt;



&lt;p&gt;&lt;code&gt;npm install multer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="form-wrapper"&amp;gt;
&amp;lt;h3&amp;gt;Upload Profile Picture&amp;lt;/h3&amp;gt;

&amp;lt;div class="main"&amp;gt;
&amp;lt;form action="/upload" method="POST" enctype="multipart/form-data"&amp;gt;

&amp;lt;input type="file" accept="image/*" id="imageInput"  name="userProfile" /&amp;gt;
&amp;lt;button type="submit" id="submit" class="btn btn-primary"&amp;gt;Upload&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;div class="image-preview"&amp;gt;
&amp;lt;img id="imageOutPut"  /&amp;gt;
&amp;lt;p id="imageName"&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upload preview
&lt;/h3&gt;

&lt;p&gt;Following code will enable the preview of choosen image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imageInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imageOutPut&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imageName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;imageInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;imageOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;imageOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;b&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;/b&amp;gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;imageOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages2.imgbox.com%2F07%2Ff7%2Fah9CzDGQ_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages2.imgbox.com%2F07%2Ff7%2Fah9CzDGQ_o.png" alt="preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;We have to setup basic  &lt;code&gt;multer&lt;/code&gt; configuration which includes where do we want to save uploaded image, what name do we want to set up. We can filter the file as well and we can set the file size limit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//fineName: fileUpload.js&lt;/span&gt;

 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;800000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diskStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upload/images&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;someUniqueName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;fileFilter&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedFileType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allowedFileType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])){&lt;/span&gt;
            &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explationation of above code
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;multer&lt;/code&gt; take 3 objects &lt;code&gt;limits&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt; and &lt;code&gt;fileFilter&lt;/code&gt; as a parameter. &lt;code&gt;storage&lt;/code&gt;have 2 parameter which are &lt;code&gt;diskStorage&lt;/code&gt; and give full control on storing file to disk and &lt;code&gt;filename&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now, we can export this as a middleware and we can use to upload image as follow:&lt;/p&gt;

&lt;h2&gt;
  
  
  Route setup to handle uploaded file
&lt;/h2&gt;

&lt;p&gt;As our frontend form will hit the endpoint &lt;code&gt;/upload&lt;/code&gt; we have to setup our route to handle this endpoint which has image in it plus we need to &lt;code&gt;require&lt;/code&gt; the middleware that we created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//fileName: route.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fileUpload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/upload/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userProfile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;   

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`images/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;
        &lt;span class="nx"&gt;filePath&lt;/span&gt;
       &lt;span class="p"&gt;})&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;upload.single()&lt;/code&gt; will handle single image came from request and saved it to specified folder. The image is saved to &lt;code&gt;upload/images&lt;/code&gt; folder.&lt;br&gt;
Now, we have to make &lt;code&gt;upload&lt;/code&gt; folder static so that images saved there can be served to the frontend and file path will be  &lt;code&gt;images/${req.file.filename}&lt;/code&gt; we can save this &lt;code&gt;imageURL&lt;/code&gt; to the database as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//fileName:app.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./route&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;


&lt;span class="c1"&gt;//router connection&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;



&lt;span class="c1"&gt;//uses of public folder&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;//app.use(express.static(path.join(__dirname,"/upload")))&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/upload`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="c1"&gt;//init handlebars&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handlebars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;handlebars&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view engine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handlebars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server is running on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you can upload image to the server using &lt;code&gt;Expressjs&lt;/code&gt; with the help of &lt;code&gt;multer&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thanks for reading.
&lt;/h3&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to build a Todo app with Typescript</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Tue, 09 Jul 2024 12:47:20 +0000</pubDate>
      <link>https://dev.to/pmadhav82/how-to-build-a-todo-app-with-typescript-3o3d</link>
      <guid>https://dev.to/pmadhav82/how-to-build-a-todo-app-with-typescript-3o3d</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yfeJHQDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blogimagesbocket.s3.ap-southeast-2.amazonaws.com/Screenshot%2B2024-07-02%2B201900.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yfeJHQDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blogimagesbocket.s3.ap-southeast-2.amazonaws.com/Screenshot%2B2024-07-02%2B201900.png" alt="cover-picture" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this blog post, we will build a to-do app using typescript. The app allows users to add, delete, and update tasks, and users can mark completed tasks or unmark them. We will follow the Model, View, Controller (MVC) design pattern. Here is the link for the app &lt;a href="https://6683cca0885c60255f7d5361--beautiful-madeleine-23be54.netlify.app/" rel="noopener noreferrer"&gt;Todo app with typescript&lt;/a&gt;. We will use &lt;code&gt;vite&lt;/code&gt; to quickly set up the typescript project. Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup typescript project using &lt;code&gt;vite&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Run the following command in the terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you to enter the project name. we can say &lt;code&gt;Typescript-todo-app&lt;/code&gt;. We will be using just typescript in this project so, in &lt;code&gt;Select a framework&lt;/code&gt; prompt we need to choose &lt;code&gt;Vanilla&lt;/code&gt;. In the next prompt, we need to choose &lt;code&gt;TypeScript&lt;/code&gt;. Then, Vite will setup the project for us. To run the project run the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd Typescript-todo-app
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the typescript-configured project. We can delete all unwanted files. I have used &lt;code&gt;bootstrap&lt;/code&gt; and &lt;code&gt;uuid&lt;/code&gt; to generate a unique &lt;code&gt;id&lt;/code&gt; for each task item. So, let's install these packages and import them into the &lt;code&gt;main.ts&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install bootstrap uuid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "bootstrap/dist/css/bootstrap.min.css";
import { v4 as uuid } from "uuid";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our project files and folders tree looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Typescript-todo-app
├── public
├── src
│   ├── controller
│   │   └── TaskListController.ts
│   ├── model
│   │   ├── TaskItem.ts
│   │   └── TaskList.ts
│   ├── view
│   │   └── TaskListView.ts
│   └── main.ts
├── index.html
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating model for TaskItem and TaskList
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TaskItem
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;model&lt;/code&gt; folder create a file &lt;code&gt;TaskItem.ts&lt;/code&gt;. In this file, we will define a model for a single task. A single task item will have the following properties&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique &lt;code&gt;id&lt;/code&gt; for that we will be using &lt;code&gt;uuid&lt;/code&gt; library&lt;/li&gt;
&lt;li&gt; &lt;code&gt;task&lt;/code&gt; actual description of the task itself&lt;/li&gt;
&lt;li&gt; &lt;code&gt;completed&lt;/code&gt; a boolean value indicating whether the task is completed or not, which value will be &lt;code&gt;false&lt;/code&gt; by default and can be changed latter
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface SingleTask {
  id: string;
  task: string;
  completed: boolean;
}

export default class TaskItem implements SingleTask {
  constructor(
    private _id: string = "",
    private _task: string = "",
    private _completed: boolean = false
  ) {}
  get id(): string {
    return this._id;
  }

  set id(id: string) {
    this._id = id;
  }

  get task(): string {
    return this._task;
  }
  set task(task: string) {
    this._task = task;
  }

  get completed(): boolean {
    return this._completed;
  }
  set completed(completed: boolean) {
    this._completed = completed;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, first, we have defined an &lt;code&gt;interface&lt;/code&gt; for a &lt;code&gt;SingleTask&lt;/code&gt;. The interface provides the syntax for classes to follow. Any classes implementing a particular interface have to follow the structure provided by that interface.&lt;br&gt;
Then, we have defined a &lt;code&gt;class&lt;/code&gt; named &lt;code&gt;TaskItem&lt;/code&gt; that implements the &lt;code&gt;SingleTask&lt;/code&gt; interface. This means that the &lt;code&gt;TaskItem&lt;/code&gt; class has to have all properties defined in the interface.&lt;br&gt;
The constructor allows us to set the value for &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;task&lt;/code&gt;, and &lt;code&gt;completed&lt;/code&gt; when creating a new &lt;code&gt;TaskItem&lt;/code&gt; instance. Plus, we've also defined getter and setter methods for each property. Getters allow us to retrieve the current value of the property, while setters ensure that any changes to the property are handled appropriately. We will see these in action while editing tasks and toggling task status.&lt;/p&gt;
&lt;h3&gt;
  
  
  TaskList
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;TaskList.ts&lt;/code&gt; will be responsible for managing the collection of TaskItem which includes retrieving tasks, adding, deleting, updating tasks, saving collection of tasks in &lt;code&gt;localStorage&lt;/code&gt;, loading tasks from &lt;code&gt;localStorage&lt;/code&gt;, and filtering tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import TaskItem from "./TaskItem";

interface AllTask {
  tasks: TaskItem[];
  load(): void;
  save(): void;
  clearTask(): void;
  addTask(taskObj: TaskItem): void;
  removeTask(id: string): void;
  editTask(id: string, updatedTaskText: string): void;
  toggleTaskChange(id: string): void;
  getTaskToComplete(): TaskItem[];
  getCompletedTask(): TaskItem[];
}

export default class TaskList implements AllTask {
  private _tasks: TaskItem[] = [];

  get tasks(): TaskItem[] {
    return this._tasks;
  }

  load(): void {
    const storedTasks: string | null = localStorage.getItem("myTodo");
    if (!storedTasks) return;

    const parsedTaskList: {
      _id: string;
      _task: string;
      _completed: boolean;
    }[] = JSON.parse(storedTasks);
    parsedTaskList.forEach((taskObj) =&amp;gt; {
      const newTaskList = new TaskItem(
        taskObj._id,
        taskObj._task,
        taskObj._completed
      );

      this.addTask(newTaskList);
    });
  }

  save(): void {
    localStorage.setItem("myTodo", JSON.stringify(this._tasks));
  }

  clearTask(): void {
    this._tasks = [];
    localStorage.removeItem("myTodo");
  }

  addTask(taskObj: TaskItem): void {
    this._tasks.push(taskObj);
    this.save();
  }

  removeTask(id: string): void {
    this._tasks = this._tasks.filter((task) =&amp;gt; task.id !== id);
    this.save();
  }

  editTask(id: string, updatedTaskText: string): void {
    if (updatedTaskText.trim() === "") return;

    const taskToUpdate = this._tasks.find((task) =&amp;gt; task.id === id);
    if (!taskToUpdate) return;
    taskToUpdate.task = updatedTaskText;
    this.save();
  }
  toggleTaskChange(id: string): void {
    const taskToUpdateChange = this._tasks.find((task) =&amp;gt; task.id === id);
    if (!taskToUpdateChange) return;
    taskToUpdateChange.completed = !taskToUpdateChange.completed;
    this.save();
  }
  getCompletedTask(): TaskItem[] {
    const completedTask = this._tasks.filter((task) =&amp;gt; task.completed);
    return completedTask;
  }

  getTaskToComplete(): TaskItem[] {
    const taskToComplete = this._tasks.filter((task) =&amp;gt; !task.completed);
    return taskToComplete;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, first, we have imported the &lt;code&gt;TaskItem&lt;/code&gt; class and then defined an interface &lt;code&gt;AllTask&lt;/code&gt;. This interface outlines the methods a &lt;code&gt;TaskList&lt;/code&gt; class should implement to manage tasks.&lt;br&gt;
 Inside the &lt;code&gt;TaskList&lt;/code&gt; class, we have private property &lt;code&gt;_tasks&lt;/code&gt; which holds an array of &lt;code&gt;TaskItem&lt;/code&gt;. &lt;br&gt;
The &lt;code&gt;load&lt;/code&gt; method attempts to retrieve a serialized list of tasks from local storage with the key "&lt;code&gt;myTodo&lt;/code&gt;".&lt;br&gt;
If data is found, it parses the JSON string back into an array of objects with properties matching TaskItem's structure. It then iterates through the parsed data and creates new &lt;code&gt;TaskItem&lt;/code&gt; objects, adding them to the internal &lt;code&gt;_tasks&lt;/code&gt; array using the &lt;code&gt;addTask&lt;/code&gt; method.&lt;br&gt;
The &lt;code&gt;save&lt;/code&gt; method serializes the current &lt;code&gt;_tasks&lt;/code&gt; array into a JSON string and stores it in &lt;code&gt;localStorage&lt;/code&gt; with the key "&lt;code&gt;myTodo&lt;/code&gt;".&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clearTask&lt;/code&gt;: This method removes all tasks from the internal list and clears the associated data from localStorage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;addTask&lt;/code&gt;: This method adds a new &lt;code&gt;TaskItem&lt;/code&gt; object to the beginning of the internal &lt;code&gt;_tasks&lt;/code&gt; array and calls the save method to persist the change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;removeTask&lt;/code&gt;: This method takes an &lt;code&gt;id&lt;/code&gt; as input and filters the &lt;code&gt;_tasks&lt;/code&gt; array, keeping only tasks where the id property doesn't match the provided id. It then calls save to persist the change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;editTask&lt;/code&gt;: This method takes an &lt;code&gt;id&lt;/code&gt; and a &lt;code&gt;updatedTaskText&lt;/code&gt; as input. It first checks if the &lt;code&gt;updatedTaskText&lt;/code&gt; is empty (trimmed) and returns if so.&lt;br&gt;
It then finds the task with the matching &lt;code&gt;id&lt;/code&gt; using the find method. If no task is found, it returns. Finally, it updates the &lt;code&gt;task&lt;/code&gt; property of the found &lt;code&gt;task&lt;/code&gt; object with the provided &lt;code&gt;updatedTaskText&lt;/code&gt; and calls &lt;code&gt;save&lt;/code&gt; to persist the change.&lt;br&gt;
&lt;code&gt;toggleTaskChange&lt;/code&gt;: This method takes an id as input. It finds the task with the matching &lt;code&gt;id&lt;/code&gt; and flips the value of its completed property (i.e., marks it as completed if pending or vice versa). Finally, it calls &lt;code&gt;save&lt;/code&gt; to persist the change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getCompletedTask&lt;/code&gt;: This method returns an array containing only the tasks where the completed property is set to true (completed tasks).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getTaskToComplete&lt;/code&gt;: This method returns an array containing only the tasks where the completed property is set to false (pending tasks).&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a controller for TaskList
&lt;/h2&gt;

&lt;p&gt;Now, let's create a file named &lt;code&gt;TaskListController.ts&lt;/code&gt; inside the &lt;code&gt;controller&lt;/code&gt; folder. This class acts as a bridge between &lt;code&gt;view&lt;/code&gt; and &lt;code&gt;modal&lt;/code&gt; as it interacts with &lt;code&gt;model&lt;/code&gt; based on user interaction through the &lt;code&gt;view&lt;/code&gt; component which we will create later.&lt;/p&gt;

&lt;p&gt;First of all, let's import &lt;code&gt;TaskItem&lt;/code&gt; and &lt;code&gt;TaskList&lt;/code&gt; classes from the &lt;code&gt;model&lt;/code&gt; and define an interface for &lt;code&gt;TaskListController&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import TaskItem from "../model/TaskItem";
import TaskList from "../model/TaskList";

interface Controller {
  getTaskList(): TaskItem[];
  addTask(newTask: TaskItem): void;
  deleteTask(taskId: string): void;
  editTask(taskId: string, updatedTaskText: string): void;
  loadTask(): void;
  clearTask(): void;
  saveTask(): void;
  toggleTaskChange(taskId: string): void;
  getPendingTask(): TaskItem[];
  getCompletedTask(): TaskItem[];
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create a class &lt;code&gt;TaskListController&lt;/code&gt; that implements the &lt;code&gt;Controller&lt;/code&gt; interface. This ensures the class provides all the functionalities defined in the interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
export default class TaskListController implements Controller {
  private _taskList: TaskList = new TaskList();

  constructor() {

    this.loadTask();
  }

  getTaskList(): TaskItem[] {
    return this._taskList.tasks;
  }

  addTask(newTask: TaskItem): void {
    this._taskList.addTask(newTask);
  }

  deleteTask(taskId: string): void {
    this._taskList.removeTask(taskId);
  }

  editTask(taskId: string, updatedTaskText: string): void {
    this._taskList.editTask(taskId, updatedTaskText);
  }

  getCompletedTask(): TaskItem[] {
    const completedTask = this._taskList.getCompletedTask();
    return completedTask;
  }

  getPendingTask(): TaskItem[] {
    const pendingTask = this._taskList.getTaskToComplete();
    return pendingTask;
  }

  clearTask(): void {
    this._taskList.clearTask();
  }
  loadTask(): void {
    this._taskList.load();
  }
  saveTask(): void {
    this._taskList.save();
  }

  toggleTaskChange(taskId: string): void {
    this._taskList.toggleTaskChange(taskId);
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the class, a private property &lt;code&gt;_taskList&lt;/code&gt; is declared and initialized with a new instance of the &lt;code&gt;TaskList&lt;/code&gt; class. This &lt;code&gt;_taskList&lt;/code&gt; object handles the actual storage and manipulation of tasks. &lt;br&gt;
The constructor gets called whenever a new &lt;code&gt;TaskListController&lt;/code&gt; object is created.&lt;br&gt;
Inside the constructor, it calls the &lt;code&gt;loadTask&lt;/code&gt; method, to retrieve any previously saved tasks from persistent storage and populate the internal &lt;code&gt;_taskList&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;The class defines several methods each of these methods simply calls the corresponding method on the internal &lt;code&gt;_taskList&lt;/code&gt; object. For instance, &lt;code&gt;getTaskList&lt;/code&gt; calls t&lt;code&gt;his._taskList&lt;/code&gt;.tasks to retrieve the task list, &lt;code&gt;addTask&lt;/code&gt; calls &lt;code&gt;this._taskList&lt;/code&gt;.&lt;br&gt;
By delegating the work to the &lt;code&gt;_taskList&lt;/code&gt; object, the TaskListController acts as a facade, providing a convenient interface for interacting with the task management functionalities.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a view for TaskList
&lt;/h2&gt;

&lt;p&gt;Let's create a file named &lt;code&gt;TaskListView.ts&lt;/code&gt; inside a folder &lt;code&gt;view&lt;/code&gt;.  We will create a class &lt;code&gt;HTMLTaskListView&lt;/code&gt;, which is responsible for rendering the tasks on the web page and managing user interactions. First, let's create an interface for this class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import TaskItem from "../model/TaskItem";
import TaskListController from "../controller/TaskListController";

interface DOMList {
  clear(): void;
  render(allTask: TaskItem[]): void;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above interface &lt;code&gt;DOMList&lt;/code&gt;, we only have two methods. These two methods will be public and are responsible for rendering tasks and clearing all tasks.&lt;br&gt;
Now let's look at &lt;code&gt;HTMLTaskListView&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
export default class HTMLTaskListView implements DOMList {
  private ul: HTMLUListElement;
  private taskListController: TaskListController;
  constructor(taskListController: TaskListController) {
    this.ul = document.getElementById("taskList") as HTMLUListElement;
    this.taskListController = taskListController;

    if (!this.ul)
      throw new Error("Could not find html ul element in html document.");
  }

  clear(): void {
    this.ul.innerHTML = "";
  }

  private createTaskListElement(task: TaskItem): HTMLLIElement {
    const li = document.createElement("li") as HTMLLIElement;
    li.className = "list-group-item d-flex gap-3 align-items-center";
    li.dataset.taskId = task.id;

    const checkBox = this.createCheckBox(task);
    const label = this.createLabel(task);
    const editTaskInput = this.createEditTaskInput();
    const [saveButton, editButton] = this.createEditAndSaveButton(
      editTaskInput,
      label,
      task
    );
    const deleteButton = this.createDeleteButton(task);

    li.append(
      checkBox,
      editTaskInput,
      label,
      editButton,
      saveButton,
      deleteButton
    );
    return li;
  }

  private createCheckBox(task: TaskItem): HTMLInputElement {
    const checkBox = document.createElement("input") as HTMLInputElement;
    checkBox.type = "checkbox";
    checkBox.checked = task.completed;
    checkBox.addEventListener("change", () =&amp;gt; {
      this.taskListController.toggleTaskChange(task.id);
    });
    return checkBox;
  }

  private createEditTaskInput(): HTMLInputElement {
    /// input field to edit task
    const editTaskInput = document.createElement("input") as HTMLInputElement;
    editTaskInput.hidden = true;
    editTaskInput.type = "text";
    editTaskInput.className = "form-control";
    return editTaskInput;
  }

  private createLabel(task: TaskItem): HTMLLabelElement {
    const label = document.createElement("label") as HTMLLabelElement;
    label.htmlFor = task.id;
    label.textContent = task.task;
    return label;
  }

  private createEditAndSaveButton(
    editTaskInput: HTMLInputElement,
    label: HTMLLabelElement,
    task: TaskItem
  ): HTMLButtonElement[] {
    const saveButton = document.createElement("button") as HTMLButtonElement;
    saveButton.hidden = true;
    saveButton.className = "btn btn-warning btn-sm";
    saveButton.textContent = "Save";

    const editButton = document.createElement("button") as HTMLButtonElement;
    editButton.className = "btn btn-success btn-sm";
    editButton.textContent = "Edit";

    saveButton.addEventListener("click", () =&amp;gt; {
      const updatedTaskText = editTaskInput.value;
      task.task = updatedTaskText;
      this.taskListController.editTask(task.id, updatedTaskText);
      saveButton.hidden = true;
      editButton.hidden = false;
      editTaskInput.hidden = true;
      this.render(this.taskListController.getTaskList());
    });

    editButton.addEventListener("click", () =&amp;gt; {
      saveButton.hidden = false;
      editTaskInput.hidden = false;
      editTaskInput.value = task.task;
      label.innerText = "";
      editButton.hidden = true;
    });

    return [saveButton, editButton];
  }

  private createDeleteButton(task: TaskItem): HTMLButtonElement {
    const deleteButton = document.createElement("button") as HTMLButtonElement;
    deleteButton.className = "btn btn-primary btn-sm";
    deleteButton.textContent = "Delete";

    deleteButton.addEventListener("click", () =&amp;gt; {
      this.taskListController.deleteTask(task.id);
      this.render(this.taskListController.getTaskList());
    });
    return deleteButton;
  }

  render(allTask: TaskItem[]): void {
    this.clear();

    allTask.forEach((task) =&amp;gt; {
      const li = this.createTaskListElement(task);
      this.ul.append(li);
    });
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Constructor and Initialization
&lt;/h3&gt;

&lt;p&gt;The constructor initializes the HTMLTaskListView class. It sets up essential properties and ensures that the necessary DOM elements are available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(taskListController: TaskListController) {
  this.ul = document.getElementById("taskList") as HTMLUListElement;
  this.taskListController = taskListController;

  if (!this.ul)
    throw new Error("Could not find html ul element in html document.");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The properties &lt;code&gt;taskListController&lt;/code&gt; is an instance of &lt;code&gt;TaskListController&lt;/code&gt; that manager the tasks and &lt;code&gt;ul&lt;/code&gt; is a reference to the HTML unordered list element where tasks will be displayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clearing the TaskList
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;clear&lt;/code&gt; method removes all child elements from the ul element, effectively clearing the task list displayed on the webpage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clear(): void {
  this.ul.innerHTML = "";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating Task List Elements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createTaskListElement(task: TaskItem): HTMLLIElement&lt;/code&gt; creates an individual task item element (li) and appends various components such as checkboxes, labels, edit inputs, and buttons and we have methods for each of these components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createEditTaskInput(): HTMLInputElement&lt;/code&gt; generates an input field used for editing the task description, initially hides the input field, and will show when the user clicks on &lt;code&gt;Edit&lt;/code&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createLabel(task: TaskItem): HTMLLabelElement&lt;/code&gt; creates a label element to display the task description.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createEditAndSaveButton(editTaskInput: HTMLInputElement, label: HTMLLabelElement, task: TaskItem): HTMLButtonElement[]&lt;/code&gt; generates buttons for editing and saving task descriptions and manages their visibility and actions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;createDeleteButton(task: TaskItem): HTMLButtonElement&lt;/code&gt; creates a delete button and when clicked removes tasks from the list.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rendering the Task List
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;render&lt;/code&gt; method takes an array of &lt;code&gt;TaskItem&lt;/code&gt; representing all tasks to be displayed, clears the current task list, and populates it with the provided tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating an add task form
&lt;/h2&gt;

&lt;p&gt;So far, we have created a Model, View, and Controller for our Todo app. Now, it's time to look into &lt;code&gt;index.html&lt;/code&gt; page. Where we will create a form where users can write a task and add it to our to-do app plus we will create buttons to show completed tasks, tasks to complete, and clear all tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;div class="container" style="max-width: 800px;"&amp;gt;

&amp;lt;h2&amp;gt;Todo list app with TypeScript&amp;lt;/h2&amp;gt;
&amp;lt;form class="m-3" id="todo-form"&amp;gt;
  &amp;lt;div class="form-group p-2 d-flex"&amp;gt;
    &amp;lt;input name="new-todo" class="form-control" type="text" id="new-todo" placeholder="Add new todo"/&amp;gt;
    &amp;lt;button type="submit" class="btn btn-primary mx-2"&amp;gt;Add&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;


&amp;lt;section  style="max-width: 600px;"&amp;gt;


  &amp;lt;div class="btn-group my-2" role="group" aria-label="Basic mixed styles example"&amp;gt;
    &amp;lt;button type="button" id="all-task" class="btn btn-danger"&amp;gt;All Tasks&amp;lt;/button&amp;gt;
    &amp;lt;button type="button" id="completed-task" class="btn btn-warning"&amp;gt;Completed Task&amp;lt;/button&amp;gt;
    &amp;lt;button type="button" id="task-to-complete" class="btn btn-success"&amp;gt;Task To Complete&amp;lt;/button&amp;gt;
    &amp;lt;buttonc type="button" id="clear-btn" class="btn btn-secondary"&amp;gt;Clear All&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;

&amp;lt;ul id="taskList" class="list-group"&amp;gt;

&amp;lt;/ul&amp;gt;

&amp;lt;/section&amp;gt;

    &amp;lt;/div&amp;gt;

  &amp;lt;/body&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2D_P86xI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blogimagesbocket.s3.ap-southeast-2.amazonaws.com/Screenshot%2B2024-07-09%2B211216.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2D_P86xI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blogimagesbocket.s3.ap-southeast-2.amazonaws.com/Screenshot%2B2024-07-09%2B211216.png" alt="todo-app" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Model, View, Controller
&lt;/h2&gt;

&lt;p&gt;In, MVC architecture, View captures user interaction such as new task is added, the delete button is clicked, and sends them to the Controller. Then, the Controller acts as an intermediary between the Model and the View. It updates the Model such as adding a new task to the task list, deleting a task from the list, or updating a single task. We have already created a Model, View, and Controller. Now let's connect them together.&lt;br&gt;
In &lt;code&gt;main.ts&lt;/code&gt; file let's import the required classes and initialize Controller and View.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importing and Initializing the Controller and View
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import TaskItem from "./model/TaskItem";
import TaskListController from "./controller/TaskListController";
import HTMLTaskListView from "./view/TaskListView";

const taskListController = new TaskListController();
const taskListView = new HTMLTaskListView(taskListController);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;taskListController&lt;/code&gt;: An instance of &lt;code&gt;TaskListController&lt;/code&gt; that manages the task data and business logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;taskListView&lt;/code&gt;: An instance of &lt;code&gt;HTMLTaskListView&lt;/code&gt; that takes the taskListController as an argument. This view will handle rendering tasks on the webpage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Accessing DOM Elements
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const todoForm = document.getElementById("todo-form") as HTMLFormElement;
const clearBtn = document.getElementById("clear-btn") as HTMLButtonElement;
const showCompletedTask = document.getElementById("completed-task") as HTMLButtonElement;
const showTaskToComplete = document.getElementById("task-to-complete") as HTMLButtonElement;
const showAllTask = document.getElementById("all-task") as HTMLButtonElement;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;todoForm&lt;/code&gt;: The form where new tasks are added.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clearBtn&lt;/code&gt;: The button to clear all tasks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;showCompletedTask&lt;/code&gt;: The button to filter and display completed tasks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;showTaskToComplete&lt;/code&gt;: The button to filter and display pending tasks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;showAllTask&lt;/code&gt;: The button to display all tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initializing the Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initApp = () =&amp;gt; {
  const allTask = taskListController.getTaskList();
  taskListView.render(allTask);
};

initApp()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;initApp&lt;/code&gt;: A function that initializes the application by fetching all tasks from the controller and rendering them using the view. We have called the &lt;code&gt;initApp&lt;/code&gt; function right away to ensure that the task list is rendered when the application loads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding a New Task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (todoForm) {
  todoForm.addEventListener("submit", (e) =&amp;gt; {
    e.preventDefault();
    const formData = new FormData(todoForm);
    const todoValue = formData.get("new-todo") as string;
    if (todoValue === null || todoValue?.toString().trim() === "") return;
    const newTask = new TaskItem(uuid(), todoValue.trim());

    taskListController.addTask(newTask);

    initApp();

    todoForm.reset();
  });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On submission, &lt;code&gt;todoForm&lt;/code&gt; will do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prevent the default form submission behavior.&lt;/li&gt;
&lt;li&gt;Extract the new task description from the form.&lt;/li&gt;
&lt;li&gt;Validate the task description (non-empty and non-null).&lt;/li&gt;
&lt;li&gt;Create a new TaskItem with a unique ID and the trimmed task description.&lt;/li&gt;
&lt;li&gt;Add the new task to the controller.&lt;/li&gt;
&lt;li&gt;Reinitialize the application to render the updated task list.&lt;/li&gt;
&lt;li&gt;Reset the form.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Clearing All Tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clearBtn.addEventListener("click", () =&amp;gt; {
  taskListController.clearTask();
  taskListView.clear();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Showing Completed Tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;showCompletedTask.addEventListener("click", () =&amp;gt; {
  const completedTask = taskListController.getCompletedTask();
  taskListView.render(completedTask);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Showing Tasks to Complete
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;showTaskToComplete.addEventListener("click", () =&amp;gt; {
  const taskToComplete = taskListController.getPendingTask();
  taskListView.render(taskToComplete);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In Conclusion, our todo application built using the MVC architecture demonstrates a robust and maintainable structure for managing tasks. By breaking down the responsibilities into distinct components—Model, View, and Controller—the application achieves a clear separation of concerns, which enhances both scalability and maintainability.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Add Google Sign-In to Your Node.js Application</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Wed, 03 Apr 2024 12:09:33 +0000</pubDate>
      <link>https://dev.to/pmadhav82/how-to-add-google-sign-in-to-your-nodejs-application-1b6j</link>
      <guid>https://dev.to/pmadhav82/how-to-add-google-sign-in-to-your-nodejs-application-1b6j</guid>
      <description>&lt;p&gt;I have recently added &lt;code&gt;Sign in with Google&lt;/code&gt; feature in my NodeJS app &lt;a href="https://p-blog.online/" rel="noopener noreferrer"&gt;P_Blog&lt;/a&gt; which allows users to quickly and easily login in using their existing Google account. &lt;code&gt;Sign in with Googgle&lt;/code&gt; feature is very convenient as it is trusted and no need to create and memorize password. In this blog post, I would like explain how I implemented &lt;code&gt;Sign in with Google&lt;/code&gt; feature in my nodejs app.&lt;br&gt;
I have used Nodejs(Expressjs) and Express-handlebars in the app. The app already has traditional email/password login system. Now, let's get right into it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup a Firebase Project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://console.firebase.google.com/u/0/" rel="noopener noreferrer"&gt;Firebase console&lt;/a&gt;, make sure you are signed in with your Google account&lt;/li&gt;
&lt;li&gt;Click on "Add project" and give your project a descriptive name and follow the prompts to create the project&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;code&gt;Authentication&lt;/code&gt; section in the sidebar and click on &lt;code&gt;Sign-in method&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Add new provider&lt;/code&gt;, find &lt;code&gt;Google&lt;/code&gt; in the list of provides and click on &lt;code&gt;Enable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to your newly created project's &lt;code&gt;Project settings&lt;/code&gt;, from &lt;code&gt;General&lt;/code&gt; tab find your "SDK setup and configuration", and select "CDN"&lt;/li&gt;
&lt;li&gt;Copy whole &lt;code&gt;script&lt;/code&gt;, we need to download the Service Account JSON file for backend but for that we will come latter&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Add &lt;code&gt;Continue with Google&lt;/code&gt; button
&lt;/h3&gt;

&lt;p&gt;We have set up the project, Now let's create a file &lt;code&gt;googleLogin.handlebars&lt;/code&gt; in &lt;code&gt;partials&lt;/code&gt; folder as we will use this button in &lt;code&gt;login&lt;/code&gt; and &lt;code&gt;signup&lt;/code&gt; pages. Following code will go in this file including copied project's SDK and configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; &amp;lt;script type="module"&amp;gt;
  // Import the functions you need from the SDKs you need
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.1/firebase-app.js";
  import {getAuth, GoogleAuthProvider, signOut, signInWithPopup} from "https://www.gstatic.com/firebasejs/10.8.1/firebase-auth.js";


  // TODO: Add SDKs for Firebase products that you want to use
  // https://firebase.google.com/docs/web/setup#available-libraries

  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""

  };

  // Initialize Firebase
  const app = initializeApp(firebaseConfig);
const auth = getAuth(app)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have used SVG google icon, here is html and css for that-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;style&amp;gt;
  .google-btn-container {
    background-color: rgb(66, 133, 244);
    color: rgb(255,
        255, 255);
    height: 50px;
    width: 240px;
    border: none;
    text-align: center;
    box-shadow: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
    font-size: 16px;
    line-height:
      48px;
    display: block;
    border-radius: 1px;
    transition: background-color 0.218s ease 0s, border-color 0.218s ease 0s, box-shadow 0.218s ease 0s;
    font-family:
      Roboto, arial, sans-serif;
    cursor: pointer;
    user-select: none;
    margin: .5rem 0;
  }

  .google-icon {
    width: 48px;
    height: 48px;
    text-align: center;
    display:
      block;
    margin-top: 1px;
    margin-left: 1px;
    float: left;
    background-color:
      rgb(255, 255, 255);
    border-radius: 1px;
    white-space: nowrap;
    display: flex;
    align-items: center;
    justify-content: center;
  }
&amp;lt;/style&amp;gt;


&amp;lt;div class="google-btn-container"&amp;gt;
  &amp;lt;span class="google-icon"&amp;gt;

    &amp;lt;svg
      width="32px"
      height="32px"
      viewBox="0 0 32 32"
      data-name="Layer 1"
      id="Layer_1"
      xmlns="http://www.w3.org/2000/svg"
      fill="#000000"
    &amp;gt;&amp;lt;g id="SVGRepo_bgCarrier" stroke-width="0"&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g
        id="SVGRepo_tracerCarrier"
        stroke-linecap="round"
        stroke-linejoin="round"
      &amp;gt;&amp;lt;/g&amp;gt;&amp;lt;g id="SVGRepo_iconCarrier"&amp;gt;&amp;lt;path
          d="M23.75,16A7.7446,7.7446,0,0,1,8.7177,18.6259L4.2849,22.1721A13.244,13.244,0,0,0,29.25,16"
          fill="#00ac47"
        &amp;gt;&amp;lt;/path&amp;gt;&amp;lt;path
          d="M23.75,16a7.7387,7.7387,0,0,1-3.2516,6.2987l4.3824,3.5059A13.2042,13.2042,0,0,0,29.25,16"
          fill="#4285f4"
        &amp;gt;&amp;lt;/path&amp;gt;&amp;lt;path
          d="M8.25,16a7.698,7.698,0,0,1,.4677-2.6259L4.2849,9.8279a13.177,13.177,0,0,0,0,12.3442l4.4328-3.5462A7.698,7.698,0,0,1,8.25,16Z"
          fill="#ffba00"
        &amp;gt;&amp;lt;/path&amp;gt;&amp;lt;polygon
          fill="#2ab2db"
          points="8.718 13.374 8.718 13.374 8.718 13.374 8.718 13.374"
        &amp;gt;&amp;lt;/polygon&amp;gt;&amp;lt;path
          d="M16,8.25a7.699,7.699,0,0,1,4.558,1.4958l4.06-3.7893A13.2152,13.2152,0,0,0,4.2849,9.8279l4.4328,3.5462A7.756,7.756,0,0,1,16,8.25Z"
          fill="#ea4435"
        &amp;gt;&amp;lt;/path&amp;gt;&amp;lt;polygon
          fill="#2ab2db"
          points="8.718 18.626 8.718 18.626 8.718 18.626 8.718 18.626"
        &amp;gt;&amp;lt;/polygon&amp;gt;&amp;lt;path
          d="M29.25,15v1L27,19.5H16.5V14H28.25A1,1,0,0,1,29.25,15Z"
          fill="#4285f4"
        &amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;
  &amp;lt;/span&amp;gt;

  &amp;lt;span class="google-text"&amp;gt;
    Continue with Google

  &amp;lt;/span&amp;gt;

&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to add this in both &lt;code&gt;Login&lt;/code&gt; and &lt;code&gt;Signup&lt;/code&gt; page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 {{&amp;gt; googleLogin}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages2.imgbox.com%2F6f%2Fe4%2FbCRcfkcb_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages2.imgbox.com%2F6f%2Fe4%2FbCRcfkcb_o.png" alt="google-btn"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Send &lt;code&gt;userIdToken&lt;/code&gt; to Backend
&lt;/h3&gt;

&lt;p&gt;Once user click on &lt;code&gt;Continue with Google&lt;/code&gt; button google displays a screen asking user to choose which account to use and outlining the permissions requested by the app. Upon user grants permission, Google provides a secure ID token which contains user information. We need to send this token to backend for verification. Once verified, we will navigate user to the home page.&lt;br&gt;
Here is code for that,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const sendUserIdToken = async(userIdToken)=&amp;gt;{

  try{
   const res = await fetch("/googleLogin",{
    headers:{
"Content-Type":"application/json"
    },
    method:"POST",
    body: JSON.stringify({userIdToken})
  })
if(!res.ok){
  throw new Error("Error sending token")
}
const responseData = await res.json();
return responseData;

  }catch(er){
    console.log(er)
  }
}


const googleSignIn = async ()=&amp;gt;{

  try{
const proveder = new GoogleAuthProvider();
const result = await signInWithPopup(auth, proveder);
const userIdToken = await result.user.getIdToken();

if(userIdToken){
 const responseData = await sendUserIdToken(userIdToken);
 if(responseData?.success){
  window.location.href = "/"
 }
  }
  }catch(er){
    console.log(er)
  }


}


const googleBtn = document.querySelector(".google-btn-container");

googleBtn.addEventListener("click", googleSignIn);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above code, we added click event in &lt;code&gt;googleBtn&lt;/code&gt; which will initiate authentication with Google and obtain a user ID token upon successfull. &lt;code&gt;sendUserIdToken&lt;/code&gt; sends token to the backend for verification and return response. If we have success response which we will setup in backend in a moment, user will redirect to homepage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token verification in the Backend
&lt;/h3&gt;

&lt;p&gt;First of all, we need to download &lt;code&gt;service-account-key.json&lt;/code&gt; file from Firebase console. For that go to your &lt;code&gt;Project settings&lt;/code&gt; and from &lt;code&gt;Service accounts&lt;/code&gt; tab download &lt;code&gt;.json&lt;/code&gt; file. We need &lt;code&gt;firebase-admin&lt;/code&gt; package as well which enables access to Firebase services from nodejs environment. To install run &lt;code&gt;npm install --save firebase-admin&lt;/code&gt; in your terminal.&lt;/p&gt;

&lt;p&gt;I have created &lt;code&gt;googleLoginRoute&lt;/code&gt; inside &lt;code&gt;routes&lt;/code&gt; folder and following code goes in that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const firebase = require("firebase-admin");
const serviceAccount = require("../service-account-key.json");
firebase.initializeApp({
  credential: firebase.credential.cert(serviceAccount),
});

const Users = require("../module/user");
const setUserDataInSession = require("../utils/setUserDataInSession");


const googleLoginRoute = require("express").Router();

googleLoginRoute.post("/", async (req, res) =&amp;gt; {
  const { userIdToken } = req.body;
if(!userIdToken || typeof userIdToken !== "string"){
   return  res.status(400).json({success:false, error:"Missing or Invalid token"})
}

  try {
    const {name, picture, email} = await firebase.auth().verifyIdToken(userIdToken);

    const foundUser = await Users.findOne({email});

    if(!foundUser){
        const newUserObj ={
            name,
            email,
            authMethod:"Google",
            password:null,
            profileURL: picture
        }
    const newUser =  await new Users(newUserObj).save();
setUserDataInSession(req, newUser);
    } 
   setUserDataInSession(req, foundUser);
    res.status(200).json({ success: true, error: null });
  } catch (er) {
    console.log(er);
    res.status(401).json({ success: false, error: "Invalid Token" });
  }
});

module.exports = googleLoginRoute;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above code, first, we setup &lt;code&gt;firebase-admin&lt;/code&gt;. We imported &lt;code&gt;Users&lt;/code&gt; module to save user information in database. We imported &lt;code&gt;setUserDataInSession&lt;/code&gt; function which will set user information in session once user is verified.&lt;/p&gt;

&lt;p&gt;When we got &lt;code&gt;POST&lt;/code&gt; request at &lt;code&gt;/googleLogin&lt;/code&gt; endpoint, first we validate the request body whether the &lt;code&gt;userIdToken&lt;/code&gt; is present or not. Then, it attempts to verify the Google ID token using Firebase Authentication. If the token is valid, we extract the user's &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;picture&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; from the decoded token as we need these to save it to our database, if user is logging in for the first time.&lt;br&gt;
If no user is found in the database with provided email, we create a new user and save user to database and set user data in the session. Finally, we send a success response to the frontend.&lt;/p&gt;

&lt;p&gt;We are all set, now we just need to required this in &lt;code&gt;app.js&lt;/code&gt; file to handle &lt;code&gt;/googleLogin&lt;/code&gt; route as following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const googleLoginRoute = require("./routes/googleLoginRoute");

app.use("/googleLogin", googleLoginRoute);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for reading! I hope this guide was helpfull in integrating Google Sign-In into your Node.js application.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building Dynamic Conversations: A Step-by-Step Guide to Implementing a Nested Comment System in Your Node.js Application</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Tue, 09 Jan 2024 06:31:43 +0000</pubDate>
      <link>https://dev.to/pmadhav82/building-dynamic-conversations-a-step-by-step-guide-to-implementing-a-nested-comment-system-in-your-nodejs-application-4pgf</link>
      <guid>https://dev.to/pmadhav82/building-dynamic-conversations-a-step-by-step-guide-to-implementing-a-nested-comment-system-in-your-nodejs-application-4pgf</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IeacImY6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/ff/37/0yHRAJUA_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IeacImY6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/ff/37/0yHRAJUA_o.png" alt="feature pic" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This blog post is about adding nested comment system in nodejs application. I have built a blogging web application &lt;a href="https://www.pblog.online/"&gt;PBlog &lt;/a&gt; using Express.js and handlebar where user can create an account, login to the system and can write a post using markdown. Now, I am adding a nested comment system to this application. A nested comment system is a feature that allows users to comment on a post add replies to other comments and create a hierarchy of comments.&lt;/p&gt;

&lt;p&gt;Now, let's create a file &lt;code&gt;comment.js&lt;/code&gt; inside &lt;code&gt;Model&lt;/code&gt; folder. In &lt;code&gt;comment.js&lt;/code&gt; file we will define a schema for comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment schema
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commentSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

&lt;span class="na"&gt;postedBy&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;parentComment&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;commentedAt&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; 
&lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;commentSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postedBy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commentSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;postedBy&lt;/strong&gt;: A reference to a user who posted that comment or reply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;text&lt;/strong&gt;: A required field representing the &lt;code&gt;text&lt;/code&gt; content of the comment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;postId&lt;/strong&gt;: A reference to a Post using its ObjectId.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;parentComment&lt;/strong&gt;: A reference to another comment (if it's a reply), defaulting to null for top-level comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;commentedAt&lt;/strong&gt;: The timestamp for when the comment was created, with a default value of the current date and time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;replies&lt;/strong&gt;: An array of ObjectId references to other comments, forming a nested structure for comment replies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following code will pre-Populate Replies and user information for each reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commentSchema.pre("find", function( next){
    this.populate({path:"replies",
populate:{path:"postedBy"}

})
    next()
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a Mongoose middleware using &lt;code&gt;pre&lt;/code&gt; which runs before the &lt;code&gt;find&lt;/code&gt; operation. It populates the &lt;code&gt;replies&lt;/code&gt; field with actual comment objects, and user information for each reply when querying the database.&lt;/p&gt;

&lt;p&gt;Finally, we create and export the &lt;code&gt;Comment&lt;/code&gt; model to make it available for use in other parts of the application&lt;br&gt;
Now, let's create a route to handle the submission of comment for a specific post. I have created a new file &lt;code&gt;Comment.js&lt;/code&gt; in &lt;code&gt;route&lt;/code&gt; folder. This file will handle all routes related to comments.&lt;/p&gt;
&lt;h2&gt;
  
  
  Route to post a comment
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commentRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connect-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express-validator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/loginHandeler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../module/comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;commentRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;withMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Comment field can not be empty!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;()){&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;commentObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;postedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentObj&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Failed to post a comment`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#comment-field`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here, we've:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defined a route for handling comments&lt;/li&gt;
&lt;li&gt;imported &lt;code&gt;connect-flash&lt;/code&gt; for flash messages, &lt;code&gt;express-validator&lt;/code&gt; for input validation, &lt;code&gt;islogin&lt;/code&gt; is a middleware for checking user is logged in or not. Because user need to log in to post a comment, and &lt;code&gt;Comment&lt;/code&gt; model&lt;/li&gt;
&lt;li&gt;set up &lt;code&gt;Post&lt;/code&gt; route to handle comment submissions&lt;/li&gt;
&lt;li&gt;used &lt;code&gt;express-validator&lt;/code&gt; to validate the comment input, ensuring it's not empty and escaping any potentially harmful characters&lt;/li&gt;
&lt;li&gt;Saved comments in the database if there are no errors. If there is a validation error we've flashed an error message to be displayed
Finally, the user is redirected to the same post.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Route to post a reply in a particular comment
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;commentRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:commentId/reply/:postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replyText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;replyText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;()){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replyObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;postedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;replyText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parentComment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newReply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replyObj&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOneAndUpdate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="na"&gt;$push&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;newReply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Failed to post a reply`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the above route, we have two parameters, &lt;code&gt;commentId&lt;/code&gt; and &lt;code&gt;postId&lt;/code&gt; which are needed to identify where a particular reply belongs to. Again, we have used &lt;code&gt;express-validator&lt;/code&gt; for the input validation and &lt;code&gt;islogin&lt;/code&gt; middleware to check if the user is logged in or not. If there are no errors, first we have saved reply in a database with &lt;code&gt;postId&lt;/code&gt; and its &lt;code&gt;parentComment&lt;/code&gt; id. Then, &lt;code&gt;replies&lt;/code&gt; field of the parent comment is updated. Finally, the user is redirected to the same post page.&lt;/p&gt;
&lt;h2&gt;
  
  
  Route to delete comment
&lt;/h2&gt;

&lt;p&gt;The route to delete a comment will be similar to the route to post a reply. It will also have two parameters &lt;code&gt;commentId&lt;/code&gt; and &lt;code&gt;postId&lt;/code&gt; as we need to know which comment to delete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;commentRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:commentId/delete/:postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;islogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postedBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="na"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
         &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Comment failed to delete`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#comment-field`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commentRoute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, first, we tried to find a comment using &lt;code&gt;Comment.findOne()&lt;/code&gt; method with the specified commentId and postId. If the comment is found and the user who posted it matches the currently logged-in user, it proceeds with the deletion process. Because only the user who posted that particular comment can delete that comment. We will show our UI according to this later on. If the logged-in user is the owner of that comment a delete button will be shown otherwise not. &lt;br&gt;
In our deletion process, it first deletes all replies associated with that particular comment&lt;br&gt;
&lt;code&gt;await Comment.deleteMany({_id:{$in:comment.replies}})&lt;/code&gt;&lt;br&gt;
and then, it deletes the comment itself &lt;code&gt;await Comment.deleteOne({_id:commentId})&lt;/code&gt;. If the deletion is successful, it redirects the user back to the post. If the comment is not found, or the user is not the owner of the post, it flashes an error message and redirects the user back to the post page.&lt;br&gt;
Lastly, we have exported the &lt;code&gt;CommentRoute&lt;/code&gt; and we will import and use it in &lt;code&gt;app.js&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commentRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes/comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commentRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's look at what is happening in &lt;code&gt;/${postId}&lt;/code&gt; route. This route is handled in &lt;code&gt;route.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parentComment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postedBy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;singlePost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;         
  &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;userStatus&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;er&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route will send a single post to the user, first, it find a particular post with &lt;code&gt;postId&lt;/code&gt; and then, It will find the top-level comment associated with that post and populate &lt;code&gt;replies&lt;/code&gt; and &lt;code&gt;postedBy&lt;/code&gt; a user information who posted that particular comment. &lt;code&gt;userStatus&lt;/code&gt; will have currently logged-in user information if the user is logged-in otherwise it will return &lt;code&gt;userStatus.login&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment input User Interface
&lt;/h2&gt;

&lt;p&gt;We have set up all routes related to comments. Now, it's time to work on frontend. First up all, we will create UI to post a comment. I've created  &lt;code&gt;-commentInput.handlebars&lt;/code&gt; inside &lt;code&gt;partial&lt;/code&gt; folder of &lt;code&gt;view&lt;/code&gt; directory. Following code goes inside this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;= "&lt;/span&gt;&lt;span class="nc"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;

   &amp;lt;div class=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;user_pic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;
        &amp;lt;img alt=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; src=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;userStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profileURL&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; /&amp;gt;
      &amp;lt;/div&amp;gt;
  &amp;lt;div class = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;
  &amp;lt;form action=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; method=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;

    &amp;lt;textarea rows = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; placeholder=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;  cols=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; name=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;
    &amp;lt;br&amp;gt;
    &amp;lt;button class=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; type = &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; Submit &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;{{&amp;gt; message}}&lt;/code&gt;  is a partial template that will show an error message if there is any, and I've used it in &lt;code&gt;singlePost.handlebars&lt;/code&gt; as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;userStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;commentInput&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;displayComment&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h5&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h5&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment input box will only be seen if the user is logged-in, and if the particular post has a comment, the comment will display. Now, we will see &lt;code&gt;{{&amp;gt; displayComment}}&lt;/code&gt; partial template in detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jd4d2L4Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/f0/34/kJFIuDt7_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jd4d2L4Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/f0/34/kJFIuDt7_o.png" alt="comment-input-UI" width="549" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering comment
&lt;/h2&gt;

&lt;p&gt;Let's create another file &lt;code&gt;displayComment.handlebars&lt;/code&gt; in &lt;code&gt;partial&lt;/code&gt; folder in &lt;code&gt;view&lt;/code&gt; directory. &lt;code&gt;displayComment.handlebars&lt;/code&gt; will do following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display comment and its reply recursively&lt;/li&gt;
&lt;li&gt;If user is not logged-in only comment will be seen&lt;/li&gt;
&lt;li&gt;If user is logged-in a &lt;code&gt;Reply&lt;/code&gt; button should be there for user to put the reply&lt;/li&gt;
&lt;li&gt;If user is logged-in and if there is any comment won by that user a &lt;code&gt;Reply&lt;/code&gt; button as well as a &lt;code&gt;Delete&lt;/code&gt; button should be there so that user would be able to delete that comment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following lines of code will in &lt;code&gt;displayComment.handlebars&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="comment"&amp;gt;
{{#each comments}}

&amp;lt;div class = "single-cmt-card" id="{{this._id}}"&amp;gt;
  &amp;lt;div class = "card-header"&amp;gt;
   &amp;lt;div class="user_pic"&amp;gt;
       &amp;lt;a href="/user?id={{this.postedBy._id}}"&amp;gt;

        &amp;lt;img alt="user-pic" src="{{this.postedBy.profileURL}}" /&amp;gt;
       &amp;lt;/a&amp;gt;

      &amp;lt;/div&amp;gt;
    &amp;lt;div class = "user-name"&amp;gt;
      &amp;lt;b&amp;gt;{{this.postedBy.name}}&amp;lt;/b&amp;gt;
            &amp;lt;span class="post_date"&amp;gt; {{formatDate this.commentedAt}}&amp;lt;/span&amp;gt;

  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="card-body"&amp;gt;
 {{this.text}}

    &amp;lt;/div&amp;gt;



&amp;lt;div class = "action-btns"&amp;gt; 

{{#if (showBtns "reply")}}
&amp;lt;button class="reply-btn"&amp;gt; Reply 
  &amp;lt;/button&amp;gt;
{{/if}}

  {{#if (showBtns "delete" this.postedBy._id)}}
  &amp;lt;form  action="/comment/{{this._id}}/delete/{{this.postId}}" method="post" &amp;gt;
  &amp;lt;button type="submit" class= "delete-btn"&amp;gt; Delete &amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
  {{/if}}

&amp;lt;/div&amp;gt;

{{#if (showBtns "reply")}}
 &amp;lt;div class = "reply-cmt"&amp;gt;
  &amp;lt;form action="/comment/{{this._id}}/reply/{{this.postId}}" method="post"&amp;gt;
      &amp;lt;textarea required class="reply-textarea" name="replyText" rows="3"  placeholder = 'Enter your reply'&amp;gt;&amp;lt;/textarea&amp;gt;
      &amp;lt;div class = "action-btns"&amp;gt;
        &amp;lt;button type="submit"&amp;gt; Submit &amp;lt;/button&amp;gt;
        &amp;lt;button type="button" class = "cancel-btn"&amp;gt; Cancel &amp;lt;/button&amp;gt;

      &amp;lt;/div&amp;gt;

  &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;

{{/if}}
&amp;lt;/div&amp;gt;



 {{#if this.replies}}
&amp;lt;div class="reply" style="margin-left: 5px; border-left: solid 1px var(--primary-text-color);"&amp;gt;
    {{&amp;gt; displayComment comments = this.replies}}
&amp;lt;/div&amp;gt;
{{/if}} 

{{/each}}

&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code will display comments and their replies recursively. &lt;code&gt;formatDate&lt;/code&gt; and &lt;code&gt;showBtns&lt;/code&gt; are &lt;a href="https://github.com/express-handlebars/express-handlebars"&gt;helper functions &lt;/a&gt;  to display the comment's relative time, and conditionally display buttons based on user permissions. &lt;code&gt;Reply&lt;/code&gt; button triggers the display of a reply form for the comment which includes submit and cancel buttons to send or discard the reply. I've used a vanilla javascript to dynamically toggle the display of reply forms for individual comments. &lt;code&gt;Delete&lt;/code&gt; button submits a form to delete the comment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wy1lW86U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/1d/99/73X9MBeE_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wy1lW86U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/1d/99/73X9MBeE_o.png" alt="display-comment" width="516" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MUZ93ENW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/74/1c/PgW22WT0_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MUZ93ENW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/74/1c/PgW22WT0_o.png" alt="display-comment" width="465" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Helper functions
&lt;/h2&gt;

&lt;p&gt;Now, let's look at these two helper functions in details. I have created &lt;code&gt;helperFunctions.js&lt;/code&gt; file inside &lt;code&gt;util&lt;/code&gt; folder and following code will go in that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userStatus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./userStatusChecker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fromNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showBtns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userStatus&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttonType&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
   &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reply&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;showBtns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above code, &lt;code&gt;formatDate(date)&lt;/code&gt; formats a date into a more human-readable format, specifically using relative time expressions like "a few seconds ago", "5 minutes ago" etc. I've used &lt;code&gt;moment&lt;/code&gt; library for that and used &lt;code&gt;moment(date).fromNow()&lt;/code&gt; to generate a relative time.&lt;br&gt;
&lt;code&gt;showBtns(buttonType, userId)&lt;/code&gt; conditionally determines wheather to display buttons based on user login status and user ID. &lt;code&gt;userStatus&lt;/code&gt; will retrieve user status from &lt;code&gt;userStatusChecker&lt;/code&gt; module. For &lt;code&gt;reply&lt;/code&gt; button type will return &lt;code&gt;true&lt;/code&gt; if user is logged-in. For &lt;code&gt;delete&lt;/code&gt; button type will return &lt;code&gt;true&lt;/code&gt; if user is logged-in and current user id is matched with comment posted user id otherwise return &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
Finally, we need to register these helper functions in our &lt;code&gt;app.js&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;showBtns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./utils/helperFunctions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handlebars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;handlebars&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
    &lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;showBtns&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, I have added nested comment system in my app.&lt;br&gt;
Finally, I would like to thank you for reading this post.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Deep Dive into Password Reset Flows with Node.js and Express.js</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Wed, 13 Sep 2023 23:48:34 +0000</pubDate>
      <link>https://dev.to/pmadhav82/a-deep-dive-into-password-reset-flows-with-nodejs-and-expressjs-46g4</link>
      <guid>https://dev.to/pmadhav82/a-deep-dive-into-password-reset-flows-with-nodejs-and-expressjs-46g4</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7l8FCIeU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/0f/2b/imkAL9B7_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7l8FCIeU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/0f/2b/imkAL9B7_o.png" alt="blog-image" width="800" height="458"&gt;&lt;/a&gt;&lt;br&gt;
When it comes to web development and ensuring the security of user accounts, the password reset process is a critical component that should not be overlooked. Therefore, implementing a robust and secure password reset flow is a necessity for any web application that values user security and usability.&lt;/p&gt;

&lt;p&gt;In this blog post, we will learn how to implement a robust password reset flow in your Node.js (Express.js) application. By the end, you'll have a comprehensive understanding of how to safeguard your users's accounts and provide them with a seamless password reset experience.&lt;/p&gt;

&lt;p&gt;Below is the password reset flow diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VhTSukvt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/4e/ca/2H39Mvzm_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VhTSukvt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/4e/ca/2H39Mvzm_o.png" alt="flow-diagram" width="431" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's break down what steps are involved in this process one by one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request for password reset: use can send a request for password reset by filling password reset form&lt;/li&gt;
&lt;li&gt;Valid user information: The system will validate user exists or not&lt;/li&gt;
&lt;li&gt;Send an email to the user with token: If the user exists, the system will send an email to the provided email ID with user id and a token which will expire within a certain time&lt;/li&gt;
&lt;li&gt;Check if the token is valid or expired: When the user clicks on the sent link, the system will again verify if the token is valid or not and if the token is related to that particular user or not&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the new password: If everything goes well, finally user will be given a password reset form where they can enter a new password&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request for password reset
Once the user clicks on &lt;code&gt;Forgot password&lt;/code&gt; link the server will get &lt;code&gt;get&lt;/code&gt; request and send a form where the user can enter their email id. The following code goes in &lt;code&gt;router.js&lt;/code&gt; file.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; //password reset route
router.get("/forgot-pass",  (req, res)=&amp;gt;{
    res.render("reset")
 })

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, let's create &lt;code&gt;reset.handlebars&lt;/code&gt; file where we will have the form&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="login-box"&amp;gt;
 &amp;lt;div class="messages"&amp;gt;
  {{&amp;gt;message}}
 &amp;lt;/div&amp;gt;

  &amp;lt;form action="/password-reset" method="POST"&amp;gt;
  &amp;lt;h2&amp;gt;Reset your password&amp;lt;/h2&amp;gt;

    &amp;lt;div class="user-box"&amp;gt;

      &amp;lt;input type="text" name="email" value="{{email}}" required&amp;gt;
      &amp;lt;label&amp;gt;Email address&amp;lt;/label&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;button type="submit"&amp;gt;Send link&amp;lt;/button&amp;gt;

  &amp;lt;/form&amp;gt;

&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MBL5SCIA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/59/51/GXHushOQ_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MBL5SCIA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/59/51/GXHushOQ_o.png" alt="password-reset-form" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once, the user enters their email id and hits &lt;code&gt;Send link&lt;/code&gt; button the server will get &lt;code&gt;post&lt;/code&gt; request at &lt;code&gt;/password-reset&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validating user information and sending an email with a link&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the most time-consuming part of this process. We have to verify user information, set up &lt;code&gt;nodemailer&lt;/code&gt;, generate a token, and save the token in the database plus send an email with the token and user id. This all process happens when the server receive &lt;code&gt;post&lt;/code&gt; requeset from &lt;code&gt;/password-reset&lt;/code&gt; endpoint&lt;/p&gt;

&lt;p&gt;First, let's create &lt;code&gt;tokenSchema&lt;/code&gt; in &lt;code&gt;token.js&lt;/code&gt; file to save token with user information and an expiration time of 1 hour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mongoose = require("mongoose");
const{Schema, model}= mongoose;

const tokenSchema= new Schema({
    userId:{
        type:Schema.Types.ObjectId,
        require:true,
        ref:"Users"   
    },
    token:{
        type:String,
        require:true
    },
    createdAt:{
        type:Date,
        default:Date.now,
        expires:3600
    }
})

module.exports = new model("token",tokenSchema);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create another file &lt;code&gt;tokenHandeler.js&lt;/code&gt; file. Where we will write generating token, validating token and saving the token in database logic&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Token = require("../module/token");
const bcrypt = require("bcrypt");
const crypto = require("crypto");


const generateToken = async(id)=&amp;gt;{
let token = await Token.findOne({userId:id});
// delete already exits token
if(token){
    await Token.deleteOne({userId:id});
}

// generate token
const resetToken = crypto.randomBytes(32).toString("hex");

//hash reset token
const hashedToken =  await bcrypt.hash(resetToken,10)

// save token in database

await new Token({
    userId:id,
    token:hashedToken,
    createdAt:Date.now()
}).save()

return resetToken;

}



const isValidToken = async({token,id})=&amp;gt;{
    try{
        const savedToken = await Token.findOne({userId:id}).lean();
        //compare the token
        if(savedToken){
            const isValidToken = await bcrypt.compare(token,savedToken.token);
            return isValidToken;
        }else return false

    } catch(er){
        console.log("something went wrong..." )
    }

}



module.exports = {
    generateToken,
    isValidToken
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we have &lt;code&gt;generateToken&lt;/code&gt; function which will generate a unique token with the help of the &lt;code&gt;crypto&lt;/code&gt; package. We hashed that token and save it to database along with user id and we have &lt;code&gt;isValidToken&lt;/code&gt; function which will take &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; and compare it with saved &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;userID&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's set up &lt;code&gt;nodemailer&lt;/code&gt;. For that create a file &lt;code&gt;sendEmail.js&lt;/code&gt;. The file will have the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const nodemailer = require("nodemailer");

const sendEmail = (payload)=&amp;gt;{
const { email, subject, html}= payload

const transporter = nodemailer.createTransport({
    host:process.env.EMAIL_HOST,
    secure:true,
    port:465,
    auth:{
        user:process.env.EMAIL_USERNAME,
        pass:process.env.EMAIL_PASSWORD
    }
})

const mailOptions ={
from:"p.blog@pblog.online",
to:email,
subject:subject,
html:  html
}

transporter.sendMail(mailOptions,(error,info)=&amp;gt;{
    if(error){
        console.log(error)
    }else{
console.log(info);
    }

})

}

module.exports = sendEmail;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we have &lt;code&gt;sendEmail&lt;/code&gt; function. Inside that function we have set up &lt;code&gt;nodemailer&lt;/code&gt; and using this function we can send an email to the user with the password reset link.&lt;/p&gt;

&lt;p&gt;I have created a &lt;strong&gt;Zoho Mail Account&lt;/strong&gt;. In my case &lt;code&gt;host&lt;/code&gt; is &lt;code&gt;smtp.zoho.com.au&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt; will be my Zoho mail and &lt;code&gt;pass&lt;/code&gt; is Zoho application password.&lt;/p&gt;

&lt;p&gt;Since we have set up &lt;code&gt;nodemailer&lt;/code&gt; and &lt;code&gt;token&lt;/code&gt;, now we are ready to handle &lt;code&gt;post&lt;/code&gt; request came from &lt;code&gt;/password-reset&lt;/code&gt;. In &lt;code&gt;router.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require ("express");
const router = express.Router();
const sendEmail = require("./utils/sendEmail");
const {generateToken, isValidToken} = require ("./utils/tokenHandeler");
const flash = require("connect-flash");

// middleware fucntion to associate connect-flash on response
router.use((req,res,next)=&amp;gt;{
res.locals.message = req.flash();
next()
})

// password reset post route

router.post("/passport-reset", async (req,res)=&amp;gt;{
    const {email} = req.body;
    try{
let user = await Users.findOne({email});

if(user){
    const resetToken =  await generateToken(user._id);
    const link= `${req.protocol}://${req.get('host')}/password-reset-link?token=${resetToken}&amp;amp;id=${user._id}`;

// html for email
const html = `&amp;lt;b&amp;gt; Hi ${user.name}, &amp;lt;/b&amp;gt;
&amp;lt;p&amp;gt; You requested to reset your password. &amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt; Please, click the link below to reset your password. &amp;lt;/p&amp;gt;
&amp;lt;a href = "${link}"&amp;gt; Reset Password &amp;lt;/a&amp;gt;
`

console.log(link);

const payload = {
    email,
    subject:"Password reset request",
    html
}

sendEmail(payload);


req.flash("success", "Check your email for the password reset link")
    res.redirect("/login")
}else{
    req.flash("error","We could not find any user, please check your email address again")
res.redirect("/forgot-pass")
}
    }catch(er){
        console.log(er);
        req.flash("error","Something went wrong, please try again later!")
        res.redirect("/forgot-pass")
    }
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down above code. First, we looked for user associated with an email id provided by user. If user is not found then we have redirected user to &lt;code&gt;/forgot-pass&lt;/code&gt; route with an &lt;code&gt;error&lt;/code&gt; message. If user is found we generate a &lt;code&gt;token&lt;/code&gt; using &lt;code&gt;generateToken&lt;/code&gt; function and we create a dynamic link where we have &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; in the link. We send an email to the user by using &lt;code&gt;sendEmail&lt;/code&gt; function. Then, we have redirected user to &lt;code&gt;/login&lt;/code&gt; router with &lt;code&gt;success&lt;/code&gt; message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--va8VN7mf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/b8/a0/eVquMoNc_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--va8VN7mf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/b8/a0/eVquMoNc_o.png" alt="sent-reset-link" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The password reset link will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8000/password-reset-link?token=e51d95cd5feba716fbe54163275ff874795fe7b52a625404a7e5adf00d3b2c22&amp;amp;id=62cd89d53e61de6c5bfbfe02

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, user will get an email where they can click to get the form where they can enter new password and send it back to server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f4dhZ0M1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/64/de/FTMi6KUL_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f4dhZ0M1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/64/de/FTMi6KUL_o.png" alt="reset-link-email" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have used &lt;code&gt;connect-flash&lt;/code&gt; to send message whenever a user is redirecting to specified route. I have created &lt;code&gt;message.handlebars&lt;/code&gt; file inside &lt;code&gt;partial&lt;/code&gt; folder and following code is in that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  {{#if message.success}}
  &amp;lt;div class="messages success-message"&amp;gt;
      {{message.success}}
  &amp;lt;/div&amp;gt;

    {{/if }}

   {{#if message.error}}
  &amp;lt;div class="messages error-message"&amp;gt;
      {{message.error}}
  &amp;lt;/div&amp;gt;
    {{/if }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show any error or success message to the user.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validating the token and sending password-reset form
When user clicks on that reset link, server will get &lt;code&gt;get&lt;/code&gt; request on &lt;code&gt;/password-reset-link&lt;/code&gt;. Let's handle this route together :)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//password reset form route
router.get("/password-reset-link",  async (req,res)=&amp;gt;{

if(req.query &amp;amp;&amp;amp; req.query.token &amp;amp;&amp;amp; req.query.id){
    //check token and id are valid
const{token,id} = req.query;

try{
   const isValid = await isValidToken({token,id});
    if(isValid){
res.render("newPasswordForm",{
    token,
    id,

})
    }else{
res.json({message:"Invalid token or link is expired"})
    }

}catch(er){
    console.log(er)
res.json({message:"something went wrong, please try again latter"})
}


}else{
    res.redirect("/login")
}

})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we have extracted the &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; from the link. We validate &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; with the help of &lt;code&gt;isValidToken&lt;/code&gt; function that we have created in &lt;code&gt;tokenHandeler.js&lt;/code&gt; file. If the link is valid &lt;code&gt;newPasswordForm&lt;/code&gt; is rendered and we have sent  &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; as well. These are needed again when we get &lt;code&gt;post&lt;/code&gt; request, once user enters new password and submit the form.&lt;/p&gt;

&lt;p&gt;One important thing to remember here is that user need to click on that link within 1hour as our token expires in 1hour. &lt;br&gt;
If user tried to modify the &lt;code&gt;URL&lt;/code&gt; or click the link after 1hour, user would not be able to see the form and user will see an error message&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HACsPwwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/79/47/Zd89v9Wx_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HACsPwwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/79/47/Zd89v9Wx_o.png" alt="error-message" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's create a file &lt;code&gt;newPasswordForm.handlebars&lt;/code&gt; file inside &lt;code&gt;views&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;div class="main-wrapper"&amp;gt;

&amp;lt;div class="login-box"&amp;gt;
{{#if errorMessage}}
    &amp;lt;div class="messages error-message"&amp;gt;
    {{errorMessage}}
    &amp;lt;/div&amp;gt;
    {{/if}}
  &amp;lt;form action="/newPassword?token={{token}}&amp;amp;id={{id}}" method="POST"&amp;gt;  
  &amp;lt;h2&amp;gt;Enter new password&amp;lt;/h2&amp;gt;

&amp;lt;div class="user-box"&amp;gt;
&amp;lt;input  type="password" name="password"  required&amp;gt;
&amp;lt;label &amp;gt; Password &amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="user-box"&amp;gt;
&amp;lt;input type="password" name="repeatPassword"  required&amp;gt;
&amp;lt;label &amp;gt; Repeat Password &amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;button type="submit"&amp;gt; Change Password&amp;lt;/button&amp;gt;

  &amp;lt;/form&amp;gt;

&amp;lt;/div&amp;gt;

&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This form will only shows up if the link is valid.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EAfLUFFT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/a4/85/37u86Znn_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EAfLUFFT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/a4/85/37u86Znn_o.png" alt="new-password-form" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting the new password
Finally, we have reached to the final step of password reset process. Once the user clicks on the link and if the link is valid and not expired user can have access to new password form where they can enter new password and submit. Once form is submitted, server will get &lt;code&gt;post&lt;/code&gt; request in &lt;code&gt;/newPasswor&lt;/code&gt; route. Let's handle this &lt;code&gt;post&lt;/code&gt; request.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//accept new password and save it to database
router.post("/newPassword",  async(req,res)=&amp;gt;{

    if(req.query.token &amp;amp;&amp;amp; req.query.id){
const {token, id}= req.query;
let isValid;
try{

    isValid = await isValidToken({token,id});
}catch(er){
    console.log(er)
}
if(isValid){
const {password, repeatPassword}=req.body;

if(password.length&amp;lt;6){

   return  res.render("newPasswordForm",{
    token,
    id,
    errorMessage:"Password need to have minimum 6 characters"
   })
}
if (password!== repeatPassword){
   return  res.render("newPasswordForm",{
    token,
    id,
    errorMessage:" Password is not match."
   })
}


if(password == repeatPassword &amp;amp;&amp;amp; password.length&amp;gt;6){
try{
    let hashedPassword = await bcrypt.hash(repeatPassword,10);
    let update_success = await Users.updateOne({_id:id},{password:hashedPassword});
    if(update_success){
        req.flash("success", "password is changed successfully.")
res.redirect("/login");
    }
}catch(er){
    console.log(er)
}
}
} else{
    res.json({message:"Invalid token or link is expired"})  

}

} else{
    res.json({message:"Something went wrong! try again latter"})  
}
    })

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down above code. Again, we have extracted &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; that came from &lt;code&gt;req.query&lt;/code&gt; and we have validated that &lt;code&gt;token&lt;/code&gt; one more time. If we have a valid &lt;code&gt;token&lt;/code&gt;, we have extracted &lt;code&gt;password&lt;/code&gt; and &lt;code&gt;repeatPassword&lt;/code&gt; from &lt;code&gt;req.body&lt;/code&gt; and we have validate the password. If the password is not valid we have render the  same &lt;code&gt;newPasswordForm&lt;/code&gt; with &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt; and related &lt;code&gt;errorMessage&lt;/code&gt;.&lt;br&gt;
If the password requirement is matched, new password is hashed and saved it to the database. Finally, we redirect user to login page with success message.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jfrrfenk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/b7/c6/yLQLlzu4_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jfrrfenk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/b7/c6/yLQLlzu4_o.png" alt="password-changed-success" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, user can login using their new password.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this blog post, and I hope you find it valuable in your web development journey. If you have any questions or suggestions, please feel free to reach out. Happy coding!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>express</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Step-by-Step Guide: Hosting a Node.js App on AWS EC2 with Domain Name and SSL Certificate</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Tue, 22 Aug 2023 02:20:23 +0000</pubDate>
      <link>https://dev.to/pmadhav82/step-by-step-guide-hosting-a-nodejs-app-on-aws-ec2-with-domain-name-and-ssl-certificate-145e</link>
      <guid>https://dev.to/pmadhav82/step-by-step-guide-hosting-a-nodejs-app-on-aws-ec2-with-domain-name-and-ssl-certificate-145e</guid>
      <description>&lt;h1&gt;
  
  
  Host your nodejs application on AWS EC2 instance with Domain name
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--guEdjpDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/34/8f/3UoFDTU2_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--guEdjpDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/34/8f/3UoFDTU2_o.png" alt="blog-pic" width="800" height="295"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This blog post is about hosting your nodejs application on AWS &lt;code&gt;EC2&lt;/code&gt; instance with domain name and secured it with the help of &lt;code&gt;certbot&lt;/code&gt;. Let's get started. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code&gt;Ec2&lt;/code&gt; instalce by choosing linux 2s &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave everything default and click launch instance &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to instances and click your instace and click on connect. &lt;br&gt;
Then, you will see option to connect to instance. If you want to access instance terminal from web-browser you can click on  &lt;code&gt;Connect&lt;/code&gt; button of &lt;code&gt;EC2 Instance Connect&lt;/code&gt;. Or, you can connect through SSH client. For that click on &lt;code&gt;SSH client&lt;/code&gt; tab to see what steps to take. &lt;br&gt;
As per instructions provided here, you need to open &lt;code&gt;SSH client&lt;/code&gt; such as linux terminal or git bash. &lt;br&gt;
Go to the folder where private key &lt;code&gt;.pem&lt;/code&gt; file is located and run following command which will give read only permision to the file&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  chmod 400 prevate-key-name.pem 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you are ready to connect instance using its public DNS or public ip address by following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ssh -i "node-server-key.pem" ec2-user@13.54.206.174 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, private key name is &lt;code&gt;node-server-key&lt;/code&gt;, user is &lt;code&gt;ec2-user&lt;/code&gt; and public id address is &lt;code&gt;13.54.206.174&lt;/code&gt;. You can use public DNS as well in place of public address&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ssh -i "node-server-key.pem" ec2-user@ec2-13-54-206-174.ap-southeast-2.compute.amazonaws.com              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that you can have access to terminal through SSH client. Once you have access to the terminal,type following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo yum install -y gcc-c++ make 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then, it install nodejs by typing following command &lt;br&gt;
  Type following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo yum install -y nodejs  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check the version of nodejs run &lt;br&gt;
 &lt;code&gt;node -v&lt;/code&gt; command &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now you can clone node app from your github. Once the repo is downloaded go to that folder and run
&lt;code&gt;npm instal&lt;/code&gt; 
which will install all the dependency. For environmental variables, you can create a new directory and create a &lt;code&gt;.env&lt;/code&gt; file there. You can put all environmental variables in that file:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  touch /var/app/env/.env 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Edit the file by running &lt;code&gt;nano /var/app/env/.env&lt;/code&gt; &lt;br&gt;
  Save the &lt;code&gt;.env&lt;/code&gt; file and we need &lt;code&gt;dotenv&lt;/code&gt; package to load those environmental variables. For that run following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  npm install dotenv 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it installed, in node.js application, we need config &lt;code&gt;dotenv&lt;/code&gt; package on top of main application file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; require('dotenv').config({path: 'var/app/env/.env'}) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the access type your &lt;code&gt;publicIP:portNum&lt;/code&gt;. In my case it is &lt;code&gt;http://13.54.206.174:3000/&lt;/code&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you want to run your sever in the background you can use &lt;code&gt;pm2&lt;/code&gt; which is a process manager for node.js application. &lt;code&gt;pm2&lt;/code&gt; can handle to start, restart and stop the server and log managmement. To install &lt;code&gt;pm2&lt;/code&gt; run following command
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; npm install pm2 -g 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start the server use command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pm2 start app.js 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8YqsXRpr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/93/24/UKqw1gXs_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8YqsXRpr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/93/24/UKqw1gXs_o.png" alt="pm2-start" width="800" height="115"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;To verify your server is running run following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pm2 list 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will display a list of all processes managed by &lt;code&gt;pm2&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;To stop the server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pm2 stop app.js 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;If you have a domain name and you want to use it, you need to install &lt;code&gt;nginx&lt;/code&gt; which is a web server by following command
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo yum install nginx 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run nginx run the command &lt;code&gt;sudo systemctl start nginx&lt;/code&gt; and visit &lt;code&gt;EC2&lt;/code&gt; instance public IP address in the browser. You will see Nginx default welcome page. &lt;br&gt;
 Once it installed, you need to configure Nginx to act as a reverse proxy for the application. Open the configuration file by following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo nano /etc/nginx/nginx.conf 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, edit the &lt;code&gt;nginx.conf&lt;/code&gt; file as following:&lt;br&gt;
&lt;/p&gt;

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

 http { 
     listen 80; 
     server_name pblog.online www.pblog.online; 

     location / { 
       proxy_pass http://localhost:8000; 
 proxy_http_version 1.1; 
 proxy_set_header Upgrade $http_upgrade; 
 proxy_set_header Connection 'upgrade'; 
 proxy_set_header Host $host; 
 proxy_cache_bypass $http_upgrade; 
     } 
 } 



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;pblog.online&lt;/code&gt; with you &lt;code&gt;domain name&lt;/code&gt;. &lt;br&gt;
 Once the file edited, we need to check if it is validate or not. For that run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo nginx -t 

 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
 nginx: configuration file /etc/nginx/nginx.conf test is successful 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the test is successful, you can reload the nginx by the command &lt;code&gt;sudo nginx -s reload&lt;/code&gt;. &lt;br&gt;
 You can check the nginx status by following command as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo systemctl status nginx 

 ● nginx.service - The nginx HTTP and reverse proxy server 
      Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: disabled) 
      Active: active (running) since Tue 2023-05-16 03:29:05 UTC; 43min ago 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;## Add Domain Name &lt;br&gt;
 To add a custom domain name, first  you need to assign Elastic IP address for you &lt;code&gt;EC2&lt;/code&gt; instance. You can do that from EC2 Dashbord, there you will elastic ip under &lt;code&gt;Network &amp;amp; Security&lt;/code&gt;. Once you have assigned elastic ip address go to your domain name provider account and find &lt;code&gt;DNS Records&lt;/code&gt; section. There we need to add new record. So, click on &lt;code&gt;Add new record&lt;/code&gt; option. Select record type &lt;code&gt;A&lt;/code&gt;, in &lt;code&gt;Name&lt;/code&gt; section type &lt;code&gt;www&lt;/code&gt; and value will be your elastic IP address and save it.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V0zh5st4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/e1/57/FwaJPCnx_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V0zh5st4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/e1/57/FwaJPCnx_o.png" alt="DNS record" width="800" height="318"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Now, hit your domain URL in the browser. It will take some time but you can visit your application running on EC2 instance by entering your domain name. &lt;br&gt;
 &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--efb0FU8x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/85/d4/8KuloDGj_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--efb0FU8x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/85/d4/8KuloDGj_o.png" alt="url" width="800" height="107"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;But the connection is not secured. To make it secured we need to add &lt;code&gt;SSL&lt;/code&gt; certificate. We can do it for free with the help of &lt;code&gt;Certbot&lt;/code&gt;. Run following commands to install &lt;code&gt;certbot&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo yum install python3 python3-venv libaugeas0 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    sudo /opt/certbot/bin/pip install --upgrade pip 
    ```





    ``` 
    sudo /opt/certbot/bin/pip install certbot certbot-nginx 
    ```





    ``` 
    sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot 
    ```





    ``` 
    sudo python3 -m venv /opt/certbot/ 
    ```



    Once it's done run the following command to get `ssl` certificate for you domain



    ``` 
    sudo certbot --nginx -d pblog.online -d www.pblog.online 
    ```


 Here, replace `pblog.online` with your own domain name. 

 `certbot` will ask you to confirm your domain name and add `ssl` certificate to your domain name. It will change configuration of `nginx.conf` file as well. Once the certificated is added `nginx.conf` file looks like this:


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; server { 
 server_name pblog.online www.pblog.online; 

     root         /usr/share/nginx/html; 

    # Load configuration files for the default server block. 
     include /etc/nginx/default.d/*.conf; 

     error_page 404 /404.html; 
     location / { 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;proxy_pass &lt;a href="http://localhost:8000"&gt;http://localhost:8000&lt;/a&gt;; &lt;br&gt;
 proxy_http_version 1.1; &lt;br&gt;
 proxy_set_header Upgrade $http_upgrade; &lt;br&gt;
 proxy_set_header Connection 'upgrade'; &lt;br&gt;
 proxy_set_header Host $host; &lt;br&gt;
 proxy_cache_bypass $http_upgrade; &lt;br&gt;
         } &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     error_page 500 502 503 504 /50x.html; 
     location = /50x.html { 
     } 

 listen [::]:443 ssl ipv6only=on; # managed by Certbot 
 listen 443 ssl; # managed by Certbot 
 ssl_certificate /etc/letsencrypt/live/pblog.online/fullchain.pem; # managed by Certbot 
 ssl_certificate_key /etc/letsencrypt/live/pblog.online/privkey.pem; # managed by Certbot 
 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 
 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; server { 
 if ($host = www.pblog.online) { 
     return 301 https://$host$request_uri; 
 } # managed by Certbot 


 if ($host = pblog.online) { 
     return 301 https://$host$request_uri; 
 } # managed by Certbot 

     listen 80; 
     listen [::]:80; 

    server_name pblog.online www.pblog.online; 
 return 404; # managed by Certbot 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} &lt;/p&gt;

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

 If you look at it closely, now our app is running on port 443 which is `https` connection. So, we have to make sure that port 443 is allowed in our `ec2` instance security groups. If your `ec2` instance security groups does not have `https` port enabled you can add new rule by clicking `add rule` button and select type `HTTPS`. 

 ![security-groups](https://images2.imgbox.com/d2/e0/4FjSUpJB_o.png) 

 Now, visit your site by entering `https://www.YOUR_DOMAIN_NAME`.You will be able to see your site which is now secure with ssl certificate. 

 ![ssl-certifite](https://images2.imgbox.com/5c/20/vBhFpNkr_o.png) 

 This way, you can run your `nodejs` app on `ec2` instance with custom domain name.  
 #### Thank you for reading :)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>React-Redux Essentials: Navigating the Basics of State Management</title>
      <dc:creator>Madhav Pandey</dc:creator>
      <pubDate>Sun, 20 Aug 2023 13:03:23 +0000</pubDate>
      <link>https://dev.to/pmadhav82/react-redux-essentials-navigating-the-basics-of-state-management-4lnd</link>
      <guid>https://dev.to/pmadhav82/react-redux-essentials-navigating-the-basics-of-state-management-4lnd</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---hIjlVsQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://waftengine.org/public/blog/1B5EE4D5D773F8A-RR.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---hIjlVsQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://waftengine.org/public/blog/1B5EE4D5D773F8A-RR.jpg" alt="blog-image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As per official &lt;a href="https://redux-toolkit.js.org/"&gt;redux&lt;/a&gt; website, redux is a pattern and library for managing and updating application state, using events called &lt;code&gt;actions&lt;/code&gt;. Redux serves as a centralized store for state that needs to be used across entire application.&lt;br&gt;
In this blog post we will learn &lt;code&gt;redux&lt;/code&gt; in react by building a &lt;code&gt;Counter App&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To install &lt;code&gt;redux&lt;/code&gt; in your react app run following command.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @reduxjs/toolkit react-redux&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Create redux store
&lt;/h2&gt;

&lt;p&gt;Inside your &lt;code&gt;src&lt;/code&gt; folder create a folder named &lt;code&gt;app&lt;/code&gt; and inside this folder create a file named &lt;code&gt;store.js&lt;/code&gt;. The redux store holds all state of the app in immutable object tree. Now in &lt;code&gt;store.js&lt;/code&gt; file write a following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {configureStore} from "@reduxjs/toolkit";

 const store = configureStore({
reducer:{

}

 })

export default store;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we  have created a &lt;code&gt;store&lt;/code&gt; which is holding empty reducer for now and exported it.&lt;br&gt;
Go to our &lt;code&gt;index.js&lt;/code&gt; file setup our redux store&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import App from './App.jsx'
import store from './app/store.jsx';
import { Provider } from 'react-redux';
ReactDOM.createRoot(document.getElementById('root')).render(
  &amp;lt;React.StrictMode&amp;gt;

&amp;lt;Provider store={store}&amp;gt;

    &amp;lt;App /&amp;gt;

&amp;lt;/Provider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have imported &lt;code&gt;store&lt;/code&gt; and &lt;code&gt;Provider&lt;/code&gt; and we wrapped our &lt;code&gt;&amp;lt;App/&amp;gt;&lt;/code&gt; component inside the provider which holds &lt;code&gt;store&lt;/code&gt;. By doing this our &lt;code&gt;store&lt;/code&gt; will become global state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Slice Reducers and Actions
&lt;/h2&gt;

&lt;p&gt;Let's create a folder &lt;code&gt;features/counter&lt;/code&gt; inside &lt;code&gt;app&lt;/code&gt; where we will have &lt;code&gt;counter slice&lt;/code&gt;. &lt;strong&gt;A slice is a collection of Redux reducer login and actions for a single feature in your app.&lt;/strong&gt;&lt;br&gt;
Now, create a file &lt;code&gt;counterSlice.js&lt;/code&gt; inside &lt;code&gt;counter&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createSlice } from "@reduxjs/toolkit";


const initialState = {
    count :0
}


export const counterSlice = createSlice({
name:"counter",
initialState,
reducers:{
    increment :(state)=&amp;gt;{
        state.count+=1;
    },
    decrement :(state)=&amp;gt;{
        state.count-=1;
    },
    incrementByAmount: (state, action)=&amp;gt;{
        state.count+=action.payload;
    }, 
    decrementByAmount:(state, action)=&amp;gt;{
        state.count-= action.payload
    }
}


})

export const {increment, decrement, incrementByAmount, decrementByAmount}= counterSlice.actions;
export default counterSlice.reducer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createSlice&lt;/code&gt; is a function that accept &lt;strong&gt;slice name, an initial state,  object of reducers function.&lt;/strong&gt; We have created and exported counterSlice plus we have exported &lt;code&gt;increment&lt;/code&gt;, &lt;code&gt;decrement&lt;/code&gt;, &lt;code&gt;incrementByAmount&lt;/code&gt; and &lt;code&gt;decrementByAmount&lt;/code&gt; actions from &lt;code&gt;counterSlice.actions&lt;/code&gt; and all reducer from &lt;code&gt;counterSlice.reducer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We have created a counter slice, it's time to import &lt;code&gt;counterReducer&lt;/code&gt; inside our &lt;code&gt;store.js&lt;/code&gt; file where we had empty reducer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter/counterSlice"
const store = configureStore({
reducer:{
counter:counterReducer
}


});
export default store;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The React Counter Component
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;features/counter&lt;/code&gt; folder create a file name &lt;code&gt;Counter.jsx&lt;/code&gt;. The &lt;code&gt;Counter.jsx&lt;/code&gt; file will have following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Container, Button, Card, InputGroup, Form } from "react-bootstrap";
import { useState } from "react";
import {useSelector, useDispatch} from "react-redux";
import { increment, incrementByAmount, decrement, decrementByAmount } from "./counterSlice";

const Counter = ()=&amp;gt;{
const [addAmount, setAddAmount] = useState("");

const count = useSelector((state)=&amp;gt; state.counter.count);

const dispatch = useDispatch();


const amountHandeler = (e)=&amp;gt;{
    if(!Number(e.target.value)){
        return setAddAmount("");
    }
    setAddAmount(e.target.value);
}

    return&amp;lt;&amp;gt;
    &amp;lt;Container className="d-flex justify-content-center m-2 "&amp;gt;
    &amp;lt;Card className="text-center w-50"&amp;gt;
      &amp;lt;Card.Header&amp;gt;Counter App&amp;lt;/Card.Header&amp;gt;
      &amp;lt;Card.Body&amp;gt;
        &amp;lt;Card.Title&amp;gt;{count}&amp;lt;/Card.Title&amp;gt;
       &amp;lt;div style={{display:"flex", gap:"1rem", justifyContent:"center", alignItems:"center"}}&amp;gt;
        &amp;lt;Button variant="primary" onClick={()=&amp;gt;dispatch(increment())}&amp;gt;+&amp;lt;/Button&amp;gt;
        &amp;lt;Button variant="secondary" onClick={()=&amp;gt;dispatch(decrement())}&amp;gt;-&amp;lt;/Button&amp;gt;
       &amp;lt;/div&amp;gt;

       &amp;lt;InputGroup className="m-2"&amp;gt;
        &amp;lt;Form.Control
        onChange={amountHandeler}
         value={addAmount}
        /&amp;gt;
        &amp;lt;Button variant="primary" id="button-addon2" onClick={()=&amp;gt;dispatch(incrementByAmount(Number(addAmount)||0))}&amp;gt;
        Add Amount
        &amp;lt;/Button&amp;gt;
        &amp;lt;Button variant="secondary" id="button-addon2" onClick={()=&amp;gt; dispatch(decrementByAmount(Number(addAmount)||0))}&amp;gt;
        Subtract Amount
        &amp;lt;/Button&amp;gt;
      &amp;lt;/InputGroup&amp;gt;



      &amp;lt;/Card.Body&amp;gt;

    &amp;lt;/Card&amp;gt;

    &amp;lt;/Container&amp;gt;
    &amp;lt;/&amp;gt;
}

export default Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hKy4i3Xg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/20/63/Wyms0ZnG_o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hKy4i3Xg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images2.imgbox.com/20/63/Wyms0ZnG_o.png" alt="preview" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we have imported &lt;code&gt;useSelector&lt;/code&gt; and &lt;code&gt;useDispatch&lt;/code&gt; from &lt;code&gt;react-redux&lt;/code&gt; plus we have imported all &lt;code&gt;actions&lt;/code&gt; from &lt;code&gt;counterSlice.js&lt;/code&gt; file. We accessed the current count value with the help of &lt;code&gt;useSelector&lt;/code&gt;. We have dispatched action with the help of &lt;code&gt;useDispatch&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const count = useSelector((state)=&amp;gt; state.counter.count);
const dispatch = useDispatch();

&amp;lt;Button variant="primary" onClick={()=&amp;gt;dispatch(increment())}&amp;gt;+&amp;lt;/Button&amp;gt;
        &amp;lt;Button variant="secondary" onClick={()=&amp;gt;dispatch(decrement())}&amp;gt;-&amp;lt;/Button&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We have learned a basic about react-redux and redux toolkit. I would like to mentioned some key notes from official &lt;a href="https://redux-toolkit.js.org/"&gt;redux&lt;/a&gt; website  at the end.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;configureStore&lt;/code&gt; accepts a &lt;code&gt;reducer&lt;/code&gt; function as a named argument&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;configureStore&lt;/code&gt; automatically sets up the store with good default settings&lt;/li&gt;
&lt;li&gt;A "slice" contains the reducer logic and actions related to a specific feature / section of the Redux state&lt;/li&gt;
&lt;li&gt;Redux Toolkit's &lt;code&gt;createSlice&lt;/code&gt; API generates action creators and action types for each individual reducer function you provide&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;dispatch(someActionCreator())&lt;/code&gt; in a component to dispatch an action&lt;/li&gt;
&lt;li&gt;Wrapping the app with &lt;code&gt;&amp;lt;Provider store={store}&amp;gt;&lt;/code&gt; enables all components to use the store
-Global state should go in the Redux store, local state should stay in React components&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
