Why my downloader has two PHP endpoints instead of one
When I first built the downloader on ramrom.io, I had one PHP file doing everything: take the pasted link, call the provider API, fetch the video file, and hand it to the visitor. It worked. It was also a mess. Every bug
When I first built the downloader on ramrom.io, I had one PHP file doing everything: take the pasted link, call the provider API, fetch the video file, and hand it to the visitor. It worked. It was also a mess. Every bug lived in the same place, every fix touched the same file, and when something broke I had no idea which half had failed.
So I split it in two. Now resolve.php does the talking and the proxy does the fetching, and they never swap jobs. Here is why that split has held up.
resolve.php: the talker
resolve.php takes one thing from the browser: the link the visitor pasted. It checks that the link is a real URL on a platform I support — TikTok, Instagram, Facebook or YouTube — and nothing else. If the link fails that check, no API call is spent. Then it calls the provider's API, gets back a list of formats, and returns that list as JSON.
That is all it does. It never touches a video file. It never streams a single byte of media.
Because it is a small, boring endpoint, it is easy to protect. The per-IP rate limit lives here, not in the proxy: 30 requests a minute per visitor, counted in a flat file, because shared hosting has no Redis. The retry loop lives here too — if the provider answers "Service is busy", it tries again up to three times before telling the visitor honestly that the provider is down. And because the response is small JSON, I can log failures without logging anything about the visitor.
The proxy: the fetcher
Once the visitor picks a quality, the browser calls the second endpoint, the download proxy, with the file URL that resolve.php returned. The proxy does not know or care which platform the link came from. Its job is to fetch bytes and pass them through.
Streaming matters here. A video can be hundreds of megabytes, and PHP on shared hosting will die if you try to load that into memory. So the proxy reads the remote file in chunks and echoes each chunk straight to the visitor, keeping memory use flat. It also sets the headers that make the browser download instead of playing: Content-Disposition: attachment with a cleaned-up filename, and the right Content-Type for MP4 or M4A.
The proxy also has its own guard rails, different from the resolver's. It checks the file really is a video before sending it — an HTML error page wearing a .mp4 name gets rejected. It follows at most five redirects, and it refuses private IP ranges, so nobody can point it at something on my own server.
Why the split pays off
The main win is that each endpoint fails in its own way, and I can tell which one it was. If resolve.php returns an error, the provider or the pasted link is the problem. If the proxy fails, the file itself is the problem — deleted, private, or the provider's link expired. The error message the visitor sees can say exactly that, instead of a generic "something went wrong".
Testing is simpler too. I can curl resolve.php with a link and check the JSON without downloading anything. I can curl the proxy with a known file URL and check the headers and the first bytes without involving the provider API at all. Two small checks, each testing one thing.
There is one honest exception. YouTube's provider tunnels sometimes return empty responses to datacenter IPs, which includes my server. For those formats the proxy cannot help, so resolve.php flags them as "direct" and the visitor's browser fetches the file straight from the tunnel instead. Same two endpoints, same split — just a flag that says "this one, skip the middleman".
If you are building a tool on plain shared hosting, resist the one-file version. Two small endpoints, each with one job and its own guard rails, are easier to debug, easier to protect, and much easier to explain to yourself six months later when something breaks at midnight.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.