Mon 30th Mar 2026
Handling large file upload to a PHP and NodeJS server
In this article I go into how you can implement chunked file uploading, this is especially useful when you are dealing with large file uploads.
Background
I had developed a document management site and we came across an issue where one user had a 200MB file to upload, each time they wanted to submit there was always a crash which made the site unusable. This actually led me into looking at the black box of how the file uploading works because I usually go down rabbit holes when something like this occurs.
How the file selection and upload process normally works in the browser
Step 1: Clicking on the file upload button
This makes the browser ‘talk’ with the operating system(windows, mac or linux) to open its native file picker. This lets the user browse around available files and almost always opens where the last uploaded file for a succesful form submission was picked
Step 2: Picking the file
The user then picks a file for upload. Upon doing this the browser creates a file object in its memory with the file’s metadata ie the name, size, type (e.g., image/jpeg), and last modified date. At this point, the whole file isn’t loaded into memory then.
Step 3: Clicking on submit to send the file
Browsers use a standard called multipart/form-data to send the file and it has the following components. Once the submit button is clicked, the browser then prepares the data to be sent. The data will have:
- Headers: This also will have a
Content-lengthheader to tell the server how big the file is - A Boundary: The browser generates a long, unique string called a boundary. This acts like a digital separator.
- A Payload: It wraps the file, and any other input supplied, in a "envelope"
Step 4: Server Processing
Many modern web frameworks (like Express in Node.js, or certain Python/PHP setups) are configured by default to "buffer" the request body(or payload). The server waits until the entire payload has arrived. Once the whole thing is there, it hands it over to the code as one giant variable in memory (RAM that is). This is where the issue was, because we had a 1GB RAM VPS and picture taking 20% of it for one user while still retaining other processes eg the server OS itself, the programming language daemons and some other background tasks.
At the time the budget was tight and resolving this issue for the long term was better than just pooling resources to expand the server’s resource.
Exploring possible solutions:
Direct-to-Cloud Uploads (Presigned URLs)
In this method - instead of sending the large file to your server, your server simply gives the browser a "VIP Pass" (a Presigned URL) to upload directly to a storage provider like AWS S3, Google Cloud Storage, or Azure Blobs.
The Workflow:
- Browser asks your server: "Can I upload this 200gb file :) ?"
- Server returns a temporary, secure URL.
- Browser PUTs the file directly to the cloud provider.
This works because major cloud providers are purpose built to handle massive streams of data. My server never even touches the large file, so it won’t crash. I would just need the link to the file which is a simple string.
I didn’t explore this because of budget reasons though it is really a good option.
TUS Protocol (Resumable Uploads)
I was actually surprised by this and wondered why never came across it earlier. I believe it mostly developed for users with mobile connections or those in public WiFi where the connection can “blink”.
How it works: It breaks the file into a "fingerprint." If the connection drops, the browser asks the server, "How many bytes did you get?" The server says "190MB," and the browser sends only the remaining 10MB.
The Benefit: It’s incredibly stable for mobile users or people with spotty connections which would be a good portion of our users.
At the time I explored its repositiories and I kind of saw it as a new tech and didn’t want to test it further
Chunking the file for upload
In this method, the file is chunked into pieces and sent to the server for processing. Think of a whole unsliced bread vs a sliced bread and the server has to ‘process’ the whole of it. I mean one is easier to get through, right?
The benefit to this is that RAM usage: remains constant regardless of whether the file is 1MB or 1GB. Also it was ,in theory at the time, set to use the programming language constructs of the servers that had been setup so no new library addition yay!
Abstract Implementation
Step 1: The front end HTML
This simply houses the form and the JS required to chunk it
Step 2: The front end JS
The JavaScript for the front end along with comments for each step
Step 3.1: The back end JS
We had a service in Node JS so I had to implement it in node JS using express as below
Step 3.2: The back end PHP
The other service in the backend doing the uploads was in PHP. This was implemented as below:
Some bonus comments
With the above abstract implementation I was able to develop a system to further perform retries continue from a halted upload by signing the upload and file via local storage and identifying which chunks were remaining.
It was a noticeable improvement and the feedback to users of the upload process was welcomed since earlier it was sort of a black box.
Do you have some more to add or want a few things tweaked? Let me know below