Storing Uploaded Files and Serving Them in Express
File uploads are a core part of many modern web applications. Profile pictures, PDFs, resumes, product images, invoices, videos, and documents all rely on proper file storage and delivery systems.
In an Express.js application, uploading a file is only half the job. You also need to decide:
Where should the file be stored?
How should users access it?
How do you serve files securely?
Should you use local storage or external cloud storage?
This blog explains how file storage works in Express, how static file serving works, and the best practices for handling uploaded files safely.
Understanding File Uploads in Express
When a user uploads a file from the frontend, the file is sent to the backend using a multipart/form-data request.
Express itself does not handle file uploads directly. Libraries such as:
multerbusboyformidable
are commonly used to process uploaded files.
A typical upload flow looks like this:
User selects a file
Browser sends file to Express server
Express receives the file
File is stored in a folder or cloud storage
Server returns the file URL
Users access the file through that URL
Where Uploaded Files Are Stored
Uploaded files can be stored in different locations depending on the application requirements.
1. Local File Storage
In local storage, files are saved directly inside the project folder on the server.
Example structure:
project/
│
├── uploads/
│ ├── image1.png
│ ├── resume.pdf
│ └── video.mp4
│
├── src/
├── public/
└── server.js
Example using multer:
const multer = require('multer');
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 });
Advantages
Easy to set up
Good for small projects
Fast local access
No external service required
Disadvantages
Files can be lost if server crashes
Difficult to scale across multiple servers
Large uploads consume server disk space
Not ideal for production-scale systems
Local Storage vs External Storage
As applications grow, storing files locally becomes harder to manage.
Local Storage
Files remain inside the server machine.
Best For
Learning projects
Small applications
Development environments
Internal tools
Challenges
Limited storage capacity
Backup management
Server migration issues
Difficult horizontal scaling
External Storage
Files are stored in dedicated cloud storage services.
Popular services include:
Amazon Web Services S3
Cloudinary
Google Cloud Storage
Firebase Storage
Advantages
Highly scalable
Better reliability
CDN support
Automatic backups
Optimized file delivery
Disadvantages
Additional cost
Slightly more setup complexity
Requires API configuration
Serving Static Files in Express
Once files are stored, users need a way to access them.
This is where static file serving comes in.
Express provides built-in middleware called express.static() for serving files publicly.
Example:
const express = require('express');
const app = express();
app.use('/uploads', express.static('uploads'));
This line means:
Physical Folder: uploads/
URL Route: /uploads
How Static File Serving Works
Suppose your folder contains:
uploads/profile.png
And Express uses:
app.use('/uploads', express.static('uploads'));
Then the file becomes accessible at:
http://localhost:3000/uploads/profile.png
The browser sends a request to the URL, and Express directly serves the file from the folder.
Accessing Uploaded Files via URL
A common pattern is returning the uploaded file URL after upload.
Example upload response:
{
"message": "Upload successful",
"fileUrl": "/uploads/1715234123-profile.png"
}
Frontend applications can then use:
<img src="/uploads/1715234123-profile.png" />
Or:
window.open('/uploads/1715234123-profile.png');
This makes uploaded files accessible just like normal web resources.
Complete Example with Multer and Static Files
Install Dependencies
npm install express multer
Folder Structure
project/
│
├── uploads/
├── server.js
└── package.json
server.js
const express = require('express');
const multer = require('multer');
const app = express();
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 });
app.use('/uploads', express.static('uploads'));
app.post('/upload', upload.single('file'), (req, res) => {
res.json({
message: 'File uploaded successfully',
fileUrl: `/uploads/${req.file.filename}`
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Security Considerations for File Uploads
File uploads are a major security risk if handled carelessly.
Never trust uploaded files blindly.
1. Validate File Types
Attackers may upload malicious files like:
.exe.php.batscripts containing malware
Restrict allowed file types.
Example:
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/png', 'image/jpeg'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
};
2. Limit File Size
Large uploads can crash the server or consume excessive storage.
Example:
const upload = multer({
storage,
limits: {
fileSize: 2 * 1024 * 1024
}
});
This limits uploads to 2 MB.
3. Rename Uploaded Files
Never store files using the original filename directly.
Bad:
resume.pdf
Better:
1715234123-resume.pdf
Or use UUIDs.
This prevents filename conflicts and reduces risks.
4. Store Uploads Outside Sensitive Directories
Do not store uploads inside:
src/
config/
node_modules/
Use a dedicated upload folder.
5. Prevent Executable File Access
Avoid allowing uploaded files to execute as code.
For example:
Do not execute uploaded scripts
Restrict server permissions
Serve uploads as static content only
6. Scan Files in Production Systems
Enterprise applications often use antivirus scanning before storing files permanently.
This is especially important for:
PDF uploads
Document uploads
Public platforms
Enterprise systems
Recommended Folder Structure
A cleaner structure improves maintainability.
project/
│
├── public/
│
├── uploads/
│ ├── images/
│ ├── documents/
│ └── videos/
│
├── routes/
├── controllers/
├── middleware/
└── server.js
Benefits:
Easier organization
Better scalability
Cleaner separation of concerns
When to Use Local Storage vs Cloud Storage
| Requirement | Recommended Option |
|---|---|
| Small learning project | Local storage |
| Portfolio project | Local storage |
| Production SaaS app | Cloud storage |
| Large media uploads | Cloud storage |
| Multiple servers | Cloud storage |
| Temporary files | Local storage |
| High scalability | Cloud storage |
Best Practices for File Handling
Recommended Practices
Validate file types
Limit upload size
Rename uploaded files
Organize uploads by folders
Use cloud storage for scalability
Protect sensitive uploads
Remove unused files regularly
Use environment variables for storage configs
Common Mistakes Developers Make
Storing Everything Publicly
Not all uploads should be publicly accessible.
Sensitive documents should require authentication.
Ignoring File Validation
This creates security vulnerabilities quickly.
Using Original Filenames
Filename collisions become inevitable in real applications.
Keeping Large Files on Small Servers
This eventually causes storage and performance issues.
Final Thoughts
Uploading files in Express is straightforward, but handling them correctly requires proper planning.
You need to think beyond just receiving the file:
Where will it be stored?
How will users access it?
How will you secure it?
Can the system scale later?
For small applications, local folder storage with express.static() works well and is easy to understand.
For production systems handling large amounts of data, cloud storage solutions provide better scalability, reliability, and performance.
A strong upload system focuses on:
Organization
Security
Accessibility
Scalability
Performance
