← Back to Tutorials
Node.js

Build a File Upload Server

Difficulty: Intermediate Est. Time: ~2 hours

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

Continue Learning

Try these tutorials:

  • Build a REST API
  • Build a Todo App with Auth
  • Build a Rate Limiter