Skip to main content
Express.js Tutorial
CHAPTER 17 Beginner

File Uploads and Storage Handling

Updated: May 14, 2026
40 min read

# CHAPTER 17

File Uploads and Storage Handling

1. Introduction

Handling text data (like a username) via JSON or URL-Encoded forms is straightforward. However, these formats cannot handle binary data like JPEGs, PDFs, or MP4s. When a user uploads a profile picture, the client must change its transmission format to multipart/form-data. Express cannot parse multipart data natively. In this chapter, we will use an industry-standard package called Multer to intercept file uploads, validate them, save them securely, and return the file paths.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Install and configure the multer middleware.
  • Handle single and multiple file uploads.
  • Implement security filters to restrict file types and file sizes.
  • Store file paths dynamically in a database.

3. Beginner-Friendly Explanation

Imagine a post office (Your API). Usually, customers mail standard letters (JSON/Text). Your normal sorting machine (express.json()) processes these easily. One day, a customer arrives with a massive, heavy wooden crate (An Image Upload). The normal letter-sorting machine will jam and break if it tries to process a crate. You need a special forklift to handle crates. Multer is the forklift. When an HTTP request arrives designated as multipart/form-data, Express steps aside, Multer intercepts the heavy crate, unpacks the image, places it securely in your warehouse (the server's hard drive), and hands you a tiny piece of paper with the aisle number (the file path).

4. Step 1: Installing and Configuring Multer

First, create an uploads folder in your public directory (as configured in Chapter 8).

Install:

bash
1
npm install multer

Creating the Multer Configuration (middleware/upload.js):

javascript
123456789101112131415161718192021222324252627282930313233
const multer = require('multer');
const path = require('path');

// 1. Configure where the file goes and what to name it
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        // Save files to our public uploads folder
        cb(null, 'public/uploads/');
    },
    filename: (req, file, cb) => {
        // Create a unique, hack-proof filename: timestamp + random number + original extension
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, uniqueSuffix + path.extname(file.originalname));
    }
});

// 2. Filter out bad files (Only accept Images)
const fileFilter = (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) {
        cb(null, true);
    } else {
        cb(new Error('Invalid file type! Please upload only images.'), false);
    }
};

// 3. Initialize Multer
const upload = multer({ 
    storage: storage,
    limits: { fileSize: 1024 * 1024 * 5 }, // 5 MB size limit
    fileFilter: fileFilter
});

module.exports = upload;

5. Step 2: Handling the Upload Route

Now, we inject our Multer forklift directly into the Express route chain.

routes/userRoutes.js:

javascript
123456789101112131415161718192021222324252627282930
const express = require('express');
const router = express.Router();
const upload = require('../middleware/upload');

// The 'upload.single('avatar')' middleware intercepts the file!
router.post('/upload-profile-pic', upload.single('avatar'), async (req, res) => {
    try {
        // Multer automatically processes the file and attaches it to 'req.file'
        if (!req.file) {
            return res.status(400).json({ error: "Please upload a file" });
        }

        // The path where the image was physically saved on the hard drive
        const imagePath = `/uploads/${req.file.filename}`;

        // Example: Save this text path to the database
        // await User.findByIdAndUpdate(req.user.id, { profilePic: imagePath });

        res.status(200).json({ 
            status: "success", 
            message: "File uploaded successfully!",
            url: imagePath 
        });

    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

module.exports = router;

6. Step 3: Handling Multiple Files

If a user is uploading an album, they will send multiple files at once. Change the middleware from .single() to .array().
javascript
1234567
// Accept up to 5 files under the field name 'gallery'
router.post('/upload-album', upload.array('gallery', 5), (req, res) => {
    // Multer attaches an array of files to 'req.files' (Notice the 's'!)
    const filePaths = req.files.map(file => `/uploads/${file.filename}`);
    
    res.json({ urls: filePaths });
});

7. Backend Workflow: Testing Uploads in Postman

You cannot test file uploads using raw JSON in Postman.
  1. 1. Open Postman. Set to POST.
  1. 2. Go to the Body tab.
  1. 3. Select form-data (NOT raw JSON).
  1. 4. In the Key column, type avatar (this must match the string in upload.single('avatar')).
  1. 5. CRITICAL: Hover over the Key cell, and a hidden dropdown will appear. Change it from "Text" to "File".
  1. 6. In the Value column, click "Select Files" and choose an image from your computer. Click Send.

8. Cloud Storage vs Local Storage

In this chapter, we saved files to the local hard drive using multer.diskStorage. This is perfect for learning and small apps. However, if your app scales horizontally to 5 servers, local storage breaks (an image uploaded to Server A cannot be seen by users routed to Server B). Professional enterprise APIs use Multer alongside cloud plugins (like multer-s3) to instantly route the uploaded binary stream directly to Amazon Web Services S3.

9. Best Practices

  • Security Check - Filenames: NEVER trust the original filename the user uploaded. A hacker might upload a file named ../../../etc/passwd. Multer's diskStorage engine allows you to generate a secure, randomized filename (as seen in Step 1) ensuring hackers cannot overwrite critical server files.

10. Common Mistakes

  • Mixing JSON and Files: If a route expects multipart/form-data for an upload, and the frontend accidentally sends pure JSON, req.file will be undefined. Vice-versa, if you send form-data to a standard route relying solely on express.json(), req.body will be completely blank! The parser must match the payload type.

11. Exercises

  1. 1. Explain the architectural reasoning behind saving a *text string* (the file path) in the MongoDB database, rather than saving the actual image file itself inside the database.

12. Coding Challenges

  • Challenge: Modify the Multer configuration in Step 1 so that it accepts .pdf files instead of images, and changes the upload size limit to 10 MB.

13. MCQs with Answers

Question 1

In an Express application, which HTTP transmission format must the client use when uploading binary files (like images)?

Question 2

When Multer successfully intercepts and saves a single uploaded file, where does it attach the file's metadata (like the newly generated filename and destination path) so the Controller logic can access it?

14. Interview Questions

  • Q: Explain the purpose of the multer middleware in an Express API. Why can't the standard express.json() middleware process an image upload?
  • Q: What are the security risks associated with allowing users to upload files to your server? How does our Multer configuration mitigate these risks?

15. FAQs

Q: My users are uploading 20MB images and crashing the server! A: You must enforce strict limits. In the Multer configuration (Step 1), we added limits: { fileSize: 1024 * 1024 * 5 }. This forcefully rejects any file larger than 5MB before it even finishes uploading, saving your server's RAM and bandwidth.

16. Summary

In Chapter 17, we expanded our Express server's capabilities beyond text. By utilizing the multer middleware, we successfully parsed multipart/form-data, implemented rigorous security filters to restrict file types and sizes, and generated randomized, collision-proof filenames. Finally, we extracted the physical file paths to be securely stored in our database.

17. Next Chapter Recommendation

You have mastered every individual component of Express.js. Now, it is time to build a portfolio project. Proceed to Chapter 18: Building a Complete Express.js Project.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·