File Uploads with Multer
# File Uploads with Multer
Welcome to Chapter 25! Until now, we have handled text data via express.json() and express.urlencoded(). But what happens when a user wants to upload a profile picture or a PDF document?
Standard JSON and URL-encoded formats cannot handle large binary files. To send files over the internet, browsers use a format called multipart/form-data. Express has no built-in way to read this format. To solve this, the Node.js community created a powerful middleware package named Multer.
---
1. Introduction
When a user submits an HTML form containing <input type="file">, the browser chops the file into multiple parts (hence "multipart") and streams it to your server.
Multer is an NPM package designed exclusively to handle multipart/form-data. It intercepts the incoming file stream, reconstructs the file, saves it to a designated folder on your hard drive, and attaches a new object (req.file) to the request so your route handler can access the file's details.
---
2. Learning Objectives
By the end of this chapter, you will be able to:
-
Install and configure the
multerpackage.
-
Write HTML forms configured for
multipart/form-data.
- Create a Multer storage engine to specify where files should be saved.
- Rename uploaded files dynamically to prevent overwriting.
- Filter uploads to only accept specific file types (e.g., Images only).
-
Access file metadata using
req.file.
---
3. Beginner-Friendly Explanations
The enctype Attribute
If you create an HTML form with a file input, but forget to add enctype="multipart/form-data" to the <form> tag, the browser will literally just send the *name* of the file (e.g., "vacation.jpg") as text, but it will not send the actual image data!
How Multer fits in
Multer acts as route-specific middleware. You don't usually apply it globally usingapp.use(), because not every route expects a file. You inject it directly into the specific POST route where the upload happens, just like the auth middleware from Chapter 24.
---
4. Syntax Explanation
Let's look at the basic setup to accept a single file upload.
```javascript id="ch25-syntax-1" const express = require('express'); const multer = require('multer'); const app = express();
// 1. Tell Multer to save files in the 'uploads/' directory const upload = multer({ dest: 'uploads/' });
// 2. Inject the middleware into the route. // 'avatar' must match the name="" attribute in the HTML input. app.post('/profile', upload.single('avatar'), (req, res) => { // 3. If successful, Multer attaches the file data to req.file console.log(req.file); res.send("File uploaded successfully!"); });
app.listen(3000);
javascript id="ch25-code-1" const multer = require('multer'); const path = require('path');
// Configure Storage const storage = multer.diskStorage({ // Where to save the file destination: (req, file, cb) => { cb(null, 'public/images/'); // Save in public/images }, // What to name the file filename: (req, file, cb) => { // Create a unique name: timestamp + original extension const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); const ext = path.extname(file.originalname); cb(null, file.fieldname + '-' + uniqueSuffix + ext); // Example: avatar-1634567890-12345.jpg } });
// Initialize Multer with our storage config const upload = multer({ storage: storage });
javascript id="ch25-code-2" const fileFilter = (req, file, cb) => { // Check if the file's mimetype starts with 'image/' if (file.mimetype.startsWith('image/')) { cb(null, true); // Accept the file } else { cb(new Error('Invalid file type! Only images are allowed.'), false); // Reject } };
const upload = multer({ storage: storage, fileFilter: fileFilter, limits: { fileSize: 5 * 1024 * 1024 } // 5MB limit });
javascript id="ch25-code-3" // Expect an array of files from the input named 'gallery' (max 5 files) app.post('/album', upload.array('gallery', 5), (req, res) => { // Note: It is req.files (plural) for arrays! console.log(req.files.length + " files uploaded."); res.send("Album saved."); });
json { "fieldname": "avatar", "originalname": "my_vacation.jpg", "encoding": "7bit", "mimetype": "image/jpeg", "destination": "public/images/", "filename": "avatar-1638293829-3829.jpg", "path": "public\\images\\avatar-1638293829-3829.jpg", "size": 150342 }
html id="ch25-mini-project-1" <!DOCTYPE html> <html><body> <h2>Upload Profile Picture</h2> <!-- CRITICAL: enctype --> <form action="/upload" method="POST" enctype="multipart/form-data"> <input type="file" name="profilePic" required> <button type="submit">Upload</button> </form> </body></html>
javascript id="ch25-mini-project-2" const express = require('express'); const multer = require('multer'); const path = require('path');
const app = express(); app.set('view engine', 'ejs'); // Serve the public folder so we can view the uploaded images app.use(express.static('public'));
// Configure Multer Storage const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, 'public/uploads/'), filename: (req, file, cb) => { const uniqueName = Date.now() + path.extname(file.originalname); cb(null, file.fieldname + '-' + uniqueName); } });
// Configure Multer Filter & Limits const upload = multer({ storage: storage, limits: { fileSize: 2 * 1024 * 1024 }, // 2MB Limit fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) cb(null, true); else cb(new Error('Only images allowed!')); } });
app.get('/', (req, res) => res.render('upload'));
// Upload Route (Inject the middleware!)
app.post('/upload', upload.single('profilePic'), (req, res) => {
try {
if (!req.file) return res.status(400).send("No file uploaded.");
// The image is now saved in public/uploads.
// We construct a URL to send back to the user.
const imageUrl = /uploads/${req.file.filename};
res.send(
<h3>Upload Successful!</h3>
<img src="${imageUrl}" width="200" style="border-radius:50%;">
<br><a href="/">Upload another</a>
);
} catch (err) {
res.status(400).send(err.message);
}
});
app.listen(3000, () => console.log('Server running'));
``
---
12. Coding Challenges
Challenge 1: Modify the mini-project form to include an <input type="text" name="caption">. Update the backend to extract req.body.caption and display it below the uploaded image on the success page. (Notice how Multer automatically parses the text fields alongside the file!).
Challenge 2: Write an Express global error handler that specifically catches Multer's "File too large" error and sends a friendly message to the user instead of crashing the server.
---
13. MCQs with Answers
Q1: Which HTML attribute is strictly required for a form to upload files?
A) method="FILE"
B) enctype="multipart/form-data"
C) action="/upload"
D) upload="true"
Answer: B
Q2: What is Multer?
A) A database system.
B) A templating engine.
C) Express middleware for handling multipart/form-data.
D) A tool for hashing passwords.
Answer: C
Q3: Which Multer configuration allows you to define exactly where a file is saved and what its name will be?
A) multer.diskStorage()
B) multer.config()
C) multer.settings()
D) multer.setup()
Answer: A
Q4: If a user uploads a file using upload.single('avatar'), where does Express store the file's metadata?
A) req.body.avatar
B) req.data
C) req.upload
D) req.file
Answer: D
---
14. Interview Questions
-
1.
Why can't express.json()
handle file uploads?
is designed to parse text-based JSON payloads. File uploads consist of massive streams of binary data (images, videos) encoded in multipart/form-data. JSON parsers cannot read or process these binary streams. Multer is required to buffer and write this binary data to the disk.
-
2.
Why is it dangerous to trust
file.originalname when saving files?
*Answer:* Two users might upload a file named image.png. The second upload will overwrite the first user's image on the server. Additionally, hackers might try to upload files with malicious names (like ../../../script.js) to attempt a directory traversal attack. Generating unique, random filenames on the backend mitigates both risks.
---
15. FAQs
Q: Does Multer save the image to MongoDB?
A: No! MongoDB is for text and JSON data. Saving massive binary files directly into a database is extremely inefficient and will bloat the database immediately. You save the physical file to your server's hard drive (via Multer), and you save the URL string (e.g.,
/images/pic1.jpg) into MongoDB.
Q: How do massive companies handle uploads?
A: Large companies (like Netflix) don't save files on the same server that runs Node.js. They use Multer to process the file in memory (
multer.memoryStorage()) and instantly stream it to a cloud storage service like Amazon S3, Azure Blob, or Cloudinary.
---
16. Summary
-
File uploads require the
multipart/form-data encoding type.
-
Multer is the standard Express middleware for handling multipart data.
-
Use
multer.diskStorage() to control filenames and folder destinations.
-
Always generate unique filenames to prevent overwriting.
-
Always use a
fileFilter and size limits for security.
-
Data is accessed via
req.file (or req.files` for arrays).
---
17. Next Chapter Recommendation
We've covered databases, authentication, and file uploads. Our apps are getting complex! With complexity comes bugs. If something breaks in production, how do you fix it without crashing the server? In Chapter 26: Error Handling and Debugging, we will learn how to write a Global Error Handler and use modern debugging techniques.