Stop Crashing Your Server with Images: Handling File Uploads in Express with Multer
Why JSON parsers fail at files, understanding multipart forms, and how to safely save user uploads to disk.
Search for a command to run...
Why JSON parsers fail at files, understanding multipart forms, and how to safely save user uploads to disk.
No comments yet. Be the first to comment.
These are the assignments or knowledge dumps I decide to write for the "Chai aur Code's web Dev Cohort 2026"
When I first thought about how developers used to collaborate before modern version control, I expected to find some archaic but functional network protocol. I expected a systematic, albeit clunky, wa
RAG helps LLMs answer questions using your own data, but it doesn't guarantee correct answers. Here's how the pipeline works and where it commonly breaks down.

Imagine asking an AI, "Write a blog," versus "Act as an SEO expert and write a 1,000-word blog on AI safety formatted in markdown." The difference in the resulting output is night and day. A prompt is

Spotify and Apple Music make it incredibly easy to showcase your live listening habits on your website. YouTube Music? Not so much. After doing some setup you can do an API call and fetch your account

Arthur C. Clarke's famous third law: "Any sufficiently advanced technology is indistinguishable from magic" perfectly captures how the pace of human innovation works. From carrying supercomputers in o

Why HTTP is amnesiac, the difference between transport and state, and how to choose the right auth strategy for your Node.js app.
You’ve built a solid Express API. It accepts user registrations, parses JSON beautifully, and stores records in your database. Then, you decide to let users upload a profile picture.
You add an <input type="file"> to your frontend, submit the form, and log req.body in your Express route.
It is completely empty. Or worse, it’s a terrifying wall of random characters that crashes your console.
When developers first encounter this, it feels like a bug. It’s not. It is a fundamental reality of how the web transmits data. Up to this point, you have likely relied on express.json() to parse incoming requests. But an image is not JSON. An image is a massive stream of binary data.
To handle file uploads in Node.js, you have to stop thinking about text and start thinking about streams, chunks, and boundaries. Let’s break down exactly how files travel across the web, why your standard middleware fails, and how to use a tool called Multer to catch them.
multipart/form-data?When a browser sends a simple text form, it encodes the data into a neat, predictable string. But when you attach a 5MB image to that form, things change.
To send massive files, the browser changes the HTTP request type to multipart/form-data. It literally slices the request into distinct "parts." One part might contain the user's name as text. The next part contains the raw binary chunks of the image. Between each part, the browser inserts a unique "boundary" string so the server knows where the text ends and the image begins.
Express, out of the box, has absolutely no idea how to read a multipart request. It looks at the incoming stream, shrugs, and ignores it.
To parse this, we need middleware specifically designed to hunt for those boundaries, extract the text fields into a normal object, and stitch the binary chunks back together into a file. In the Node.js ecosystem, the standard tool for this job is Multer.
Multer is a piece of Express middleware. It intercepts the incoming request, parses the multipart data, saves the file, and then hands control over to your route handler.
Before we can use it, we have to tell Multer exactly where to put the files and what to name them. We do this using multer.diskStorage.
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// 1. Configure the storage engine
const storage = multer.diskStorage({
// Where should the files go?
destination: (req, file, cb) => {
// 'cb' is a callback. The first argument is for errors (null),
// the second is the destination folder.
cb(null, 'uploads/');
},
// What should we name the file?
filename: (req, file, cb) => {
// To prevent naming collisions, we append the current timestamp
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
// 2. Initialize Multer with our storage configuration
const upload = multer({ storage: storage });
This configuration acts as the blueprint. It tells Multer: "When a file arrives, put it in the /uploads directory, and rename it with a timestamp so we don't accidentally overwrite someone else's file."
Let’s say a user is updating their profile. They are sending a text field called username and a file field called avatar.
We inject Multer directly into the specific route that needs it using upload.single().
// The string 'avatar' MUST match the name attribute of your HTML input
app.post('/profile', upload.single('avatar'), (req, res) => {
// 1. Multer puts the text fields back into req.body
const username = req.body.username;
// 2. Multer puts the file information into req.file
const fileInfo = req.file;
if (!fileInfo) {
return res.status(400).json({ error: "No file uploaded" });
}
// By the time this code runs, the file is already saved to your hard drive.
res.json({
message: `Profile updated for ${username}`,
filePath: fileInfo.path // e.g., "uploads/avatar-1683492023.jpg"
});
});
Notice the order of operations. You don't have to write code to save the file. The middleware handles the complex stream parsing and disk writing before your arrow function ever executes.
What if you are building an e-commerce site and the user wants to upload 5 photos of a product at once?
Instead of upload.single(), we use upload.array(). We pass the field name, and an optional maximum number of files to prevent abuse.
// Accept up to 5 files from an input named 'gallery'
app.post('/products', upload.array('gallery', 5), (req, res) => {
// For multiple files, Multer uses req.files (plural)
const uploadedFiles = req.files;
if (!uploadedFiles || uploadedFiles.length === 0) {
return res.status(400).json({ error: "No images provided" });
}
// Extract the paths to save in our database
const filePaths = uploadedFiles.map(file => file.path);
res.json({
message: "Product created with images!",
images: filePaths
});
});
At this point, users can upload files, and your server is happily saving them to the uploads/ folder on your hard drive.
But if the frontend tries to render <img src="http://localhost:3000/uploads/avatar.jpg">, the image will break. Why? Because Express routes are locked down by default. You haven't written an app.get('/uploads/:filename') route, so Express just throws a 404.
Instead of writing a custom route to send back files, Express gives us a built-in tool to expose a folder directly to the public: express.static().
Add this single line near the top of your file, right under your app initialization:
// Make the 'uploads' directory publicly accessible
app.use('/uploads', express.static('uploads'));
This tells Express: "If a request comes in for the /uploads URL, look inside the uploads/ folder on my hard drive and just send back whatever file matches the name."
Handling file uploads in Node.js feels intimidating because it forces you to confront the reality of how browsers package data. JSON is a luxury; multipart/form-data is the raw reality of the web.
But by trusting Multer to sit between the incoming network stream and your route handler, the complexity vanishes. You configure where the data should go, attach the middleware, and let it do the heavy lifting. Your route handlers stay clean, predictable, and focused entirely on your business logic.
(A word of caution for the future: saving files to your local server disk is perfect for learning and small apps. But when you deploy a massive application, local hard drives fill up quickly and disappear when servers restart. Once you master this local flow, your next step is modifying Multer to stream those files directly to a cloud storage bucket like AWS S3. The concepts remain exactly the same.)