<?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: Ghazala Parveen</title>
    <description>The latest articles on DEV Community by Ghazala Parveen (@ghazala_parveen_e291ee74f).</description>
    <link>https://dev.to/ghazala_parveen_e291ee74f</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%2F3862051%2Fd5c74570-39a4-48e4-9305-0ece05dd3197.png</url>
      <title>DEV Community: Ghazala Parveen</title>
      <link>https://dev.to/ghazala_parveen_e291ee74f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ghazala_parveen_e291ee74f"/>
    <language>en</language>
    <item>
      <title>I Built a Simple Web Tool and Here's What Nobody Tells You About the Process</title>
      <dc:creator>Ghazala Parveen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:04:41 +0000</pubDate>
      <link>https://dev.to/ghazala_parveen_e291ee74f/i-built-a-simple-web-tool-and-heres-what-nobody-tells-you-about-the-process-57gk</link>
      <guid>https://dev.to/ghazala_parveen_e291ee74f/i-built-a-simple-web-tool-and-heres-what-nobody-tells-you-about-the-process-57gk</guid>
      <description>&lt;p&gt;I'm not a senior developer. I don't have a CS degree. But a few months ago, I shipped a working web tool that actual people use. And the journey from "I have an idea" to "it's live on a custom domain" taught me more than any tutorial ever did.&lt;br&gt;
Let me walk you through what actually happened.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Idea Was Stupidly Simple
&lt;/h2&gt;

&lt;p&gt;I kept running into the same annoying problem — I wanted to save short videos from social media to watch offline. Not for reposting, not for any shady reason. Just so I could watch a cooking tutorial on a flight or send a funny clip to my mom on WhatsApp.&lt;/p&gt;

&lt;p&gt;Every tool I found was either full of sketchy pop-up ads, asked for my login credentials (huge red flag), or just didn't work half the time.&lt;/p&gt;

&lt;p&gt;So I thought — how hard can it be to build something that just takes a URL, fetches the video file, and lets you download it?&lt;br&gt;
Turns out, it's both easier and harder than I expected.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Tech Stack I Chose (And Why)
&lt;/h2&gt;

&lt;p&gt;I kept it dead simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend: HTML, CSS, vanilla JavaScript
Backend: Node.js with Express
Hosting: VPS with Nginx
Domain: Custom .com domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No React. No Next.js. No Tailwind. I know that sounds heretical in 2026, but hear me out — for a single-purpose tool, you don't need a framework. A clean HTML page with some JS fetch calls does the job perfectly.&lt;/p&gt;

&lt;p&gt;The backend was where the real work happened. The flow is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of the core logic&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/fetch-video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate the URL&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="nf"&gt;isValidURL&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid URL&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="c1"&gt;// Fetch the page, parse the meta tags / video source&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;extractVideoSource&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="c1"&gt;// Return the direct video URL to the client&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videoData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videoData&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;p&gt;The tricky part? The extractVideoSource function. Social media platforms don't just hand you the video file on a silver platter. You have to parse the page HTML, look through meta tags, and sometimes dig through embedded JSON in the page source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things That Broke (And What I Learned)
&lt;/h2&gt;

&lt;p&gt;Lesson 1: APIs change without warning.&lt;/p&gt;

&lt;p&gt;I built my first version using a specific pattern to extract video URLs. It worked perfectly for two weeks. Then one morning — everything broke. The platform had changed its page structure. No announcement, no changelog. Just broken.&lt;/p&gt;

&lt;p&gt;I rewrote the parser three times in the first month. That's when I learned to build flexible parsing logic with multiple fallback methods:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractVideoSource&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Try method 1: Open Graph meta tags&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;tryOGTags&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Try method 2: Embedded JSON-LD data&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;tryJSONLD&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Try method 3: Script tag parsing&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;tryScriptParsing&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not extract video source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lesson 2: Error handling is not optional.
&lt;/h2&gt;

&lt;p&gt;My first version had almost no error handling.&lt;/p&gt;

&lt;p&gt;If something went wrong, the user got a blank screen or a cryptic error. I quickly learned that users don't read console logs. They need clear, human-readable messages.&lt;br&gt;
Now every possible failure has a friendly message: "This link doesn't seem to contain a video," "The video might be private," or "Something went wrong on our end — try again in a minute."&lt;/p&gt;
&lt;h2&gt;
  
  
  Lesson 3: Rate limiting will save your server.
&lt;/h2&gt;

&lt;p&gt;About a week after launch, someone (or some bot) started hitting my API hundreds of times per minute. My tiny VPS almost died. I added rate limiting the same day:&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;rateLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-rate-limit&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;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 15 minutes&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// limit each IP to 50 requests per window&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too many requests. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should have done this from day one. Lesson learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Things Tutorials Don't Teach You
&lt;/h2&gt;

&lt;p&gt;DNS propagation is not instant. &lt;/p&gt;

&lt;p&gt;I bought my domain, pointed it to my server, and then spent 6 hours wondering why it wasn't working. Turns out, DNS can take up to 48 hours to propagate. Nobody told me that.&lt;/p&gt;

&lt;p&gt;SSL certificates matter more than you think. &lt;/p&gt;

&lt;p&gt;Without HTTPS, browsers show a scary "Not Secure" warning. I used Let's Encrypt with Certbot — it's free, and setting it up with Nginx took me about 20 minutes after watching a tutorial.&lt;/p&gt;

&lt;p&gt;Nobody will find your tool unless you tell them.&lt;/p&gt;

&lt;p&gt;I spent weeks building it, launched it, and then got zero visitors for days. Turns out, "build it and they will come" is a lie. I had to learn basics of SEO — meta tags, page titles, descriptions, sitemap.xml, submitting to Google Search Console. This was a whole separate skill I didn't expect to need.&lt;/p&gt;

&lt;p&gt;Your first users will find bugs you never imagined.&lt;/p&gt;

&lt;p&gt;Someone tried to paste a URL with extra spaces. Someone else pasted a whole paragraph instead of a URL. Someone submitted an empty form 47 times. You learn to never trust user input.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started over today, I would:&lt;/p&gt;

&lt;p&gt;Start with error handling and rate limiting from day one, not after things break&lt;/p&gt;

&lt;p&gt;Add basic analytics (even just counting requests per day) so I know if anyone is actually using it&lt;/p&gt;

&lt;p&gt;Write tests — I know, I know, everyone says this. But I didn't write a single test until something broke in production at 2 AM and I couldn't figure out what changed&lt;/p&gt;

&lt;p&gt;Use environment variables from the start instead of hardcoding config values and then painfully refactoring later&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The tool works. It's live at &lt;a href="https://downreels.com/" rel="noopener noreferrer"&gt;downreels.com&lt;/a&gt; and handles a few hundred requests daily. It's not going to make me rich. It's not going to impress anyone at a FAANG interview. But it solves a real problem, real people use it, and I built it from scratch.&lt;/p&gt;

&lt;p&gt;That feeling of typing your domain into a browser and seeing something YOU built — I can't describe it. If you've shipped something, you know what I mean. If you haven't yet, I promise it's worth the frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  For Anyone Thinking About Building Their First Tool
&lt;/h2&gt;

&lt;p&gt;Just start. Pick a problem you personally have. Keep the tech stack simple. Don't use a framework unless you actually need one. Ship something ugly that works, then make it pretty later.&lt;/p&gt;

&lt;p&gt;The gap between "I'm learning to code" and "I built something people use" is smaller than you think. It's mostly just the willingness to deal with things breaking at 2 AM and not quitting.&lt;/p&gt;

&lt;p&gt;Happy building.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
