All guides

Uploading PDF Files to a Server with Node.js

In modern web applications, handling file uploads is a common requirement. This article will guide you through implementing PDF file upload functionality using Node.js.

Prerequisites

Before we begin, ensure you have:

  • Node.js installed on your system
  • Basic knowledge of JavaScript and Node.js
  • A code editor of your choice

Setting Up the Project

First, initialize a new Node.js project and install the required dependencies:

mkdir pdf-upload-server
cd pdf-upload-server
npm init -y
npm install express multer

Basic Server Setup

Create a file named server.js and set up a basic Express server:

const express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");

const app = express();
const PORT = process.env.PORT || 3000;

// Create uploads directory if it doesn't exist
const uploadsDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadsDir)) {
  fs.mkdirSync(uploadsDir, { recursive: true });
}

app.use(express.static("public"));
app.use(express.json());

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "public", "index.html"));
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Configuring Multer for File Uploads

Multer is a middleware for handling multipart/form-data, which is primarily used for file uploads. Let's configure it to handle PDF files:

// Configure storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    // Generate unique filename with timestamp
    const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
    cb(
      null,
      file.fieldname + "-" + uniqueSuffix + path.extname(file.originalname)
    );
  },
});

// File filter to allow only PDFs
const fileFilter = (req, file, cb) => {
  if (file.mimetype === "application/pdf") {
    cb(null, true);
  } else {
    cb(new Error("Only PDF files are allowed!"), false);
  }
};

// Initialize multer
const upload = multer({
  storage: storage,
  fileFilter: fileFilter,
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB limit
  },
});

Implementing the Upload Endpoint

Now, let's create the endpoint that handles PDF uploads:

// Single file upload endpoint
app.post("/upload", upload.single("pdfFile"), (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({
        success: false,
        message: "No file uploaded or invalid file type",
      });
    }

    // File uploaded successfully
    res.json({
      success: true,
      message: "File uploaded successfully",
      file: {
        filename: req.file.filename,
        originalname: req.file.originalname,
        size: req.file.size,
        path: req.file.path,
      },
    });
  } catch (error) {
    console.error("Upload error:", error);
    res.status(500).json({
      success: false,
      message: "Error uploading file",
    });
  }
});

// Multiple files upload endpoint
app.post("/upload-multiple", upload.array("pdfFiles", 5), (req, res) => {
  try {
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({
        success: false,
        message: "No files uploaded or invalid file types",
      });
    }

    const uploadedFiles = req.files.map((file) => ({
      filename: file.filename,
      originalname: file.originalname,
      size: file.size,
      path: file.path,
    }));

    res.json({
      success: true,
      message: "Files uploaded successfully",
      files: uploadedFiles,
    });
  } catch (error) {
    console.error("Upload error:", error);
    res.status(500).json({
      success: false,
      message: "Error uploading files",
    });
  }
});

Error Handling Middleware

Add error handling middleware to catch Multer errors:

// Error handling middleware
app.use((error, req, res, next) => {
  if (error instanceof multer.MulterError) {
    if (error.code === "LIMIT_FILE_SIZE") {
      return res.status(400).json({
        success: false,
        message: "File too large. Maximum size is 10MB.",
      });
    }
  }

  if (error.message === "Only PDF files are allowed!") {
    return res.status(400).json({
      success: false,
      message: error.message,
    });
  }

  res.status(500).json({
    success: false,
    message: "Internal server error",
  });
});

Creating a Simple Frontend

Create a public directory and add an index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>PDF Upload</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 600px;
        margin: 50px auto;
        padding: 20px;
      }
      .upload-form {
        border: 2px dashed #ccc;
        padding: 30px;
        text-align: center;
        margin-bottom: 20px;
      }
      .success {
        color: green;
      }
      .error {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Upload PDF Files</h1>

    <div class="upload-form">
      <h3>Single File Upload</h3>
      <form id="singleUploadForm" enctype="multipart/form-data">
        <input type="file" name="pdfFile" accept=".pdf" required />
        <button type="submit">Upload PDF</button>
      </form>
    </div>

    <div class="upload-form">
      <h3>Multiple Files Upload (Max 5)</h3>
      <form id="multipleUploadForm" enctype="multipart/form-data">
        <input type="file" name="pdfFiles" accept=".pdf" multiple required />
        <button type="submit">Upload PDFs</button>
      </form>
    </div>

    <div id="message"></div>

    <script>
      // Handle single file upload
      document
        .getElementById("singleUploadForm")
        .addEventListener("submit", async (e) => {
          e.preventDefault();
          const formData = new FormData(e.target);
          await uploadFile(formData, "/upload");
        });

      // Handle multiple files upload
      document
        .getElementById("multipleUploadForm")
        .addEventListener("submit", async (e) => {
          e.preventDefault();
          const formData = new FormData(e.target);
          await uploadFile(formData, "/upload-multiple");
        });

      async function uploadFile(formData, endpoint) {
        const messageDiv = document.getElementById("message");
        messageDiv.innerHTML = "Uploading...";
        messageDiv.className = "";

        try {
          const response = await fetch(endpoint, {
            method: "POST",
            body: formData,
          });

          const result = await response.json();

          if (result.success) {
            messageDiv.innerHTML = result.message;
            messageDiv.className = "success";

            if (result.file) {
              messageDiv.innerHTML += `<br>File: ${result.file.originalname}`;
            } else if (result.files) {
              messageDiv.innerHTML += `<br>Files uploaded: ${result.files.length}`;
            }
          } else {
            messageDiv.innerHTML = result.message;
            messageDiv.className = "error";
          }
        } catch (error) {
          console.error("Error:", error);
          messageDiv.innerHTML = "Upload failed";
          messageDiv.className = "error";
        }
      }
    </script>
  </body>
</html>

Running the Server

To start your server, run:

node server.js

Visit http://localhost:3000 in your browser to test the PDF upload functionality.

Additional Features

You can enhance this basic implementation with:

  1. File Validation: Add more robust validation for PDF files
  2. Database Integration: Store file metadata in a database
  3. Authentication: Add user authentication for secure uploads
  4. File Processing: Integrate with PDF processing libraries
  5. Cloud Storage: Upload files to cloud storage services like AWS S3

Security Considerations

  • Always validate file types on the server side
  • Implement proper authentication and authorization
  • Sanitize filenames to prevent directory traversal attacks
  • Consider virus scanning for uploaded files
  • Set appropriate file size limits

Conclusion

This implementation provides a solid foundation for handling PDF file uploads in Node.js. The code demonstrates how to set up a server, configure file uploads with Multer, create upload endpoints, and build a simple frontend interface. You can extend this basic setup based on your specific requirements and security needs.

Remember to always handle file uploads securely and validate all user inputs to protect your application from potential security vulnerabilities.