Storing Uploaded Files and Serving Them in Express

File uploads are a core feature in modern web applications.
Whether users are uploading profile pictures, documents, resumes, or media files — your backend needs a reliable way to store, manage, and serve those files back when needed.
If you're working with Node.js and Express, understanding how file storage works is essential for building real-world applications.
In this article, we’ll explore where uploaded files are stored, how Express serves them, how users access them via URLs, and what security practices you should follow.
Why File Handling Matters
When a user uploads a file, your backend must answer a few critical questions:
Where should this file be stored?
How will we access it later?
How do we serve it efficiently?
How do we prevent unsafe uploads?
Poor handling can lead to:
Broken file links
Server crashes
Security vulnerabilities
Data loss
So let’s break it down step by step.
Where Uploaded Files Are Stored
When using Express, uploaded files are typically stored in one of the following:
Local server storage (file system)
External storage services (cloud-based)
The simplest approach is local storage, especially for learning and small projects.
Example Folder Structure
Here:
uploads/is where user files are storedFiles are organized into categories (images, documents)
Upload Storage Folder Structure (Diagram)
This keeps your storage organized and scalable.
Local Storage vs External Storage
Choosing where to store files depends on your application scale.
Local Storage
Files are stored directly on your server.
Example path:
/uploads/images/profile.png
Advantages:
Simple to implement
No external dependencies
Fast for small apps
Limitations:
Not scalable
Risk of data loss if server crashes
Difficult to handle multiple servers
External Storage (Cloud)
Examples:
AWS S3
Cloudinary
Firebase Storage
Advantages:
Highly scalable
Reliable and redundant
CDN support for fast delivery
Limitations:
Requires setup
Costs involved
Slight complexity increase
When to Use What
Use local storage for learning and small apps
Use cloud storage for production-level applications
Handling File Uploads in Express
To upload files in Express, we commonly use middleware like:
npm install multer
Basic Setup
const express = require("express");
const multer = require("multer");
const path = require("path");
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/images");
},
filename: function (req, file, cb) {
const uniqueName = Date.now() + path.extname(file.originalname);
cb(null, uniqueName);
}
});
const upload = multer({ storage: storage });
app.post("/upload", upload.single("image"), (req, res) => {
res.send("File uploaded successfully");
});
app.listen(3000);
This stores uploaded files inside uploads/images.
Serving Static Files in Express
Uploading files is only half the job.
You also need to serve them so users can access them via URL.
Express provides a built-in way to serve static files.
Static Middleware
app.use("/uploads", express.static("uploads"));
This means:
Any file inside uploads/ can be accessed via:
http://localhost:3000/uploads/<filename>
Example:
http://localhost:3000/uploads/images/profile.png
Now your uploaded files are publicly accessible.
Static File Serving Flow
This is exactly how images, PDFs, and files are served.
Accessing Uploaded Files via URL
Once static serving is enabled:
If file is stored as:
uploads/images/17123456789.png
You can access it via:
http://localhost:3000/uploads/images/17123456789.png
This URL can be:
Stored in database
Sent to frontend
Used in
<img>tags
Example:
<img src="http://localhost:3000/uploads/images/17123456789.png" />
Important Concept: Public vs Private Files
By default, static serving makes files publicly accessible.
But not all files should be public.
Examples of sensitive files:
User documents
Private PDFs
Personal data
For such cases:
Do NOT expose via static middleware
Serve files through protected routes
Add authentication checks
Security Considerations for File Uploads
File uploads can be risky if not handled carefully.
Here are essential practices.
Validate File Type
Only allow specific file types.
fileFilter: function (req, file, cb) {
if (file.mimetype.startsWith("image/")) {
cb(null, true);
} else {
cb(new Error("Only images allowed"), false);
}
}
Limit File Size
Prevent large uploads.
limits: { fileSize: 2 * 1024 * 1024 } // 2MB
Use Unique File Names
Avoid overwriting files.
Already handled with:
Date.now()
Avoid Executable Files
Never allow:
.exe.js.sh
These can be dangerous.
Store Outside Public Folder (if needed)
Sensitive files should not be directly accessible.
Sanitize File Names
Prevent path traversal attacks.
Use HTTPS in Production
Protect file transfer.