<?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>Forem: Damir Mahamedov</title>
    <description>The latest articles on Forem by Damir Mahamedov (@damir_maham).</description>
    <link>https://forem.com/damir_maham</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%2F2145472%2F2a04c70c-d152-4648-81f0-ff5abaa1ee99.jpeg</url>
      <title>Forem: Damir Mahamedov</title>
      <link>https://forem.com/damir_maham</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/damir_maham"/>
    <language>en</language>
    <item>
      <title>Setting Up PostgreSQL in Docker on Hetzner with SSL Certificates from Certbot</title>
      <dc:creator>Damir Mahamedov</dc:creator>
      <pubDate>Mon, 29 Sep 2025 18:40:47 +0000</pubDate>
      <link>https://forem.com/damir_maham/setting-up-postgresql-in-docker-on-hetzner-with-ssl-certificates-from-certbot-3le3</link>
      <guid>https://forem.com/damir_maham/setting-up-postgresql-in-docker-on-hetzner-with-ssl-certificates-from-certbot-3le3</guid>
      <description>&lt;p&gt;Securing a PostgreSQL instance with SSL is essential for protecting your data, especially when deploying on cloud platforms like Hetzner. In this guide, we’ll set up PostgreSQL in Docker, configure SSL using Certbot certificates, and automate certificate renewal using a cron job to ensure minimal downtime for your database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding, ensure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Hetzner cloud server running a Linux distribution (e.g., Ubuntu).&lt;/li&gt;
&lt;li&gt;Docker and Docker Compose installed on your server.&lt;/li&gt;
&lt;li&gt;A domain name (e.g., db.example.com) pointing to your Hetzner server.&lt;/li&gt;
&lt;li&gt;Certbot installed for generating SSL certificates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hetzner Firewall Configuration
&lt;/h2&gt;

&lt;p&gt;To ensure your server is accessible and secure, you need to configure the Hetzner firewall to open the necessary inbound ports. Here’s what you need to allow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Port 80 (HTTP)&lt;/strong&gt;: Required for Certbot to verify your domain when issuing SSL certificates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 22 (SSH)&lt;/strong&gt;: Required for SSH access to your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 5432 (PostgreSQL)&lt;/strong&gt;: Required for PostgreSQL clients to connect to the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You also need to allow &lt;strong&gt;TCP outbound traffic&lt;/strong&gt; on any port, which is necessary for network connectivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Hetzner Firewall
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your Hetzner Cloud Console.&lt;/li&gt;
&lt;li&gt;Go to the Firewall section and create a new firewall.&lt;/li&gt;
&lt;li&gt;Add the following &lt;strong&gt;inbound rules&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP (Port 80)&lt;/strong&gt;: To allow Certbot to perform domain validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH (Port 22)&lt;/strong&gt;: To allow SSH access to your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL (Port 5432)&lt;/strong&gt;: To allow connections to your PostgreSQL database (from your IP or trusted IPs).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add the following outbound rule:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TCP (any port)&lt;/strong&gt;: To allow your server to communicate freely over the internet, which is needed for Certbot renewal and other outbound communications.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Assign the firewall to your server.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Use SSL with PostgreSQL?
&lt;/h2&gt;

&lt;p&gt;SSL (Secure Sockets Layer) ensures that data transmitted between PostgreSQL and its clients is encrypted. It protects against potential man-in-the-middle attacks and data theft by encrypting the communication channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Docker and Docker Compose
&lt;/h2&gt;

&lt;p&gt;If Docker and Docker Compose aren’t already installed on your Hetzner server, follow these steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Docker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SSH into your Hetzner server.&lt;/li&gt;
&lt;li&gt;Install Docker:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker.io &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Enable Docker to start on boot and verify the installation:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing Docker Compose
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install Docker Compose:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker-compose &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Install Certbot and Generate SSL Certificates
&lt;/h2&gt;

&lt;p&gt;Certbot simplifies generating SSL certificates from Let’s Encrypt. Here’s how to install and generate SSL certificates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Certbot:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate SSL certificates for your domain:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certonly &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; db.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The certificates will be saved in /etc/letsencrypt/live/db.example.com/. The important files for PostgreSQL are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fullchain.pem&lt;/code&gt; (public certificate)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;privkey.pem&lt;/code&gt; (private key)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Automate Certificate Renewal Using Cron
&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt certificates expire every 90 days, so it’s crucial to set up automated certificate renewal. Certbot can handle this automatically using cron, ensuring that your SSL certificates are renewed without manual intervention.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the cron file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add the following cron job to renew the certificates and restart the PostgreSQL container:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/bin/certbot renew &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; /path/to/your/docker-compose.yml restart postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0 3 * * *&lt;/code&gt;: This cron job will run every day at 3:00 AM. Adjust the time as needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/usr/bin/certbot renew --quiet&lt;/code&gt;: This runs Certbot’s renewal process in quiet mode, meaning it will renew certificates only if they are about to expire (i.e., within 30 days of expiration).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose -f /path/to/your/docker-compose.yml restart postgres&lt;/code&gt;: After renewal, this command restarts the PostgreSQL container to apply the new SSL certificates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save and exit the cron file. Now, Certbot will automatically renew the certificates, and PostgreSQL will restart to load the new certificates with no downtime for your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure Docker Compose for PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Now we’ll configure PostgreSQL using Docker Compose, mounting SSL certificates, and setting up environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  The docker-compose.yml File
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file to define the PostgreSQL service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres_db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=$DB_USERNAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=$DB_PASSWORD&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=$DB_NAME&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pg_data:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pg_hba.conf:/var/lib/postgresql/data/pg_hba.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/db.example.com/fullchain.pem:/var/lib/postgresql/fullchain.pem&lt;/span&gt;
        &lt;span class="s"&gt;- /etc/letsencrypt/live/db.example.com/privkey.pem:/var/lib/postgresql/privkey.pem&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-net&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;postgres -c shared_buffers=2GB&lt;/span&gt;
               &lt;span class="s"&gt;-c work_mem=16MB&lt;/span&gt;
               &lt;span class="s"&gt;-c maintenance_work_mem=256MB&lt;/span&gt;
               &lt;span class="s"&gt;-c effective_cache_size=6GB&lt;/span&gt;
               &lt;span class="s"&gt;-c ssl=on&lt;/span&gt;
               &lt;span class="s"&gt;-c ssl_cert_file='/var/lib/postgresql/fullchain.pem'&lt;/span&gt;
               &lt;span class="s"&gt;-c ssl_key_file='/var/lib/postgresql/privkey.pem'&lt;/span&gt;
               &lt;span class="s"&gt;-c listen_addresses='*'&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pg_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation of Memory and Performance Options
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-c shared_buffers=2GB&lt;/code&gt;&lt;br&gt;
The shared_buffers setting determines how much memory PostgreSQL will use to cache frequently accessed data. Setting this higher can significantly improve performance by reducing disk I/O since the data will be stored in memory instead of being read from disk repeatedly.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Default&lt;/strong&gt;: The default value is usually very low, often 128MB or 256MB, which is insufficient for production systems.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Recommended Setting&lt;/strong&gt;: A general recommendation is to allocate about 25–40% of the total available RAM to shared_buffers. In this example, 2GB has been allocated, which is suitable for systems with at least 8GB of RAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-c work_mem=16MB&lt;/code&gt;&lt;br&gt;
The work_mem setting specifies the amount of memory allocated for each query operation that requires memory (e.g., sorting, hash tables). Each connection and each query can use its own chunk of work_mem.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Default&lt;/strong&gt;: Typically around 4MB.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Recommended Setting&lt;/strong&gt;: Increasing this to 16MB allows queries that involve sorting or creating hash tables to perform faster by reducing the need to write temporary data to disk.&lt;br&gt;&lt;br&gt;
Note: Be cautious with this setting, as high values combined with many concurrent users can lead to excessive memory usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-c maintenance_work_mem=256MB&lt;/code&gt;&lt;br&gt;
The maintenance_work_mem setting controls the memory used for maintenance tasks such as VACUUM, CREATE INDEX, and ALTER TABLE. These operations can be memory-intensive, and allocating more memory here can speed up these operations.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Default&lt;/strong&gt;: Often set around 64MB.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Recommended Setting&lt;/strong&gt;: For production environments, especially those with large tables, increasing this to 256MB helps PostgreSQL complete maintenance tasks faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-c effective_cache_size=6GB&lt;/code&gt;&lt;br&gt;
The effective_cache_size parameter is a guideline that PostgreSQL uses to estimate the amount of memory available for disk caching by the operating system. This setting doesn’t allocate memory but helps PostgreSQL make decisions about which queries to optimize and which indexes to use.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Default&lt;/strong&gt;: Often set to 4GB.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Recommended Setting&lt;/strong&gt;: The value should generally be about 50–75% of the system’s total memory. In this case, with 6GB, PostgreSQL will assume there is enough memory to cache large chunks of data, leading to more efficient queries.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The .env File
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the same directory as your docker-compose.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DB_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_db_user
&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_secure_password
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_database_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Configure &lt;code&gt;pg_hba.conf&lt;/code&gt; to Restrict Access by IP Address
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;pg_hba.conf&lt;/code&gt; file is used to control access to PostgreSQL. In this step, we’ll configure it to only allow connections from a specific IP address (99.99.99.99) using SSL and block all other IP addresses.&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;pg_hba.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow only the specific IP address 99.99.99.99 to connect via SSL&lt;/span&gt;
hostssl    all             all             99.99.99.99/32      scram-sha-256

&lt;span class="c"&gt;# Block all other IP addresses&lt;/span&gt;
hostssl    all             all             0.0.0.0/0           reject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hostssl&lt;/code&gt;: Enforces SSL for all connections.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;99.99.99.99/32&lt;/code&gt;: Allows only connections from the IP address 99.99.99.99.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scram-sha-256&lt;/code&gt;: Uses SCRAM-SHA-256 authentication for secure password hashing. Please don’t use md5.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0.0.0.0/0&lt;/code&gt;: Blocks all other IP addresses from accessing the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Adjust File Permissions for SSL Certificates
&lt;/h2&gt;

&lt;p&gt;PostgreSQL requires strict permissions on the private key file (&lt;code&gt;privkey.pem&lt;/code&gt;). It must be owned by the postgres user inside the Docker container, and the permissions must be set to 0600 (read/write only for the owner).&lt;/p&gt;

&lt;p&gt;Set the appropriate permissions on the host machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;999:999 /etc/letsencrypt/live/db.goachievo.com/privkey.pem
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/letsencrypt/live/db.goachievo.com/privkey.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL also needs read access to the public certificate file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;999:999 /etc/letsencrypt/live/db.goachievo.com/fullchain.pem
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;644 /etc/letsencrypt/live/db.goachievo.com/fullchain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why 999?&lt;/strong&gt;&lt;br&gt;
Inside the Docker container, the postgres user typically has the UID and GID of 999. This ensures that PostgreSQL can read the private key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Start PostgreSQL
&lt;/h2&gt;

&lt;p&gt;With everything set up, start the PostgreSQL container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker will pull the PostgreSQL image, create the necessary volumes, and start the database with SSL enabled and access restricted to the specified IP address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Verify SSL and Connection Restrictions
&lt;/h2&gt;

&lt;p&gt;Once PostgreSQL is running, verify that SSL is enabled and that the connection restrictions are in place.&lt;/p&gt;

&lt;p&gt;Connect to the PostgreSQL instance and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SHOW ssl&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should return &lt;code&gt;on&lt;/code&gt;, indicating that SSL is enabled.&lt;/p&gt;

&lt;p&gt;Check the PostgreSQL logs to verify SSL connections and confirm that only the allowed IP address is connecting:&lt;br&gt;
  docker logs postgres_db&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test the connection from the allowed IP (99.99.99.99) and ensure that connections from other IP addresses are blocked.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In this article, we’ve successfully set up PostgreSQL in Docker with SSL certificates from Certbot, restricted access to a specific IP address using pg_hba.conf, and ensured secure encrypted connections. By using cron for automated certificate renewal, you’ve also ensured that your SSL certificates remain up to date with minimal manual intervention.&lt;/p&gt;




&lt;p&gt;Feel free to share your thoughts and improvements in the comments section below!&lt;/p&gt;

&lt;p&gt;If you enjoyed this article or found these tools useful, make sure to follow me on &lt;a href="https://medium.com/@damir_maham" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; for more insights and tips on coding and development. I regularly share helpful content to make your coding journey smoother.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://x.com/damir_maham" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;, where I share more interesting thoughts, updates, and discussions about programming and tech! Don’t miss out — click those follow buttons.&lt;/p&gt;

&lt;p&gt;You can also follow me on &lt;a href="https://www.linkedin.com/in/mahamedov-damir" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for professional insights, updates on my latest projects, and discussions about coding, tech trends, and more. Don’t miss out on valuable content that can help you level up your development skills — let’s connect!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>docker</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>5 Tools to Turn Your Code into Beautiful Visuals for Sharing</title>
      <dc:creator>Damir Mahamedov</dc:creator>
      <pubDate>Mon, 07 Oct 2024 08:05:24 +0000</pubDate>
      <link>https://forem.com/damir_maham/5-tools-to-turn-your-code-into-beautiful-visuals-for-sharing-cpb</link>
      <guid>https://forem.com/damir_maham/5-tools-to-turn-your-code-into-beautiful-visuals-for-sharing-cpb</guid>
      <description>&lt;p&gt;As developers, we often need to share code — whether it’s to help others learn, demonstrate our projects, or simply show off a solution we’re proud of. However, raw code doesn’t always look great in a tweet or blog post. Thankfully, there are some fantastic tools that can help turn our code into visually appealing images that are easier to digest and share.&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk you through 5 powerful tools that allow you to create professional-looking code snippets for sharing on social media or in presentations.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Carbon
&lt;/h3&gt;

&lt;p&gt;Website: &lt;a href="https://carbon.now.sh/" rel="noopener noreferrer"&gt;carbon.now.sh&lt;/a&gt;&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F446zueaa13yovrq8wjri.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%2F446zueaa13yovrq8wjri.png" alt="Screenshot with [carbon.now.sh](https://carbon.now.sh/)" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Great&lt;/strong&gt;: Carbon is one of the most popular tools for creating and sharing beautiful code snippets. It’s widely used by developers on platforms like X (formerly Twitter) and GitHub due to its flexibility and range of customization options.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Supports multiple languages, including JavaScript, Python, and Go.&lt;/li&gt;
&lt;li&gt;Dozens of customizable themes.&lt;/li&gt;
&lt;li&gt;Ability to adjust window style, font, and background color.&lt;/li&gt;
&lt;li&gt;Easily export images as PNG or SVG files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Use It&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Carbon and paste your code.&lt;/li&gt;
&lt;li&gt;Customize the appearance (themes, background, window controls).&lt;/li&gt;
&lt;li&gt;Export your image and share it on your platform of choice.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Try the “Dracula” or “Solarized Dark” theme for a modern, eye-catching look!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ray.so
&lt;/h3&gt;

&lt;p&gt;Website: &lt;a href="https://ray.so/" rel="noopener noreferrer"&gt;ray.so&lt;/a&gt;&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqrxwvk0iw3e5a0yy4on.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%2Fyqrxwvk0iw3e5a0yy4on.png" alt="Screenshot with [ray.so](https://ray.so/)" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Great&lt;/strong&gt;: Ray.so is perfect for developers who want to create dark mode code snippets that stand out. It’s simple to use and offers a sleek design with minimal configuration. This is a great tool if you need to create a quick, clean visual without too many customizations.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Dark mode by default.&lt;/li&gt;
&lt;li&gt;Simple and intuitive UI.&lt;/li&gt;
&lt;li&gt;Options for adjusting the padding and text size.&lt;/li&gt;
&lt;li&gt;Exports in high-quality PNG format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Use It&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://ray.so/" rel="noopener noreferrer"&gt;Ray.so&lt;/a&gt;, paste your code, and select your preferred settings.&lt;/li&gt;
&lt;li&gt;Adjust the padding and color if needed.&lt;/li&gt;
&lt;li&gt;Download your snippet and you’re ready to share!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Snappify
&lt;/h3&gt;

&lt;p&gt;Website: &lt;a href="https://snappify.com/" rel="noopener noreferrer"&gt;snappify.io&lt;/a&gt;&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm51guzm59kz4msopoorh.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%2Fm51guzm59kz4msopoorh.png" alt="Screenshot with [snappify.io](https://snappify.com/)" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Great&lt;/strong&gt;: Snappify stands out because of its extensive customization options. Beyond just changing the background color or font, Snappify allows you to adjust the layout, add annotations, and even create multi-snippet visuals. It’s perfect for educational content, tutorials, or explaining complex ideas.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Full control over layout and design.&lt;/li&gt;
&lt;li&gt;Ability to add annotations, comments, or diagrams.&lt;/li&gt;
&lt;li&gt;Drag-and-drop support for arranging snippets.&lt;/li&gt;
&lt;li&gt;Multiple output formats including PNG and SVG.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Use It&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Snappify, paste your code, and use the layout editor to organize it.&lt;/li&gt;
&lt;li&gt;Add annotations if needed (great for explaining the code).&lt;/li&gt;
&lt;li&gt;Export your snippet and post it wherever you need.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Snappify is great for creating infographics or X (Twitter) threads that require more explanation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Polacode (VS Code Extension)
&lt;/h3&gt;

&lt;p&gt;Website: &lt;a href="https://marketplace.visualstudio.com/items?itemName=pnp.polacode" rel="noopener noreferrer"&gt;Polacode&lt;/a&gt;&lt;/p&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%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dpnp.polacode" 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%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dpnp.polacode" alt="Screenshot with Polacode" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Great&lt;/strong&gt;: Polacode is perfect for VS Code users who want to quickly turn their code into an image without leaving the editor. It integrates directly with VS Code, making it highly convenient if you’re already working on a project and want to share snippets without switching tools.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Direct integration into VS Code.&lt;/li&gt;
&lt;li&gt;Supports all languages that VS Code supports.&lt;/li&gt;
&lt;li&gt;Easily customizable (theme, font, and layout).&lt;/li&gt;
&lt;li&gt;Exports to PNG.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Use It&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Polacode from the VS Code Marketplace.&lt;/li&gt;
&lt;li&gt;Highlight the code you want to capture and open Polacode.&lt;/li&gt;
&lt;li&gt;Customize the appearance, then export your image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;: Since Polacode uses your current VS Code theme, make sure you’re using a visually appealing theme like “Monokai” or “One Dark Pro” for polished results.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Codeimg.io
&lt;/h3&gt;

&lt;p&gt;Website: &lt;a href="https://codeimg.io/" rel="noopener noreferrer"&gt;codeimg.io&lt;/a&gt;&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2curs53hwsh74s0qusq.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%2Fc2curs53hwsh74s0qusq.png" alt="Screenshot with [codeimg.io](https://codeimg.io/)" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It’s Great&lt;/strong&gt;: Codeimg.io is a simple, no-frills tool for creating quick code snippets. It’s great if you need a basic visual without too many customization options. The interface is easy to use, and you can go from raw code to an image in seconds.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Support for multiple themes and languages.&lt;/li&gt;
&lt;li&gt;Simple UI for fast snippet creation.&lt;/li&gt;
&lt;li&gt;Export to PNG, JPEG, or SVG.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Use It&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Codeimg.io, paste your code, and select a theme.&lt;/li&gt;
&lt;li&gt;Adjust the font size, color, and padding.&lt;/li&gt;
&lt;li&gt;Download your snippet and you’re good to go.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Whether you’re looking to share a quick snippet on Twitter, showcase a new feature, or teach a concept, using one of these tools will help make your code more engaging and easier to understand. Each tool has its own unique strengths, so choose the one that fits your style and workflow the best.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What’s Your Favorite Tool&lt;/strong&gt;? If you have a favorite tool that I didn’t mention, let me know in the comments! I’d love to hear how you share your code with the world.&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts and improvements in the comments section below!&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay Connected for More Insights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://medium.com/@damir_maham" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;&lt;/strong&gt; for coding tips and insights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://x.com/damir_maham" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;&lt;/strong&gt; for programming thoughts and tech updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/mahamedov-damir/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt; for professional insights and project updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.instagram.com/damir_maham/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;/strong&gt; for behind-the-scenes looks at my coding journey.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tooling</category>
      <category>programming</category>
    </item>
    <item>
      <title>Streamline File Uploads in NestJS: Efficient In-Memory Parsing for CSV &amp; XLSX Without Disk Storage</title>
      <dc:creator>Damir Mahamedov</dc:creator>
      <pubDate>Mon, 30 Sep 2024 16:56:54 +0000</pubDate>
      <link>https://forem.com/damir_maham/streamline-file-uploads-in-nestjs-efficient-in-memory-parsing-for-csv-xlsx-without-disk-storage-145g</link>
      <guid>https://forem.com/damir_maham/streamline-file-uploads-in-nestjs-efficient-in-memory-parsing-for-csv-xlsx-without-disk-storage-145g</guid>
      <description>&lt;p&gt;&lt;em&gt;Effortless File Parsing in NestJS: Manage CSV and XLSX Uploads in Memory for Speed, Security, and Scalability&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Handling file uploads in a web application is a common task, but dealing with different file types and ensuring they are processed correctly can be challenging. Often, developers need to parse uploaded files without saving them to the server, which is especially important for reducing server storage costs and ensuring that sensitive data is not unnecessarily retained. In this article, we’ll walk through the process of creating a custom NestJS module to handle file uploads specifically for CSV and XLS/XLSX files, and we’ll parse these files in memory using Node.js streams, so no static files are created on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NestJS?
&lt;/h2&gt;

&lt;p&gt;NestJS is a progressive Node.js framework that leverages TypeScript and provides an out-of-the-box application architecture that enables you to build highly testable, scalable, loosely coupled, and easily maintainable applications. By using NestJS, we can take advantage of its modular structure, powerful dependency injection system, and extensive ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;Before we dive into the code, let’s set up a new NestJS project. If you haven’t already, install the NestJS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @nestjs/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new NestJS project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nest new your-super-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate into the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your-super-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Installing Required Packages
&lt;/h2&gt;

&lt;p&gt;We’ll need to install some additional packages to handle file uploads and parsing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @nestjs/platform-express multer exceljsfile-type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multer&lt;/strong&gt;: A middleware for handling multipart/form-data, which is primarily used for uploading files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exlesjs&lt;/strong&gt;: A powerful library for parsing CSV/XLS/XLSX files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File-Type&lt;/strong&gt;: A library for detecting the file type of a stream or buffer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Creating the Multer Storage Engine Without Saving Files
&lt;/h2&gt;

&lt;p&gt;To customize the file upload process, we’ll create a custom Multer storage engine. This engine will ensure that only CSV and XLS/XLSX files are accepted, parse them in memory using Node.js streams, and return the parsed data without saving any files to disk.&lt;/p&gt;

&lt;p&gt;Create a new file for our engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PassThrough&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fileType&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BadRequestException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Workbook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exceljs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createParserCsvOrXlsx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./parser-factory.js&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;ALLOWED_MIME_TYPES&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="s1"&gt;text/csv&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="s1"&gt;application/vnd.ms-excel&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="s1"&gt;text/comma-separated-values&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="s1"&gt;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&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="s1"&gt;application/vnd.ms-excel&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;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CsvOrXlsxMulterEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;destKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;maxFileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;destKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;maxFileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&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="nx"&gt;destKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destKey&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="nx"&gt;maxFileSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxFileSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;_handleFile&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;Request&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="kr"&gt;any&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="kr"&gt;any&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;contentLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-length&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;contentLength&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;contentLength&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxFileSize&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&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="s2"&gt;`Max file size is &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="nx"&gt;maxFileSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes.`&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;fileStream&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;fileType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileTypeStream&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;stream&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;mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileType&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt; &lt;span class="o"&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="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;ALLOWED_MIME_TYPES&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;mime&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BadRequestException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File must be *.csv or *.xlsx&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replacementStream&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;PassThrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replacementStream&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;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createParserCsvOrXlsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mime&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;data&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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replacementStream&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="p"&gt;{&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="nx"&gt;destKey&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
          &lt;span class="nx"&gt;mime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getWorksheet&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;error&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="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="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;_removeFile&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;Request&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="kr"&gt;any&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="kr"&gt;any&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="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 custom storage engine checks the file’s MIME type and ensures it’s either a CSV or XLS/XLSX file. It then processes the file entirely in memory using Node.js streams, so no temporary files are created on the server. This approach is both efficient and secure, especially when dealing with sensitive data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Creating the Parser Factory
&lt;/h2&gt;

&lt;p&gt;The parser factory is responsible for determining the appropriate parser based on the file type.&lt;/p&gt;

&lt;p&gt;Create a new file for our parser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;excel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exceljs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createParserCsvOrXlsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;workbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&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="s1"&gt;application/vnd.ms-excel&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;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xlsx&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csv&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 factory function checks the MIME type and returns the appropriate parser (either xlsx or csv).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Configuring Multer in the NestJS Controller
&lt;/h2&gt;

&lt;p&gt;Next, let’s create a controller to handle file uploads using our custom storage engine.&lt;/p&gt;

&lt;p&gt;Generate a new controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nest g controller files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the files.controller.ts, configure the file upload using Multer and the custom storage engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Controller&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;UploadedFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UseInterceptors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FileInterceptor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/platform-express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worksheet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exceljs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CsvOrXlsxMulterEngine&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../shared/multer-engines/csv-xlsx/engine.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FilesService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./files.service.js&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;MAX_FILE_SIZE_IN_MiB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Only for test&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilesController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;filesService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FilesService&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="nd"&gt;UseInterceptors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;FileInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&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;storage&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;CsvOrXlsxMulterEngine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;maxFileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_IN_MiB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;destKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worksheet&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="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;UploadedFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;worksheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Worksheet&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worksheet&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 controller sets up an endpoint to handle file uploads. The uploaded file is processed by the CsvOrXlsxMulterEngine, and the parsed data is returned in the response without ever being saved to disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Setting Up the Module
&lt;/h2&gt;

&lt;p&gt;Finally, we need to set up a module to include our controller.&lt;/p&gt;

&lt;p&gt;Generate a new module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nest g module files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the files.module.ts, import the controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FilesController&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./files.controller.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FilesService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./files.service.js&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FilesService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FilesController&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilesModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to import this module into your AppModule:&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Testing the File Upload with HTML
&lt;/h2&gt;

&lt;p&gt;To test the file upload functionality, we can create a simple HTML page that allows users to upload CSV or XLS/XLSX files. This page will send the file to our /api/files endpoint, where it will be parsed and processed in memory.&lt;/p&gt;

&lt;p&gt;Here’s the basic HTML file for testing the file upload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;File Upload&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Upload a File (CSV or XLSX)&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&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;"/api/files"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Choose file:&lt;span class="nt"&gt;&amp;lt;/label&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;"file"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;".csv, .xlsx"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&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;Upload&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;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To render the HTML page for file uploads, we first need to install an additional NestJS module called @nestjs/serve-static. You can do this by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @nestjs/serve-static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing, we need to configure this module in AppModule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ServeStaticModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/serve-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FilesModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./modules/files/files.module.js&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;FilesModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ServeStaticModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;rootPath&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&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="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;serveRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup will allow us to serve static files from the public directory. Now, we can open the file upload page by navigating to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser.&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjyds03tcrlybw254cdj.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%2Fgjyds03tcrlybw254cdj.png" alt="Upload Your File"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upload Your File&lt;/p&gt;

&lt;p&gt;To upload a file, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose a file by clicking on the ‘Choose file’ button.&lt;/li&gt;
&lt;li&gt;Click on the ‘Upload’ button to start the upload process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the file is uploaded successfully, you should see a confirmation that the file has been uploaded and formatted.&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs11df4majcqsjbrzdktr.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%2Fs11df4majcqsjbrzdktr.png" alt="File Upload Result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: I haven’t included code for formatting the uploaded file, as this depends on the library you choose for processing CSV or XLS/XLSX files. You can view the complete implementation on &lt;a href="https://github.com/Magam9/article-multer-engine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
Comparing Pros and Cons of In-Memory File Processing&lt;br&gt;
When deciding whether to use in-memory file processing or saving files to disk, it’s important to understand the trade-offs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros of In-Memory Processing:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No Temporary Files on Disk&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security: Sensitive data isn’t left on the server’s disk, reducing the risk of data leaks.&lt;/li&gt;
&lt;li&gt;Resource Efficiency: The server doesn’t need to allocate disk space for temporary files, which can be particularly useful in environments with limited storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Faster Processing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: Parsing files in memory can be faster since it eliminates the overhead of writing and reading files from disk.&lt;/li&gt;
&lt;li&gt;Reduced I/O Operations: Fewer disk I/O operations means lower latency and potent
ially higher throughput for file processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Simplified Cleanup&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Cleanup Required: Since files aren’t saved to disk, there’s no need to manage or clean up temporary files, simplifying the codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cons of In-Memory Processing:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High Memory Consumption: Large files can consume significant amounts of memory, which might lead to out-of-memory errors if the server doesn’t have enough resources.&lt;/li&gt;
&lt;li&gt;Scalability: Handling large files or multiple file uploads simultaneously may require careful memory management and scaling strategies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;File Size Limitations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited by Memory: The maximum file size that can be processed is limited by the available memory on the server. This can be a signific
ant drawback for applications dealing with very large files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Complexity in Error Handling&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error Management: Managing errors in streaming data can be more complex than handling files on disk, especially in cases where partial data might need to be recovered or analyzed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use In-Memory Processing:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Small to Medium Files&lt;/strong&gt;: If your application deals with relatively small files, in-memory processing can offer speed and simplicity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security-Sensitive Applications&lt;/strong&gt;: When handling sensitive data that shouldn’t be stored on disk, in-memory processing can reduce the risk of data breaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-Performance Scenarios&lt;/strong&gt;: Applications that require high throughput and minimal latency may benefit from the reduced overhead of in-memory processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Consider Disk-Based Processing:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Large Files&lt;/strong&gt;: If your application needs to process very large files, disk-based processing may be necessary to avoid running out of memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource-Constrained Environments&lt;/strong&gt;: In cases where server memory is limited, processing files on disk can prevent memory exhaustion and allow for better resource management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent Storage Needs&lt;/strong&gt;: If you need to retain a copy of the uploaded file for auditing, backup, or later retrieval, saving files to disk is necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration with External Storage Services&lt;/strong&gt;: For large files, consider uploading them to external storage services like AWS S3, Google Cloud &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage, or Azure Blob Storage. These services allow you to offload storage from your server, and you can process the files in the cloud or retrieve them for in-memory processing as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Cloud storage solutions can handle massive files and provide redundancy, ensuring that your data is safe and easily accessible from multiple geographic locations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost Efficiency&lt;/strong&gt;: Using cloud storage can be more cost-effective for handling large files, as it reduces the need for local server resources and provides pay-as-you-go pricing.&lt;/p&gt;

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

&lt;p&gt;In this article, we’ve created a custom file upload module in NestJS that handles CSV and XLS/XLSX files, parses them in memory, and returns the parsed data without saving any files to disk. This approach leverages the power of Node.js streams, making it both efficient and secure, as no temporary files are left on the server.&lt;/p&gt;

&lt;p&gt;We’ve also explored the pros and cons of in-memory file processing versus saving files to disk. While in-memory processing offers speed, security, and simplicity, it’s important to consider the memory usage and potential file size limitations before adopting this approach.&lt;/p&gt;

&lt;p&gt;Whether you’re building an enterprise application or a small project, handling file uploads and parsing correctly is crucial. With this setup, you’re well on your way to mastering file uploads in NestJS without worrying about unnecessary server storage or data security issues.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to share your thoughts and improvements in the comments section below!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this article or found these tools useful, make sure to follow me on &lt;a href="https://dev.to/damir_maham"&gt;Dev.to&lt;/a&gt; for more insights and tips on coding and development. I regularly share helpful content to make your coding journey smoother.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://x.com/damir_maham" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;, where I share more interesting thoughts, updates, and discussions about programming and tech! Don't miss out - click those follow buttons.&lt;/p&gt;

&lt;p&gt;You can also follow me on &lt;a href="https://www.linkedin.com/in/mahamedov-damir/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for professional insights, updates on my latest projects, and discussions about coding, tech trends, and more. Don't miss out on valuable content that can help you level up your development skills - let's connect!&lt;/p&gt;

</description>
      <category>node</category>
      <category>programming</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
