Build a File Upload Server
Introduction
File uploading is a fundamental feature in many web applications. From profile pictures to document management, understanding how to handle file uploads is essential for any developer.
In this tutorial, you'll build a file upload server with Node.js that can handle single and multiple file uploads, validate file types, and manage uploaded files.
What You'll Build
- Single file upload endpoint
- Multiple file upload endpoint
- File type validation
- File size limiting
- File download endpoint
What You'll Learn
- Multer middleware for file handling
- File validation and sanitization
- File system operations
- Security best practices
Project Overview
Our file upload server will provide these endpoints:
API Endpoints
- POST /api/upload/single - Upload one file
- POST /api/upload/multiple - Upload multiple files
- GET /api/upload/:filename - Download a file
- DELETE /api/upload/:filename - Delete a file
Prerequisites
- Node.js installed
- Basic JavaScript knowledge
- Code editor
Project Setup
Bash
# Create project
mkdir file-upload-server
cd file-upload-server
npm init -y
# Install dependencies
npm install express multer cors dotenv
Project Structure
Structure
file-upload-server/
├── uploads/ # Uploaded files directory
├── routes/
│ └── upload.js
├── middleware/
│ └── upload.js
├── .env
├── server.js
└── package.json
Setting Up Multer
Multer is Node.js middleware for handling multipart/form-data, which is used for file uploads.
JavaScript
// middleware/upload.js
const multer = require('multer');
const path = require('path');
const fs = require('fs');
# Ensure uploads directory exists
const uploadDir = './uploads';
if (!fs.existsSync(uploadDir)){
fs.mkdirSync(uploadDir);
}
# Configure storage
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
# Create unique filename: fieldname-timestamp.extension
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname))
}
});
# File filter function
const fileFilter = (req, file, cb) => {
# Allowed file types
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif',
'application/pdf', 'text/plain'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, PDF, and TXT allowed.'), false);
}
};
# Create multer instance
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024 # 5MB limit
},
fileFilter: fileFilter
});
module.exports = upload;
Upload Routes
Now let's create the upload routes.
JavaScript
// routes/upload.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const upload = require('../middleware/upload');
// @route POST /api/upload/single
// @desc Upload single file
// @access Public
router.post('/single', upload.single('file'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ message: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
}
});
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// @route POST /api/upload/multiple
// @desc Upload multiple files
// @access Public
router.post('/multiple', upload.array('files', 5), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ message: 'No files uploaded' });
}
const files = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size
}));
res.json({
message: 'Files uploaded successfully',
count: files.length,
files: files
});
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// @route GET /api/upload/:filename
// @desc Download file
// @access Public
router.get('/:filename', (req, res) => {
const filename = req.params.filename;
const filepath = path.join(__dirname, '../uploads', filename);
if (fs.existsSync(filepath)) {
res.download(filepath);
} else {
res.status(404).json({ message: 'File not found' });
}
});
// @route DELETE /api/upload/:filename
// @desc Delete file
// @access Public
router.delete('/:filename', (req, res) => {
const filename = req.params.filename;
const filepath = path.join(__dirname, '../uploads', filename);
if (fs.existsSync(filepath)) {
fs.unlinkSync(filepath);
res.json({ message: 'File deleted successfully' });
} else {
res.status(404).json({ message: 'File not found' });
}
});
module.exports = router;
File Management
Let's create the main server file.
JavaScript
// server.js
require('dotenv'.config());
const express = require('express');
const cors = require('cors');
const path = require('path');
const app = express();
const uploadRoutes = require('./routes/upload');
# Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
# Serve uploaded files statically
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
# Routes
app.use('/api/upload', uploadRoutes);
# Error handling for multer
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ message: 'File too large. Max 5MB allowed.' });
}
return res.status(400).json({ message: err.message });
} else if (err) {
return res.status(400).json({ message: err.message });
}
next();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Security Considerations
Important security practices for file uploads.
Security Best Practices
- Validate file types - Don't trust file extensions
- Limit file size - Prevent DoS attacks
- Rename files - Never use original filenames
- Store outside web root - Or use cloud storage
- Scan for malware - In production environments
Production Enhancements
- Use cloud storage (AWS S3, Cloudinary)
- Generate signed URLs
- Add authentication
- Implement virus scanning
- Use secure file serving
Testing the Application
Bash
# Start the server
node server.js
# Upload single file using curl
curl -X POST http://localhost:3000/api/upload/single \
-F "file=@/path/to/image.jpg"
# Upload multiple files
curl -X POST http://localhost:3000/api/upload/multiple \
-F "files=@file1.txt" \
-F "files=@file2.txt"
# Download file
curl http://localhost:3000/api/upload/filename.jpg
# Delete file
curl -X DELETE http://localhost:3000/api/upload/filename.jpg
Testing Checklist
- ✓ Single file upload works
- ✓ Multiple file upload works
- ✓ File type validation works
- ✓ File size limit enforced
- ✓ File download works
- ✓ File deletion works
Summary
Congratulations! You've built a complete file upload server.
What You Built
- Single File Upload - POST endpoint
- Multiple File Upload - Up to 5 files
- File Validation - Type and size limits
- File Management - Download and delete
Next Steps
- Add user authentication
- Integrate cloud storage (AWS S3)
- Add image resizing
- Implement file compression