Skip to main content

File uploading using multer

Background

File upload is a common task in web development, that allows users to upload files to a server, then the server will store the file in a specific folder, and the user can retrieve the file from the server by hitting the route.

The file can be an image, video, audio, pdf, etc. There are many ways to upload a file, and many libraries are available, but in this doc, we will learn how to upload a file using the multer library, store the file in the server, and also how to retrieve the file from the server.

What is multer?

Multer is node.js middleware for handling multipart/form-data, which is primarily used for uploading files to a server. multer is a kind of industry standard for handling file uploads in node.js.

Let's use it

First of all, create a basic express app, if you don't know how to create a basic express app, read the Basic Code docs.

Install multer

Install the multer library which will be used to upload the file.

npm install multer

Create a middleware

We need to create a storage function to store the file on the server, we need to pass the destination and filename to the storage function which will be used to store the file on the server.

upload.js
import multer from "multer";

// Here we are configuring how multer will store the file
const storage = multer.diskStorage({
// detination is used to determine within which folder 📁 the uploaded files would be stored
destination: (req, file, cb) => {
// ⚠️ public folder 📁 must be created in the root directory of the project
cb(null, "public/"); //
},
// filename is used to determine what the file should be named inside the folder
filename: (req, file, cb) => {
cb(null, file.originalname); // For now, we are using the original name of the file
},
});

// upload variable is a middleware that will be used in the route to store the file in the server
const upload = multer({ storage });

export default upload;

We passed the destination and filename to the multer.diskStorage function, and the multer.diskStorage function returns a middleware that will be used to store the file in the server.

  • destination: Used to determine within which folder the uploaded files should be stored. The callback function accepts two arguments, the first is an error, and the second is the path where the files should be stored.
  • filename: Used to determine what the file should be named inside the folder. The callback function accepts two arguments, the first is an error, and the second is the name of the file. If an error occurs, you can pass it to the callback function.

The upload variable is a middleware that will be used in the route to store the file in the server.

caution

The public folder must be created in the root directory, otherwise, it will throw an error. You can change the name of the folder, but make sure to change the name in the destination function as well.

note

I am storing files on the server, but you normally store files on cloud storage like AWS S3, Google Cloud Storage, etc. We'll cover this in another doc.

Create a route to upload the file

Now, we need to create a route to upload the file, we will use the upload.single("file") middleware to upload the file, and the file is the name of the file that we will send from the frontend.

index.js
import express from "express";
import upload from "./upload.js"; // import the upload middleware

const app = express();

app.use(express.json());
// This is required to parse the form data, must for file upload
app.use(express.urlencoded());

// This is the route to upload the file
app.post("/upload", upload.single("file"), (req, res) => {
// req.file contains information of the uploaded file, like file name, path, etc
const downloadLink = `${req.protocol}://${req.get("host")}/${req.file.path}`;
// link will be like http://localhost:3000/public/IMG_20190720_123456.jpg

// Return the download link to the client
res.send("File uploaded successfully. Download link: " + downloadLink);
});

app.listen(3000, () => {
console.log("Server is running on port 3000");
});

Here, we are using the upload.single("file") middleware to upload the file, and the file is the name of the file that we will send from the frontend. And there are other methods as well, like:

  • upload.array("file", 3): This method is used to upload multiple files, and the second parameter is the maximum number of files that can be uploaded.
  • upload.fields([{ name: "file", maxCount: 1 }]): This method is used to upload multiple files, and the second parameter is an array of objects, and each object contains the name of the file and the maximum number of files that can be uploaded.
  • upload.none(): This method is used to upload only text fields and no files.
  • upload.any(): This method is used to upload any type of file.
  • upload.single("file"): This method is used to upload a single file.

Create a route to download the file

So, the file is being uploaded to the server, in stored in the public folder, but how can we retrieve the file from the server? We need to create a route to retrieve the file from the server. We can use the express.static middleware to create a route to retrieve the file from the server.

What is express.static?

express.static is a built-in middleware in express used to create a GET route that takes the file name as a parameter, and serves static files, such as images, CSS files, and JavaScript files from the server.

To create a route using express.static middleware, we need to pass the path of the folder where the files are stored, like this:

index.js
app.use("/public", express.static("public"));

// here, the first parameter is the route, and the second parameter is the path of the folder where the files are stored
// So, now we can access the files using the GET http://localhost:3000/public/<file-name> route

Now, for example, the public folder has a file named exmaple.jpg, then we can access the file using the GET http://localhost:3000/public/example.jpg route.

Let's do this modification in the index.js file, we need to add the following code:

index.js
import express from "express";
import upload from "./upload.js";

const app = express();

app.use("/public", express.static("public"));

app.use(express.json());
// This is required to parse the form data, must for file upload
app.use(express.urlencoded());

// This is the route to upload the file
app.post("/upload", upload.single("file"), (req, res) => {
const downloadLink = `${req.protocol}://${req.get("host")}/${req.file.path}`;
// link will be like http://localhost:3000/public/IMG_20190720_123456.jpg

// Return the download link to the client
res.send("File uploaded successfully. Download link: " + downloadLink);
});

app.listen(3000, () => {
console.log("Server is running on port 3000");
});

As already mentioned, it will create a route GET /public to retrieve the file stored in the public folder. For example, if we have a file named IMG_20190720_123456.jpg in the public folder, then we can retrieve the file by hitting the route http://localhost:3000/public/IMG_20190720_123456.jpg

Testing

Both routes are created, now let's test the routes.

Upload the file

First, hit the route to upload the file, and the response will be the link to retrieve the file from the server.

cURL of the route to upload the file
curl -X POST \
http://localhost:3000/upload \
-H 'Content-Type: multipart/form-data' \
-F file=@/Users/username/Downloads/IMG_20190720_123456.jpg
What is cURL?

What is cURL?

cURL is a command-line tool for transferring data using various protocols. In this example, we are using cURL to hit the route to upload the file. You can use any tool to hit the route, like Postman, Insomnia, etc.

If you didn't understand the cURL command, then you can use Postman to hit the route. It's not very important to understand the cURL command, but if you want to learn more about cURL, then you can check out this cURL tutorial.

To upload the file from Postman, use the following steps:

  1. Click on the Body section
  2. Select the form-data option in the body section
  3. Type file in the key field
  4. Select the data type as file from the dropdown
    info

    You need to hover over the data type dropdown to see the file option (it's not visible by default)

  5. Select the file from your computer

img.png

Hit the send button, and you will get the response like this:

Response of the route to upload the file
{
"message": "File uploaded successfully. Download link: http://localhost:3000/public/IMG_20190720_123456.jpg"
}

Retrieve the file

Now, hit the route to retrieve the file from the server, and you will get the file in the response.

cURL of the route to retrieve the file
curl -X GET \
http://localhost:3000/public/IMG_20190720_123456.jpg

Example

Let's create a simple example to upload a file and retrieve the file from the server, which will also store the file's information in the database. Let's start by creating a new project.

Create a new project

First, create a new folder for the project, and then open the terminal in that folder, and run the following command to create a new project:

npm init -y

Install the required libraries

npm install express multer mongoose
npm install -D nodemon

Modify the package.json file, and the final version will be like this:

package.json
{
"name": "upload-file",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"author": "@mrizwanashiq",
"license": "ISC",
"dependencies": {
"express": "^4.17.2",
"mongoose": "^6.5.0",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}

Create Multer middleware middlewares/upload.js

Create a new folder named middlewares and create a new file named upload.js inside the middlewares folder. Then, add the following code to the upload.js file.

middlewares/upload.js
import multer from "multer";
import path from "path";

const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./public");
},
filename: (req, file, cb) => {
const fileName =
path
.basename(file.originalname, path.extname(file.originalname))
.toLowerCase()
.replace(/\s+/g, "-") +
"-" +
Date.now().toString() +
path.extname(file.originalname).toLowerCase();
cb(null, fileName);
},
});

export default multer({ storage });

Here, I made some changes in the filename function, to make the file name unique, if I don't do this, and on the server, I have a file named abc.jpg, and if I upload another file named abc.jpg, then the file will be overwritten (replaced). So, to avoid this,

I am doing the following:

  1. Converted the file name to lowercase i.e. Abc to abc

  2. Replaced the spaces with - i.e. abc def to abc-def

  3. Added the current timestamp to the file name i.e. abc-def to abc-def-1626780000000

    What is timestamp?

    Timestamp is the number of milliseconds since January 1, 1970, 00:00:00 UTC. For example, if the current date is 2022-03-10T00:00:00.000Z, then the timestamp will be 1641708800000. You can check the timestamp of the current date using the following code:

    console.log(Date.now()); // 1626780000000

    Why I am using it? Because it will change every time I upload a file, so it will be unique.

  4. Added the extension to the file name i.e. abc-def-1626780000000 to abc-def-1626780000000.jpg

So, if the original file name is Abc def.jpg, then the new file name will be abc-def-1626780000000.jpg, and if I upload another file named Abc def.jpg, then the new file name will be abc-def-1626780000001.jpg. So, it will be unique, even if I upload the same file multiple times.

caution
Create a public folder

You must have to create a folder named public in the root directory of the project, otherwise, you will get an error.

Create models/file.js file

Create a new folder named models and create a new file named file.js inside the models folder. Then, add the following code to the file.js file.

models/file.js
import mongoose from "mongoose";

const schema = mongoose.Schema({
name: { type: String, required: true }, // file name
original_name: { type: String, required: true }, // original file name
path: { type: String, required: true }, // path to the file in the server
type: { type: String, required: true }, // type of the file (image, video, audio, etc)
link: { type: String }, // link to the file in the server
});

export default mongoose.model("File", schema);

You see, I am storing both, the original file name, and the new file name. The original file name is the file name that the user uploaded, and the new file name is the file name that I am generating using the filename function in the middlewares/upload.js file.

Create routes/file.js file

Create a new folder named routes and create a new file named file.js inside the routes folder. Then, add the following code to the file.js file.

routes/file.js
import express from "express";
const router = express.Router();
import upload from "../middlewares/upload.js";
import FileModel from "../models/file.js";

router.get("/", async (req, res) => {
try {
const data = await FileModel.find();
res.status(200).json(data);
} catch (error) {
res.status(500).send(error);
}
});

router.get("/:id", async (req, res) => {
try {
const data = await FileModel.findById(req.params.id);
res.status(200).json(data);
} catch (error) {
res.status(500).send(error);
}
});

router.post("/", upload.single("file"), async (req, res) => {
try {
if (req.file) {
req.file.path = req.file.path.replace(/\\/g, "/"); // replace \\ with / in the file path
// It's not necessary, it will just make the file path look good to the user
// For example, if the file path is public\\abc-def-1626780000000.jpg, then it will be public/abc-def-1626780000000.jpg
// If you don't want to do this, then you can remove this line

const file = {
link: `${req.protocol}://${req.get("host")}/${req.file.path}`,
name: req.file.filename,
original_name: req.file.originalname,
type: req.file.mimetype,
path: req.file.path,
};
const data = await FileModel.create(file);
res.status(200).json(data);
} else {
res.status(400).send("no file found");
}
} catch (error) {
res.send(error);
}
});

router.patch("/:id", async (req, res) => {
try {
if (req.file) {
req.file.path = req.file.path.replace(/\\/g, "/");

const file = {
link: `${req.protocol}://${req.get("host")}/${req.file.path}`,
name: req.file.filename,
original_name: req.file.originalname,
type: req.file.mimetype,
path: req.file.path,
};
const data = await FileModel.findByIdAndUpdate(req.params.id, file);
res.status(200).json(data);
} else {
res.status(400).send("no file found");
}
} catch (error) {
res.status(500).send(error);
}
});

router.delete("/:id", async (req, res) => {
try {
const data = await FileModel.findByIdAndRemove(req.params.id);
res.status(200).json(data);
} catch (error) {
res.status(500).send(error);
}
});

export default router;

Create an index.js file

index.js
import express from "express";
import mongoose from "mongoose";
import fileRouter from "./routes/file.js";

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/public", express.static("public"));
app.use("/file", fileRouter);

const connection = mongoose.connection;
connection.once("connected", () => console.log("Database Connected ~"));
connection.on("error", (error) => console.log("Database Error: ", error));
mongoose.connect("mongodb://127.0.0.1:27017/db");

app.listen(2022, () => console.log("Server Started ~"));

Run the server

Run the following command to run the server.

npm run dev

Test the API

Now, we can test the API using Postman.

Upload a file

Upload a file using the POST method on the http://localhost:2022/file endpoint. img_2.png

It will return the following response:

Response
{
"name": "abc-1678349955235.jpg",
"original_name": "abc.jpg",
"path": "public/abc-1678349955235.jpg",
"type": "image/jpeg",
"link": "http://localhost:2022/public/abc-1678349955235.jpg",
"_id": "640996831f050a91f0bb5432",
"__v": 0
}

Get all files

Get all files using the GET method on the http://localhost:2022/file endpoint.

img_3.png

It will return the following response:

Response
[
{
"_id": "6409901ffafa38bc1220630a",
"name": "abc-1678348319784.jpg",
"original_name": "abc.jpg",
"path": "public/abc-1678348319784.jpg",
"type": "image/jpeg",
"link": "http://localhost:2022/public/abc-1678348319784.jpg",
"__v": 0
},
{
"_id": "640996831f050a91f0bb5432",
"name": "abc-1678349955235.jpg",
"original_name": "abc.jpg",
"path": "public/abc-1678349955235.jpg",
"type": "image/jpeg",
"link": "http://localhost:2022/public/abc-1678349955235.jpg",
"__v": 0
}
]

Get a file

Get a file using the GET method on the http://localhost:2022/file/:id endpoint.

img_4.png

It returns a response like this:

Response
{
"_id": "6409901ffafa38bc1220630a",
"name": "abc-1678348319784.jpg",
"original_name": "abc.jpg",
"path": "public/abc-1678348319784.jpg",
"type": "image/jpeg",
"link": "http://localhost:2022/public/abc-1678348319784.jpg",
"__v": 0
}

Same way, you can test the PATCH and DELETE methods.

Code

You can download the related code from here.

Conclusion

In this tutorial, we have learned how to upload a file using multer library, store the file in the server, and also how to retrieve the file from the server.