Mean 16 File Upload
title: "File Upload — Handling File Uploads in the MEAN Stack" description: "Implement file upload in the MEAN Stack using Multer with Express, store files locally or in cloud storage, and build Angular upload components." weight: 26 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, mean]
File upload is a common requirement in web applications. In the MEAN stack, Multer handles file uploads on the Express side, and Angular provides the upload UI.
What You'll Learn
You will implement file upload with Multer, serve uploaded files, integrate with cloud storage, and build Angular components with progress tracking.
Why It Matters
File upload is required for user avatars, product images, document management, and many other features. Proper implementation ensures security and good user experience.
Real-World Use
DodaZIP's file sharing portal uses Multer with S3 storage for file uploads, supporting drag-and-drop, chunked uploads for large files, and progress bars.
flowchart LR
A[Angular Upload] --> B[Multipart Request]
B --> C[Multer Middleware]
C --> D[File Validation]
D --> E[Save File]
E --> F[Local Storage]
E --> G[Cloud Storage S3]
E --> H[Return File URL]
H --> A
style C fill:#4a90d9,color:#fff
Multer Setup
Install and configure Multer for file uploads.
npm install multer
// backend/config/upload.js
const multer = require('multer');
const path = require('path');
// Storage configuration
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
// File filter
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and PDF are allowed.'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
}
});
module.exports = upload;
Expected output: Multer configured with disk storage, file type filtering, and size limits. Files are saved to the uploads/ directory with unique names.
Upload Route
Create an Express route for file upload.
// backend/routes/uploadRoutes.js
const express = require('express');
const router = express.Router();
const upload = require('../config/upload');
const { authMiddleware } = require('../middleware/auth');
// Single file upload
router.post('/single', authMiddleware, (req, res) => {
upload.single('file')(req, res, (err) => {
if (err) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large. Maximum size is 5MB.' });
}
return res.status(400).json({ error: err.message });
}
if (!req.file) {
return res.status(400).json({ error: 'No file provided' });
}
res.status(201).json({
filename: req.file.filename,
originalName: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype,
url: `/uploads/${req.file.filename}`
});
});
});
// Multiple file upload
router.post('/multiple', authMiddleware, (req, res) => {
upload.array('files', 5)(req, res, (err) => {
if (err) {
return res.status(400).json({ error: err.message });
}
const files = req.files.map(file => ({
filename: file.filename,
originalName: file.originalname,
size: file.size,
url: `/uploads/${file.filename}`
}));
res.status(201).json({ files });
});
});
Expected output: POST /api/upload/single accepts a single file. POST /api/upload/multiple accepts up to 5 files. Both return file metadata and URLs.
Serving Static Files
Make the uploads directory accessible from the browser.
// backend/server.js
const path = require('path');
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
Expected output: Files in the uploads/ directory are accessible at /uploads/filename.jpg. Angular can use the URL directly in img src attributes.
Angular Upload Component
Build an Angular component with file selection and progress tracking.
// src/app/components/file-upload/file-upload.component.ts
import { Component, EventEmitter, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-file-upload',
standalone: true,
imports: [CommonModule],
template: `
<div class="upload-container"
(dragover)="onDragOver($event)"
(drop)="onDrop($event)">
<input
type="file"
#fileInput
(change)="onFileSelected($event)"
accept="image/jpeg,image/png,image/gif,application/pdf"
[attr.multiple]="multiple ? '' : null"
style="display: none"
/>
<div *ngIf="!uploading" class="drop-zone" (click)="fileInput.click()">
<p>Drag and drop files here or click to browse</p>
<p style="font-size: 12px; color: #666">
Supported: JPEG, PNG, GIF, PDF (max 5MB each)
</p>
</div>
<div *ngIf="uploading" class="progress-container">
<p>Uploading... {{ progress }}%</p>
<div class="progress-bar">
<div class="progress-fill" [style.width.%]="progress"></div>
</div>
</div>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="uploadedFiles.length" class="file-list">
<div *ngFor="let file of uploadedFiles" class="file-item">
<img *ngIf="isImage(file.mimetype)"
[src]="environment.apiUrl + '/../' + file.url"
width="80" height="80" />
<span>{{ file.originalName }}</span>
<span>{{ (file.size / 1024).toFixed(1) }} KB</span>
</div>
</div>
</div>
`,
styles: [`
.drop-zone { border: 2px dashed #ccc; padding: 40px; text-align: center; cursor: pointer; }
.drop-zone:hover { border-color: #4a90d9; }
.progress-bar { background: #eee; height: 8px; border-radius: 4px; }
.progress-fill { background: #4a90d9; height: 100%; border-radius: 4px; transition: width 0.3s; }
.error { color: red; }
.file-list { margin-top: 16px; }
.file-item { display: flex; align-items: center; gap: 12px; padding: 8px; }
`]
})
export class FileUploadComponent {
@Output() uploadComplete = new EventEmitter<any[]>();
uploading = false;
progress = 0;
error: string | null = null;
uploadedFiles: any[] = [];
multiple = true;
constructor(private http: HttpClient) {}
onFileSelected(event: any) {
const files = event.target.files;
if (files.length) this.uploadFiles(files);
}
onDragOver(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
}
onDrop(event: DragEvent) {
event.preventDefault();
const files = event.dataTransfer?.files;
if (files?.length) this.uploadFiles(files);
}
uploadFiles(files: FileList) {
const formData = new FormData();
const endpoint = files.length > 1 ? `${environment.apiUrl}/upload/multiple` : `${environment.apiUrl}/upload/single`;
for (let i = 0; i < files.length; i++) {
formData.append(files.length > 1 ? 'files' : 'file', files[i]);
}
this.uploading = true;
this.error = null;
this.progress = 0;
const req = new HttpRequest('POST', endpoint, formData, {
reportProgress: true
});
this.http.request(req).subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.progress = Math.round(100 * event.loaded / (event.total || 1));
} else if (event.type === HttpEventType.Response) {
const result = event.body as any;
this.uploadedFiles = result.files || [result];
this.uploading = false;
this.uploadComplete.emit(this.uploadedFiles);
}
},
error: (err) => {
this.uploading = false;
this.error = err.error?.error || 'Upload failed';
}
});
}
isImage(mimetype: string): boolean {
return mimetype.startsWith('image/');
}
}
Expected output: A drag-and-drop file upload component with progress bar, file type validation, preview for images, and support for single and multiple files.
Common Mistakes
Not validating file types on the server: Client-side validation can be bypassed. Always validate file types and sizes on the server.
Not setting file size limits: Without size limits, users can upload very large files that exhaust server storage and bandwidth.
Serving uploads from the Node.js process: For production, use a CDN or cloud storage (S3, Cloudinary). Node.js is not optimized for serving static files.
Not cleaning up failed uploads: If validation fails after the file is partially saved, remove the incomplete file to prevent orphaned files.
Not checking for existing files with the same name: Use unique filenames to prevent overwriting existing uploads.
Practice Questions
- What is Multer and what does it do?
Multer is a Node.js middleware for handling multipart/form-data, primarily used for file uploads.
- How do you limit file size in Multer?
Set limits.fileSize in the Multer configuration. The value is in bytes.
- How do you track upload progress in Angular?
Use HttpRequest with reportProgress: true. Listen for HttpEventType.UploadProgress events.
- How do you serve uploaded files from Express?
Use express.static() middleware pointing to the uploads directory.
- How do you validate file types in Multer?
Implement a fileFilter function that checks file.mimetype and calls cb(null, true) or cb(new Error()).
Challenge
Build a complete file upload system with: image upload (JPEG, PNG, GIF only, max 2MB), document upload (PDF only, max 10MB), thumbnail generation for images using Sharp, and a file gallery component in Angular.
Frequently Asked Questions
{{< faq "How do I secure uploaded files?" >> Store files outside the public directory. Serve them through an authenticated Express route instead of static middleware. {{< /faq >}}
Mini Project
Build a user avatar upload system with: Multer configuration for images only, avatar resize with Sharp, Angular upload component with crop preview, and avatar display on the user profile page.
What's Next
Implement {{< ilink "error handling middleware" "Error Handling Middleware" > }} for consistent API error responses.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro