yooo its me shivam
and today we are doing to talk about file uploads yessss
it might sound a piece of shit topic but it is hard okay so lets start π
first lets start with frontend and backend flow
frontend send the file to the backend
backend receives
thing to be handled:
- taking the data properly (and when i say data i mean file)
- after that store them properly
- and in the end serve that data to the user properly
okay this is the main goal
first let understand one thing
and that is how the data will come from frontend to backend
Normal HTTP requests usually send:
{"name":"shivam"}
This is easy.
But files are NOT text.
A PNG file is raw binary bytes:
89 50 4E 47 ...
So browsers needed a special way to send everything in proper way:
- text
- files
- metadata
all together in ONE request.
and that will be done using:
multipart/form-data
why called multipart?
because it contain multi form data π
yes this is why it is called like that
Example
Part 1 β username
Part 2 β image file
Part 3 β another field
Each part is separated by using a special string called:
Boundary
like for example
When browser sends:
const form = new FormData();
form.append("username", "shivam");
form.append("image", file);
The browser internally creates something like:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKit123
this is how the body look like
------WebKit123
Content-Disposition: form-data; name="username"
shivam
------WebKit123
Content-Disposition: form-data; name="image"; filename="cat.png"
Content-Type: image/png
<binary file bytes here>
------WebKit123--
Now Letβs Break This Down
Part 1
------WebKit123
Content-Disposition: form-data; name="username"
shivam
This means:
{
username: "shivam"
}
Part 2 (File)
------WebKit123
Content-Disposition: form-data; name="image"; filename="cat.png"
Content-Type: image/png
This is metadata.
It tells:
- field name = image
- filename = cat.png
- mime type = image/png
Then comes:
(binary bytes)
Actual file data.
How Data Actually Travels
HTTP body travels as STREAMS
NOT like a giant packet.
Instead:
Chunk 1
Chunk 2
Chunk 3
Chunk 4
Like:
------WebKit123
Content-Dispo
then:
sition: form-data;
then:
name="image"
Everything arrives gradually.
Node.js Receives It as Stream
Inside Node.js req is actually a readable stream.
You can literally do:
req.on("data", chunk => {
console.log(chunk);
});
Example you may see:
<Buffer 2d 2d 2d 2d 57 65 62 4b>
These are raw bytes.
now the best part comes
how a developer should handle this
see express is not having anything inbuilt to handle this
but there is one module:
multer
it helps us convert that raw bit into proper data
kinda like this:
{
fieldname: 'image',
originalname: 'cat.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: 'a8f91c.png',
path: 'uploads/a8f91c.png',
size: 34567
}
let see how multer does that
multer parse this stream manually like:
- Reads chunks
- Detects boundaries
- Separates parts
- Reads headers
- Extracts metadata
- Writes file bytes to disk
How File Is Recreated
Suppose multer extracts:
89 50 4E 47 ...
(this data is after cleaning all the other data this is only the file data)
These are PNG bytes.
It simply writes them into a file using:
fs.writeFile()- or stream pipe
Because files ARE just bytes.
yes it is that simple π
SUPER IMPORTANT UNDERSTANDING
A file is NOT special.
A file is just:
Raw bytes + metadata
Example:
cat.png
is just:
[bytes] stored on disk
now this is how the multer code looks like
Backend Setup
const express = require("express");
const multer = require("multer");
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/");
},
filename: function (req, file, cb) {
cb(null, Date.now() + "-" + file.originalname);
}
});
const upload = multer({ storage: storage });
app.post("/upload", upload.single("image"), (req, res) => {
console.log(req.file);
res.send("File uploaded");
});
app.listen(3000);
Understanding upload.single("image")
This means:
- expect ONE file
- field name should be
"image"
Frontend looks like this:
<input type="file" name="image" />
must match exactly.
lets understand this code
first there is:
multer.diskStorage()
this means what it says:
going to store on disk π
this code specify the destination
here it is /uploads folder
destination: function (req, file, cb) {
cb(null, "uploads/");
}
then comes filename
(just give a name to that file)
filename: function (req, file, cb) {
cb(null, Date.now() + "-" + file.originalname);
}
then passing that into multer function
and in the end using it as middleware like:
upload.single("image")
req.file
and one more thing
in the next route handler u can access the file using:
req.file
it contains:
{
fieldname: 'image',
originalname: 'cat.png',
mimetype: 'image/png',
destination: 'uploads/',
filename: '8f91c.png',
path: 'uploads/8f91c.png',
size: 34567
}
Multer creates this object manually.
everything in short
destination
Defines where files will be stored.
uploads/
filename
Creates unique file names.
Example:
171523432-photo.png
This prevents filename collisions.
Without unique names:
image.png
image.png
image.png
Files would overwrite each other.
Creating Upload Middleware
const upload = multer({ storage });
Upload Route
app.post("/upload", upload.single("image"), (req, res) => {
res.send("File uploaded successfully");
});
Understanding upload.single()
upload.single("image")
This means:
- Expect one file
- Field name must be
"image"
Frontend:
<input type="file" name="image" />
Names must match.
Folder Structure After Upload
project/
β
βββ uploads/
β βββ 171523432-profile.png
β βββ 171523500-resume.pdf
β
βββ server.js
βββ package.json
Visual Flow
Browser Request
β
/uploads/photo.png
β
Express Static Middleware
β
Find File in uploads Folder
β
Return File Response
now one of the best parts
storing that file π
we have two option whenever it comes to storing any file
- Local Storage
- External Storage
1. Local Storage
Files are stored directly inside the server filesystem.
Example structure:
project/
β
βββ uploads/
β βββ image1.png
β βββ profile.jpg
β βββ resume.pdf
β
βββ server.js
βββ package.json
The uploaded file physically exists inside the project folder.
2. External Storage
Instead of storing files on the server:
Files are stored in external services like:
- AWS S3
- Cloudinary
- Firebase Storage
- DigitalOcean Spaces
Flow:
User Upload
β
Express Server
β
Cloud Storage
The Problem After Uploading
Now files exist on the server.
But users still cannot access them from browser.
Why?
Because Express does NOT expose folders automatically.
Serving Static Files in Express
To make uploaded files accessible:
Use:
express.static()
Static File Serving
app.use("/uploads", express.static("uploads"));
This line is extremely important.
It tells Express:
"Serve files inside uploads folder publicly"
How Static Serving Works
Suppose file exists:
uploads/photo.png
Now browser can access:
http://localhost:3000/uploads/photo.png
Express automatically serves the file.
Important Security Considerations
File uploads are dangerous if handled carelessly π
Never trust uploaded files blindly.
1. Validate File Types
Users may upload:
- virus.exe
- malware.js
- dangerous.php
Instead of images.
Use file filtering.
Example:
const fileFilter = (req, file, cb) => {
if (file.mimetype.startsWith("image")) {
cb(null, true);
} else {
cb(new Error("Only images allowed"));
}
};
2. Limit File Size
Huge uploads can crash servers.
Example:
const upload = multer({
storage,
limits: {
fileSize: 5 * 1024 * 1024
}
});
5MB limit.
3. Rename Uploaded Files
Never trust original filenames.
Bad:
myvirus.exe
Good:
171523432-file.png
4. Avoid Executable Uploads
Never allow:
.exe.bat.sh.php
These may become security risks.
5. Store Sensitive Files Privately
Not all uploads should be public.
Examples:
- Aadhaar cards
- Passports
- Medical documents
These should NOT use public static serving.
Instead:
- Store privately
- Add authentication before access
Public vs Private File Access
Public Access
/uploads/image.png
Anyone with URL can access it.
Used for:
- Product images
- Profile photos
- Blog thumbnails
Private Access
but only this it require is authorization π
Example flow:
User Login
β
Authentication Check
β
Server Sends File
Used for sensitive documents.
final flow of working
High-Level Upload Flow
User Selects File
β
Frontend Sends Multipart Request
β
Multer Processes File
β
File Stored in uploads/
β
Express Static Middleware Exposes File
β
Browser Accesses File via URL
one thing i did not talk about
when we store data on external provider so how user will get that data?
best thing is:
when u upload user file to that provider that provider gives u a URL of that image
so u just need to send that to frontend
and that URL can be inside image tag
then u can see it π
so everything is done:
- how to take the data
- how to store it
- how to give it back to the user
so i think this is the end
if u like the blog please let me know in comment
and like the blog
and subscribe me π
i have alot of knowledge i want to share
so if u want to be part of it then subscribe