<?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: Aqib Shoaib</title>
    <description>The latest articles on DEV Community by Aqib Shoaib (@aqib_shoaib_460d14658d79a).</description>
    <link>https://dev.to/aqib_shoaib_460d14658d79a</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%2F1757528%2Fd6113b20-152d-4b38-8806-a30592767abb.jpg</url>
      <title>DEV Community: Aqib Shoaib</title>
      <link>https://dev.to/aqib_shoaib_460d14658d79a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aqib_shoaib_460d14658d79a"/>
    <language>en</language>
    <item>
      <title>How I Survived Building Auto-Translate in Strapi v5 (and the Loops That Haunted Me)</title>
      <dc:creator>Aqib Shoaib</dc:creator>
      <pubDate>Sat, 30 Aug 2025 10:50:46 +0000</pubDate>
      <link>https://dev.to/aqib_shoaib_460d14658d79a/how-i-survived-building-auto-translate-in-strapi-v5-and-the-loops-that-haunted-me-46c2</link>
      <guid>https://dev.to/aqib_shoaib_460d14658d79a/how-i-survived-building-auto-translate-in-strapi-v5-and-the-loops-that-haunted-me-46c2</guid>
      <description>&lt;h2&gt;
  
  
  The Introduction
&lt;/h2&gt;

&lt;p&gt;Recently I have been working on strapi v5, this time I was given a challenging task to implement a feature that does not have any code examples yet. I had to figure it all out myself. At the time I was given this task, I did know about some basics of strapi infrastructure but the breaking changes between strapi v4 and v5 continuously haunted me throughout the journey. There were so many options and I didn’t know which one to choose, the decision between choosing entity service, document service of strapi etc. But there was one main decision that I took which I took at the last moment and it made me think about all the frustration I had faced. Here is a little story of my journey on this. I hope someone finds it helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The challenging task was to implement the auto translate feature in strapi. This feature was supposed to work on all localized collection types in strapi, including all relations, images, components, dynamic zones being used in that collection. The purpose of this feature was to make user life easier. The aim was to take user input about a collection of data in one language and auto translate it behind the scenes. Now this process had to be so perfect because if it failed, it could easily turn the website into a useless website.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Approach
&lt;/h2&gt;

&lt;p&gt;I started with checking on how I can intercept user input and use it to create other instances in other languages as well. I chose lifecycle hooks, a choice that turned out to be too poor 😂. Honestly this was not a choice at all. At the time I chose this option, I didn’t even know there were other ways to intercept user input. Anyway I started working on it, of course I had the help of various AI tools like Grok and ChatGpt, but they were not as helpful as I was expecting. In the beginning I relied too much on these AI tools. And because of this, for the first 2 days, I was just seeing one error after another. &lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Experience
&lt;/h2&gt;

&lt;p&gt;Within these two days, I realized that the AI tools didn’t have enough information on this topic and they were just guessing their way around. So I decided to unlock my own potential 🙂. These two days made me realize that AI tools are not that good for my type of work, at least in my current scenario where things like documentation, user blogs are not enough, because that is the primary source of information for these AI tools. I’m not saying these AI tools were totally useless, they were good enough to provide me with a starting boiler plate and some sense on how I can proceed with this. &lt;/p&gt;

&lt;p&gt;Now that I had started this, I got more and more familiar with how strapi handles things behind the scenes, allowing me to find out the potential bugs and fix them early. At this point, AI was out of options, I could only use it to generate the code that I already knew will work, kind of using AI to do the algorithm translation 😀. Now that I am writing this blog, I am not relying on AI as well as I normally do. Although I might use AI to generate some nice thumbnails or short heading 😂. &lt;/p&gt;

&lt;h2&gt;
  
  
  The outcome
&lt;/h2&gt;

&lt;p&gt;At the end of the first 5 days, I started seeing some results. The translations were being handled properly up to some extent. So although this took 5 days for me, it was still cheerful to see some results coming out. There were some issues on how components, dynamic zones were being handled but eventually I figured it all out and it started working perfectly. &lt;br&gt;
Now once it started working, a bug came up, a bug that will haunt me for more than 1 day. I got stuck in an infinite loop, the reason was the choice of “life cycle hooks to intercept user input”. But still I didn’t figure it out at that moment, I just found a work around by storing in memory keys for each translation under process to identify already translated entries and it worked. Now at this point I thought this was over, but it was not. After a few weeks, when I had to integrate strapi APIs with Nextjs frontend, another issue came up, “The content coming from APIs was not being translated”. The reason was the different structure of data. Until now, I was dealing with data coming from strapi admin panel, but the data coming from APIs was slightly different. The difference was not a bug, but a population issue, strapi does not include the data that needs population in API responses until we explicitly tell it to. Now I started fixing this error. I fixed it, faced another infinite loop issue because my work around failed at this point but I made it work anyway. &lt;/p&gt;

&lt;h2&gt;
  
  
  The end or the beginning??
&lt;/h2&gt;

&lt;p&gt;When it worked, I thought this is the end, I do not have to spend anymore time on this challenge because all types of scenarios were covered, But here comes a plot twist that made me realize why the choice of life cycle hooks was poor. I didn’t realize this on my own though, it was Grok AI who made me realize this. The issue was in translation, let me explain the issue with an example.&lt;/p&gt;

&lt;p&gt;The example is kind of my experience. I entered a title “Grand Palace”, now this title has been translated into other languages but it also got translated into English as well 😂. I entered “Grand Palace” but in the database I saw “Large Tent” 😂😂😂. At this point, out of frustration, I just spammed prompts on ChatGpt and Grok, and while I was spamming prompts in frustration, I found the solution, the solution was to use middlewares of strapi v5 with document service API. Until this point, I just thought middlewares were not even an option, I did some research on strapi docs, found out that middlewares are something built for this kind of work. I wanted to smash something around me, because the docs made me realize that middleware could also solve my infinite loop issue in a simpler way than what I was doing. Anyway I did what I had to and now I think it is perfect, as long as something new does not come up to haunt me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moral of story....
&lt;/h2&gt;

&lt;p&gt;Now I could write a lot of morals about the story but I want to know what you think about this. Let me know if you have any advice for me after reading my story.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>react</category>
    </item>
    <item>
      <title>Building a Custom Database Backup Plugin in Strapi — From Errors to Execution</title>
      <dc:creator>Aqib Shoaib</dc:creator>
      <pubDate>Wed, 16 Jul 2025 18:29:14 +0000</pubDate>
      <link>https://dev.to/aqib_shoaib_460d14658d79a/building-a-custom-database-backup-plugin-in-strapi-from-errors-to-execution-1chf</link>
      <guid>https://dev.to/aqib_shoaib_460d14658d79a/building-a-custom-database-backup-plugin-in-strapi-from-errors-to-execution-1chf</guid>
      <description>&lt;p&gt;&lt;em&gt;Written by &lt;a href="//www.linkedin.com/in/aqib-shoaib-2537ab257"&gt;Aqib Shoaib&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a full-stack developer working on projects that demand both flexibility and control, I found myself needing something Strapi doesn’t offer out of the box: a one-click, on-demand PostgreSQL database backup, directly from the admin panel or a secure API.&lt;br&gt;
Today, I decided to build it myself — and let me tell you, it wasn’t just a coding task. It was an adventure.&lt;br&gt;
Here’s how it went.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Set Out to Build&lt;/strong&gt;&lt;br&gt;
A custom Strapi plugin called db-backup that lets me:&lt;br&gt;
Trigger a backup via API&lt;br&gt;
Dump the PostgreSQL DB using pg_dump&lt;br&gt;
Save .sql files in public/backups&lt;br&gt;
(Eventually) send backups via email, zip them, etc.&lt;/p&gt;

&lt;p&gt;And while I was at it, I also experimented with the Strapi Import/Export plugin, planning for production deployments and safe data migrations between environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin Setup: The Skeleton&lt;/strong&gt;&lt;br&gt;
The plugin lives under:&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%2F3q0rfgt3uwqr2yghwjvo.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%2F3q0rfgt3uwqr2yghwjvo.png" alt=" " width="383" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In strapi-server.js, I kept it minimal to ensure loading worked:&lt;br&gt;
module.exports = () =&amp;gt; ({&lt;br&gt;
  register() {},&lt;br&gt;
  bootstrap() {&lt;br&gt;
    console.log('🛠️ Plugin bootstrapped');&lt;br&gt;
  },&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Went Wrong (And How I Fixed It)&lt;/strong&gt;&lt;br&gt;
❌ Route returns 404&lt;br&gt;
Initially, my route looked like this:&lt;br&gt;
{&lt;br&gt;
  method: 'GET',&lt;br&gt;
  path: '/backup',&lt;br&gt;
  handler: 'index.backupDatabase',&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;But hitting /api/db-backup/backup returned 404. Turns out: plugin routes are not prefixed with /api. The correct URL was:&lt;br&gt;
GET &lt;a href="http://localhost:1337/db-backup/backup" rel="noopener noreferrer"&gt;http://localhost:1337/db-backup/backup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lesson: Plugin routes skip the /api prefix entirely.&lt;/p&gt;

&lt;p&gt;❌ "Cannot read properties of undefined (reading 'backupDatabase')"&lt;br&gt;
This classic error means Strapi can’t find your handler. In my case, I had:&lt;br&gt;
File named index.js in controllers&lt;br&gt;
Handler string: 'index.backupDatabase'&lt;/p&gt;

&lt;p&gt;✔️ The fix: Make sure the file is named index.js, and that you’re exporting properly from it:&lt;br&gt;
module.exports = {&lt;br&gt;
  async backupDatabase(ctx) {&lt;br&gt;
    ctx.send({ message: 'Backup works!' });&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;❌ 'pg_dump' is not recognized as an internal or external command&lt;br&gt;
Windows users, brace yourself.&lt;br&gt;
This error showed up when trying to run:&lt;br&gt;
const command = &lt;code&gt;pg_dump ...&lt;/code&gt;;&lt;br&gt;
exec(command);&lt;br&gt;
Turns out:&lt;br&gt;
pg_dump isn't globally available on Windows unless PostgreSQL is added to PATH&lt;br&gt;
PGPASSWORD=... inline syntax doesn’t work on Windows&lt;br&gt;
✅ My approach:&lt;br&gt;
Ensure PostgreSQL is installed and added to PATH&lt;br&gt;
Move away from using inline PGPASSWORD&lt;br&gt;
Use native .env vars and rely on PostgreSQL authentication mechanisms (or .pgpass in VPS)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Note on Neon DB (PostgreSQL as a service)&lt;/strong&gt;&lt;br&gt;
While testing, I was using Neon.tech — a cloud-hosted Postgres service.&lt;br&gt;
What I discovered:&lt;br&gt;
pg_dump works technically, but:&lt;/p&gt;

&lt;p&gt;Neon only allows secure SSL connections, so your VPS must support this&lt;br&gt;
Backup performance may lag for large datasets over network&lt;br&gt;
In production, I’m moving to a VPS-hosted PostgreSQL instance where both the Strapi backend and DB are colocated — making pg_dump blazing fast and predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import/Export Plugin: The Data Lifeline&lt;/strong&gt;&lt;br&gt;
Aside from building the custom plugin, I also tinkered with the strapi-plugin-import-export-content to move data between local and production environments.&lt;br&gt;
✔️ It was smooth to install and use:&lt;br&gt;
npm install strapi-plugin-import-export-content&lt;br&gt;
✔️ I could export entire collections into .csv and re-import them wherever I needed.&lt;br&gt;
 ❌ Just be careful with deeply relational data — some post-processing might be required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Controller Code That Worked&lt;/strong&gt;&lt;br&gt;
const { exec } = require('child_process');&lt;br&gt;
const path = require('path');&lt;br&gt;
const fs = require('fs');&lt;/p&gt;

&lt;p&gt;module.exports = {&lt;br&gt;
  async backupDatabase(ctx) {&lt;br&gt;
    try {&lt;br&gt;
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');&lt;br&gt;
      const fileName = &lt;code&gt;backup-${timestamp}.sql&lt;/code&gt;;&lt;br&gt;
      const backupDir = path.join(strapi.dirs.static.public, 'backups');&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if (!fs.existsSync(backupDir)) {
    fs.mkdirSync(backupDir, { recursive: true });
  }

  const filePath = path.join(backupDir, fileName);

  const command = `pg_dump -U ${process.env.DATABASE_USERNAME} -h ${process.env.DATABASE_HOST} -p ${process.env.DATABASE_PORT || 5432} -F c -b -v -f "${filePath}" ${process.env.DATABASE_NAME}`;

  exec(command, (error, stdout, stderr) =&amp;gt; {
    if (error) {
      console.error('❌ Backup failed:', error.message);
      return ctx.internalServerError('Backup failed');
    }

    ctx.send({ message: '✅ Backup created', file: `/backups/${fileName}` });
  });
} catch (err) {
  console.error('💥 Error during backup:', err);
  ctx.internalServerError('Backup failed');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;},&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's Next?&lt;/strong&gt;&lt;br&gt;
Add UI support in the Admin panel&lt;br&gt;
Zip backup files&lt;br&gt;
Add email notifications&lt;br&gt;
Auto-delete old backups&lt;br&gt;
Secure route with auth policies&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Words&lt;/strong&gt;&lt;br&gt;
This wasn't just plugin development — it was Strapi internals, Linux/Windows command-line quirks, and real-world dev experience all rolled into one. I hit 404s, obscure errors, and environment-related gotchas… but I pushed through, and it works.&lt;br&gt;
If you're building serious apps on Strapi, I strongly encourage you to write tools that serve your exact workflow. No plugin? No problem. Build it.&lt;br&gt;
If you found this useful or want the full plugin code, feel free to connect or ping me!&lt;br&gt;
 – Aqib Shoaib&lt;/p&gt;

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