Uploading files without using Multer or any other library/module

Vikas SinghVikas Singh
5 min read

Introduction:

As me and my friend was working on my project "ChatAndCode", I stumbled upon handling file upload. Then after searching on internet, I found the popular module called Multer. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboyfor maximum efficiency.

But I want to handle this thing by my own, don't want to use the external module and library. Then I came to know about that node.js has the same thing in the form of buffer and its file system.

So in this Blog i am going to show you how you can do this, by using the power of node.js.

Basic Setup

First set up your project. Note that this is not the basic tutorial to set up the project, so i am assuming you already know that.

npm init -y
npm install express

Folder structure

please note that this is my folder structure, my main file is app.js. also i have made new folder uploads where all uploaded files will be stored there.


<!-- this is the index.html file. that serves the page on the browser -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>We are going to upload a file</h1>
    <input id="file" type="file">
    <button onclick='handleClick()'>Send File</button>
</body>

<script>
    function handleClick() {
        let input = document.getElementById("file");
        let file = input.files[0];

        if (!file) {
            alert("Please select a file.");
            return;
        }

        const reader = new FileReader();
        reader.onload = async () => {
            let fileMsg = {
                name: file.name,
                fileData: reader.result.split(',')[1] // Remove the base64 prefix
            };

            try {
                const response = await fetch('/upload', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(fileMsg)
                });

                if (response.ok) {
                    alert("File uploaded successfully");
                } else {
                    alert("File upload failed");
                }
            } catch (error) {
                console.error("Error uploading file:", error);
                alert("An error occurred while uploading the file.");
            }
        };
        reader.readAsDataURL(file);
    }
</script>
</html>
// app.js file
// This is the main file, which handles the upload route

const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const fs = require("fs");

const app = express();

let myStaticDir = path.join(__dirname, "/public");
console.log(myStaticDir);

app.use(bodyParser.json({ limit: '50mb' })); // Increase limit to handle large files
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); // Even nested objects are parsed into json

// Serve static files from the 'public' directory
app.use(express.static(myStaticDir));

// Endpoint to handle file upload
app.post('/upload', (req, res) => {
    const { name, fileData } = req.body;

    if (!name || !fileData) {
        return res.status(400).send('No file data received.');
    }

    const filePath = path.join(__dirname, 'uploads', name);
    const fileBuffer = Buffer.from(fileData, 'base64');

    fs.writeFile(filePath, fileBuffer, (err) => {
        if (err) {
            console.error("Error saving file:", err);
            return res.status(500).send('Error saving file.');
        }
        res.send('File uploaded successfully.');
    });
});

let port = 8080;
app.listen(port, () => {
    console.log(`I am listening at ${port}`);
});

Breakdown of Upload route

  • This line defines a new POST route at /upload. When a POST request is sent to /upload, the function provided will be executed.

  • req represents the incoming request.

  • res represents the response that will be sent back to the client.

      const { name, fileData } = req.body;
    
  • This line extracts the name and fileData properties from the request body.

  • name is expected to be the filename.

  • fileData is expected to be the file content encoded in base64.

      if (!name || !fileData) {
          return res.status(400).send('No file data received.');
      }
    
    • This checks if either name or fileData is missing.

    • If either is missing, it responds with a 400 status code and an error message "No file data received." and exits the function early.

        const filePath = path.join(__dirname, 'uploads', name);
      
      • This constructs the full path where the file will be saved.

      • __dirname is a built-in Node.js variable that contains the directory of the current module.

      • path.join is used to safely concatenate directory and file name into a full path.

          const fileBuffer = Buffer.from(fileData, 'base64');
        

        This converts the base64 encoded file data into a Buffer object which is suitable for writing to a file.

          fs.writeFile(filePath, fileBuffer, (err) => {
        
        • fs.writeFile is a function from the Node.js fs (file system) module.

        • It writes the Buffer data to the specified file path.

        • It takes a callback function to handle any errors and the completion of the write operation.

            if (err) {
                console.error("Error saving file:", err);
                return res.status(500).send('Error saving file.');
            }
            res.send('File uploaded successfully.');
          
          • If there is an error writing the file, it logs the error and sends a 500 status code with an error message.

          • If the file is written successfully, it sends a success message "File uploaded successfully."

what does the Buffer Do?

A Buffer in Node.js is a way of handling binary data directly. This is particularly useful for tasks like file handling, network communications, and dealing with binary streams. Buffers are part of the buffer module, which is built into Node.js.

  1. Handles Binary Data:

    • Buffers provide a way to store and manipulate raw binary data.

    • Unlike JavaScript strings, which are encoded in UTF-16, buffers represent raw sequences of bytes.

  2. Fixed-Length Chunk of Memory:

    • Buffers allocate a fixed amount of memory, which is efficient for handling data streams (like files, network protocols) where you need to manipulate data directly.

Conversions Between Different Formats:

Buffers can easily convert between different encodings, like UTF-8, base64, hex, etc.

Conclusion

Its up to you ,what suits you best. but its worth learning about something new. If you have read so far congratulations to you. Thankyou.

0
Subscribe to my newsletter

Read articles from Vikas Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vikas Singh
Vikas Singh