Storing Uploaded Files and Serving Them in Express
Handling file uploads is one of those features every backend developer eventually builds—whether it’s profile pictures, PDFs, videos, or datasets. In a Node.js application using Express.js, this involves two key responsibilities:
Accepting and storing uploaded files
Serving those files back to users efficiently and securely
This detailed blog walks you through the entire process—from setup to best practices—so you can implement file handling in real-world applications.
Why File Upload Handling Matters
Modern applications rely heavily on user-generated content:
Social media apps → images & videos
Job portals → resumes (PDFs)
E-learning platforms → assignments
Analytics systems → datasets
Without proper file handling:
Your server can crash due to large uploads
Security vulnerabilities can arise
Files may not be accessible properly
Prerequisites
Before we begin, ensure you have:
Basic knowledge of Node.js and Express
npm installed
A project initialized
Step 1: Setting Up the Project
Create a new project:
mkdir file-upload-app
cd file-upload-app
npm init -y
Install dependencies:
npm install express multer
Here:
express → Web framework
multer → Middleware for handling file uploads
Step 2: Project Structure
A clean structure helps maintain scalability:
file-upload-app/
│
├── uploads/ # Stored files
├── public/ # Static assets
├── app.js # Main server file
└── package.json
Step 3: Basic Express Server
const express = require("express");
const app = express();
app.listen(3000, () => {
console.log("Server running on port 3000");
});
Step 4: Handling File Uploads with Multer
Multer is the most popular library for handling multipart/form-data.
Basic Setup
const multer = require("multer");
// Storage configuration
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 });
Step 5: Upload Route
app.post("/upload", upload.single("file"), (req, res) => {
res.send("File uploaded successfully!");
});
Explanation:
upload.single("file")→ Handles one file"file"→ Name attribute in HTML form
HTML Form for Upload
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
Step 6: Storing Files
Files are stored in the uploads/ directory.
Example filename:
1714828392-myfile.png
Why rename files?
Avoid duplicates
Improve traceability
Prevent overwriting
Step 7: Serving Uploaded Files
Now comes the second part—serving files to users.
Static Serving
app.use("/uploads", express.static("uploads"));
Now any file can be accessed via:
http://localhost:3000/uploads/filename.png
Example
If a file is stored as:
uploads/1714828392-photo.jpg
You can access it at:
http://localhost:3000/uploads/1714828392-photo.jpg
Step 8: File Validation & Security
File uploads are a major security risk if not handled properly.
1. File Type Validation
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/png" || file.mimetype === "image/jpeg") {
cb(null, true);
} else {
cb(new Error("Only images allowed!"), false);
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter
});
2. File Size Limit
const upload = multer({
storage: storage,
limits: { fileSize: 2 * 1024 * 1024 } // 2MB
});
3. Preventing Malicious Files
Avoid:
.exe,.sh,.batfilesUnknown MIME types
Use libraries like:
helmet
express-validator
Step 9: Multiple File Uploads
app.post("/uploads", upload.array("files", 5), (req, res) => {
res.send("Multiple files uploaded!");
});
"files"→ input name5→ max number of files
Step 10: Uploading Different File Types
Example: Images + PDFs
const fileFilter = (req, file, cb) => {
const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("Invalid file type"), false);
}
};
Step 11: Storing Files in Cloud (Advanced)
Instead of storing locally, you can use:
Amazon S3
Cloudinary
Benefits:
Scalability
Better performance
CDN support
Step 12: Serving Files Securely
Sometimes you don’t want files publicly accessible.
Controlled Access Route
app.get("/file/:name", (req, res) => {
const filePath = `uploads/${req.params.name}`;
res.sendFile(__dirname + "/" + filePath);
});
Add Authentication
You can restrict access using:
JWT authentication
Session-based login
Step 13: Performance Optimization
1. Use Streaming
const fs = require("fs");
app.get("/stream/:name", (req, res) => {
const stream = fs.createReadStream(`uploads/${req.params.name}`);
stream.pipe(res);
});
2. Use Caching
app.use("/uploads", express.static("uploads", {
maxAge: "1d"
}));
Step 14: Testing File Uploads
Use tools like:
Postman
cURL
Step 15: Real-World Use Cases
1. Social Media App
Upload profile pictures
Serve images via CDN
2. Resume Portal
Upload PDF resumes
Restrict access to recruiters
3. Data Analytics Platform
Upload CSV datasets
Process files asynchronously
Common Mistakes
❌ 1. Not validating files
Leads to security issues
❌ 2. Storing files in root directory
Always use separate folders
❌ 3. No size limits
Can crash your server
❌ 4. Exposing sensitive files publicly
Use controlled routes
Best Practices
Always validate file type and size
Use unique filenames
Store metadata in a database
Use cloud storage for production
Implement authentication for access control
Conclusion
Handling file uploads and serving them efficiently is a critical backend skill. With Express.js and Multer, you can build robust systems that:
Accept user files securely
Store them efficiently
Serve them reliably
As your application scales, consider moving to cloud storage solutions and adding advanced security layers.
Final Thoughts
Mastering file uploads in Node.js opens doors to building real-world applications like:
Instagram-like platforms
E-learning systems
SaaS dashboards
