<?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: Ahmer Arain</title>
    <description>The latest articles on DEV Community by Ahmer Arain (@ahmerarain).</description>
    <link>https://dev.to/ahmerarain</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%2F1173743%2Fd55ebb77-5d86-4d1a-b67e-8b4f2dab7131.png</url>
      <title>DEV Community: Ahmer Arain</title>
      <link>https://dev.to/ahmerarain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmerarain"/>
    <language>en</language>
    <item>
      <title>How I Built a File Converter That Never Touches Your Files published</title>
      <dc:creator>Ahmer Arain</dc:creator>
      <pubDate>Wed, 29 Apr 2026 19:25:35 +0000</pubDate>
      <link>https://dev.to/ahmerarain/how-i-built-a-file-converter-that-never-touches-your-files-published-3692</link>
      <guid>https://dev.to/ahmerarain/how-i-built-a-file-converter-that-never-touches-your-files-published-3692</guid>
      <description>&lt;p&gt;I've used plenty of online file converters. Most of them upload your files to some server, process them, then (hopefully) delete them. You're trusting a stranger's backend with your documents, photos, or sensitive data.&lt;/p&gt;

&lt;p&gt;I didn't love that. So I built &lt;strong&gt;&lt;a href="https://www.convertifyhub.net" rel="noopener noreferrer"&gt;ConvertifyHub&lt;/a&gt;&lt;/strong&gt; — a file converter that supports 150+ formats and processes everything locally, right in your browser. Your files never leave your device.&lt;/p&gt;

&lt;p&gt;Here's how I built it and what I learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Idea: Client-Side Everything
&lt;/h2&gt;

&lt;p&gt;The whole product is built around one constraint: &lt;strong&gt;no file uploads, no server, no database&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means every conversion — image, document, audio, video, archive — has to happen in the browser using JavaScript and WebAssembly. This is harder to build, but the payoff is massive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero privacy risk for the user&lt;/li&gt;
&lt;li&gt;No infrastructure cost for conversions&lt;/li&gt;
&lt;li&gt;Instant processing (no round-trip to a server)&lt;/li&gt;
&lt;li&gt;Works offline once loaded&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend:   Next.js (TypeScript)
Deployment: AWS
Processing: Client-side JS + WebAssembly libraries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No backend for file processing. No database. Just a fast frontend deployed on AWS.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Each Tool Works Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Image Conversion
&lt;/h3&gt;

&lt;p&gt;For image conversion I use the browser's native &lt;code&gt;Canvas&lt;/code&gt; API combined with libraries like &lt;code&gt;sharp.js&lt;/code&gt; compiled to WebAssembly. The user drops a file, it gets read via &lt;code&gt;FileReader&lt;/code&gt;, drawn to a canvas, then exported in the target format as a Blob.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&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;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Trigger download&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;downloadFile&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;outputName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;targetMimeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&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;h3&gt;
  
  
  Audio &amp;amp; Video
&lt;/h3&gt;

&lt;p&gt;This was the trickiest part. I used &lt;strong&gt;FFmpeg compiled to WebAssembly&lt;/strong&gt; (&lt;code&gt;@ffmpeg/ffmpeg&lt;/code&gt;). It's the same FFmpeg you'd run on a server — but running entirely in the browser via WASM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createFFmpeg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchFile&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;@ffmpeg/ffmpeg&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;ffmpeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFFmpeg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;writeFile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputName&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="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readFile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file never leaves the browser. FFmpeg runs in a sandboxed WASM environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Archives
&lt;/h3&gt;

&lt;p&gt;For ZIP/RAR/7Z handling I used &lt;strong&gt;JSZip&lt;/strong&gt; and &lt;strong&gt;libarchive.js&lt;/strong&gt; (another WASM port). Reading and creating archives purely in-browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Conversion
&lt;/h3&gt;

&lt;p&gt;This one has limitations — true DOCX-to-PDF conversion server-side is always going to be more reliable. But for many use cases (txt, csv, basic formats) the browser handles it well.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tools I Ended Up Building
&lt;/h2&gt;

&lt;p&gt;What started as "just an image converter" turned into a full suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image Converter&lt;/strong&gt; — 25+ formats including WebP, AVIF, HEIC&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image Resizer &amp;amp; Compressor&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Converter&lt;/strong&gt; — MP3, WAV, FLAC, AAC, OGG (via FFmpeg WASM)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video Trimmer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document Converter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archive Converter&lt;/strong&gt; — ZIP, RAR, 7Z, TAR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spreadsheet Converter&lt;/strong&gt; — XLSX, CSV, TSV&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit &amp;amp; Digital Converters&lt;/strong&gt; — 50+ units, number bases, color formats&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QR &amp;amp; Barcode Generator&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Minifier &amp;amp; Beautifier&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Font Converter&lt;/strong&gt; — TTF, OTF, WOFF, WOFF2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Tools&lt;/strong&gt; — JWT, hashing, encryption utilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON → TOON Converter&lt;/strong&gt; — for LLM token optimization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Website Image Scraper&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SVG Optimizer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown Converter&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 20+ tools, all running client-side.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Made This Hard
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. WebAssembly Loading Time
&lt;/h3&gt;

&lt;p&gt;FFmpeg WASM is ~25MB. First load is slow. I lazy-load it only when the user actually needs audio/video conversion, and show a progress indicator.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Memory Limits
&lt;/h3&gt;

&lt;p&gt;Browsers cap memory. Large files (especially video) can crash the tab. I added file size warnings and chunk large files where possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Format Support Gaps
&lt;/h3&gt;

&lt;p&gt;Not every format has a good JS/WASM library. HEIC on some browsers is still a pain. I had to pick my battles and clearly communicate unsupported edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cross-Browser Consistency
&lt;/h3&gt;

&lt;p&gt;Canvas API behaves differently across browsers for certain image formats. Lots of testing across Chrome, Firefox, Safari.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Privacy-First Architecture
&lt;/h2&gt;

&lt;p&gt;No file uploads means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No S3 buckets storing user files&lt;/li&gt;
&lt;li&gt;No database needed&lt;/li&gt;
&lt;li&gt;No GDPR headaches around file storage&lt;/li&gt;
&lt;li&gt;No server costs per conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only infra I run is serving the Next.js frontend on AWS. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;More batch processing support&lt;/li&gt;
&lt;li&gt;Better progress indicators for large files&lt;/li&gt;
&lt;li&gt;PWA support for offline use&lt;/li&gt;
&lt;li&gt;More AI-assisted format recommendations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.convertifyhub.net" rel="noopener noreferrer"&gt;convertifyhub.net&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No account needed. No upload. Just drop a file and convert.&lt;/p&gt;

&lt;p&gt;If you're building something privacy-sensitive and wondering whether you can move processing to the client — in many cases, you can. WebAssembly has made things possible in the browser that used to require a backend. I'd love to hear if you've taken a similar approach.&lt;/p&gt;

&lt;p&gt;Drop a comment if you have questions about any part of the stack!&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ahmer Arain&lt;/strong&gt; — Full-stack developer specializing in MERN stack, Next.js, and AWS. I build scalable web and mobile products, from SaaS platforms to marketplace apps.&lt;/p&gt;

&lt;p&gt;🌐 &lt;a href="https://ahmerarain.com" rel="noopener noreferrer"&gt;ahmerarain.com&lt;/a&gt;&lt;br&gt;
💼 &lt;a href="https://www.linkedin.com/in/ahmer-arain" rel="noopener noreferrer"&gt;linkedin.com/in/ahmer-arain&lt;/a&gt;&lt;br&gt;
🐙 &lt;a href="https://github.com/ahmerarain" rel="noopener noreferrer"&gt;github.com/ahmerarain&lt;/a&gt;&lt;br&gt;
📧 &lt;a href="mailto:ahmerarain18@gmail.com"&gt;ahmerarain18@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>convertfile</category>
    </item>
  </channel>
</rss>
