How to handle File Uploads (images, videos, pdf) using Multer in Express.js Node.js

Published On

In this guide we are going to handle File Uploads like images, videos, pdf in Node.js Express application using multer middleware. Also we will checkout how to get file metadata and filter out file and delete the files.

Getting Started

Let’s set up our nodejs application. I have setup a basic express typescript starter code.

Download starter code

Clone the starter code branch of the repo or directly download the zip

Extract it and install the packages.

For the development mode you can run

terminal
pnpm run dev

Setting up multer

Install the multer package which can be used for handling multipart/form-data

terminal
pnpm i multer

pnpm i @types/multer -D

Multer middleware

Now create a middlewares folder in the src folder and create a multer.ts file

src/middlewares/multer.ts
Github
import multer from "multer";
import { customAlphabet } from "nanoid";
import path from "node:path";

const customId = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 8);

const storage = multer.diskStorage({
  destination: "uploads/",
  filename: (req, file, cb) => {
    const fileExtension = path.extname(file.originalname);
    const fileName = `${customId()}${fileExtension}`;
    cb(null, fileName);
  },
});

export const multerMiddleware = multer({ storage });

In this we are setting up multer and using the diskStorage strategy. That means that file will be stored in the disk. Another strategy is to use the memory (RAM) but I think disk is better choice.

In this destination is set to uploads. You can change is as you want. Also for the security purposes I am changing the name of the files. You can choose to store the original name but there are some security risk involved in it.

I am using nanoid v3 to generate a 8 character random file name.You can choose to increase the length if you are serving more requests. I chose to only use digits and alphabets to generate the filename. The random id hence generated will be url safe.

Setting up upload POST route

Let’s add an upload route to handle file uploads.

To your existing src/index.ts add a new route.

src/index.ts
Github
app.post("/upload", async (req, res) => {
  return res.json({ success: true });
});

Now we have upload route. Let’s add the multer middleware define above.

src/index.ts
Github
import { multerMiddleware } from "./middlewares/multer";

app.post("/upload", multerMiddleware.single("file"), async (req, res) => {
  return res.json({ success: true });
});

Getting file

Getting the file is very easy. Just access it via req.file

src/middlewares/multer.ts
Github
import { multerMiddleware } from "./middlewares/multer";

app.post("/upload", multerMiddleware.single("file"), async (req, res) => {
  const file = req.file;

  return res.json({ success: true });
});

Now that we can access the file let’s refine the route

src/index.ts
Github
app.post("/upload", multerMiddleware.single("file"), async (req, res) => {
  try {
    const file = req.file;

    if (!file) {
      return res.status(400).json({
        error: { code: "no_file", message: "No file uploaded" },
      });
    }

    return res.json({
      data: {
        filePath: `/uploads/${file.filename}`,
      },
      message: "File uploaded successfully",
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      error: { code: "server_error", message: "Internal server error" },
    });
  }
});

File metadata

We can get various information about the file like size, mimetype, filename, etc.

src/index.ts
Github
const fileName = file.filename;
const fileSize = file.size;
const mimetype = file.mimetype;
console.log("filename", fileName);
console.log("fileSize", fileSize);
console.log("mimetype", mimetype);

You can use this info to control the uploads. For example you can only allow images to be uploaded with size less than 10MB. Let’s enfore these constraints.

src/index.ts
Github
app.post("/upload", multerMiddleware.single("file"), async (req, res) => {
  try {
    const file = req.file;

    if (!file) {
      return res.status(400).json({
        error: { code: "no_file", message: "No file uploaded" },
      });
    }

    if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
      return res.status(415).json({
        error: { code: "invalid_file", message: "Invalid File" },
      });
    }

    // 10MB
    if (fileSize > 10000000) {
      return res.status(413).json({
        error: { code: "file_size_limit", message: "File size limit exceeded" },
      });
    }
    return res.json({
      data: {
        filePath: `/uploads/${file.filename}`,
      },
      message: "File uploaded successfully",
    });
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      error: { code: "server_error", message: "Internal server error" },
    });
  }
});

Deleting the file

For deleting the file we can use the fs module from node and pass the file path.

src/index.ts
fs.unlink(file.path, (err) => {
  console.error("error while deleting file", err);
});

Uploading images to cloudinary

Let’s make a new endpoint and implement functionality to upload image to cloudinary.

Install cloudinary package

terminal
npm i cloudinary

Now we need three values from the cloudinary dashboard: cloud name, API key and API secret. Paste these values into the env file.

Let’s create a new route and initialize cloudinary

Upload route

index.ts
Github
app.post(
  "/upload-cloudinary",
  multerMiddleware.single("file"),
  async (req, res) => {
    try {
      const file = req.file;
      if (!file) {
        return res.status(400).json({
          error: { code: "no_file", message: "No file uploaded" },
        });
      }
      const fileSize = file.size;
      const mimetype = file.mimetype;

      if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
        fs.unlink(file.path, (err) => {
          if (err) {
            console.error("error while deleting file", err);
          }
        });
        return res.status(415).json({
          error: { code: "invalid_file", message: "Invalid File" },
        });
      }

      if (fileSize > 5000000) {
        fs.unlink(file.path, (err) => {
          if (err) {
            console.error("error while deleting file", err);
          }
        });
        return res.status(413).json({
          error: {
            code: "file_size_limit",
            message: "File size limit exceeded",
          },
        });
      }

      const cloudinaryResponse = await cloudinary.uploader.upload(
        file.path,
        {},
        (err) => {
          if (err) {
            console.error("error while uploading file", err);
          }
        }
      );

      fs.unlink(file.path, (err) => {
        if (err) {
          console.error("error while deleting file", err);
        }
      });

      return res.json({
        data: {
          url: cloudinaryResponse.secure_url,
        },
        message: "File uploaded successfully",
      });
    } catch (error) {
      console.error(error);
      return res.status(500).json({
        error: { code: "server_error", message: "Internal server error" },
      });
    }
  }
);

Conclusion

So in this post we implmented the file uploads functionality using Multer in Express.js (Node.js).

For reference please refer to the repo. If you are facing any error you can join the EverythingCS discord server.