Build a REST API with Node.js: Upload an Image File

Subscribe to my newsletter and never miss my upcoming articles

Hello everyone! Welcome back to the 5th part of Let's Build a Node.js REST API Series. We are so close to finishing this API. Let's not waste any more time and begin!

If you are new to this series, please check out the previous articles to follow along:

  1. Designing and Planning the API
  2. Routes and Controllers
  3. Integrating MongoDB Atlas
  4. Finalizing Controllers

In the previous article, we finally have all our controller functions done and working. Our API can GET, POST and DELETE our tea objects that consists of:

PropertiesDescriptionType
namethe tea nameString
imagean image urlString
descriptionthe descriptionString
keywordswords associated with the teaString
origincountry where the tea is first madeString
brew_timetime to brew in minutesNumber
temperaturebest temperature in Celsius to drinkNumber
commentsany comments posted about the teaArray of String

However, in the previous article, I set the image property to just "dummy". In this article, we shall work on setting up this correctly.

Step 1: Install multer and import

For images, we don't just supply a text string like "name" but a file, an image file to be exact. And our image property is a String that will be the path of our uploaded image file.

Simply typing "/myImage.png" in our req.body.image will not work because that path does not exist. We need to upload our image with multer, a node.js middleware useful for uploading files.

Install multer by running:

npm install --save multer

Then let's import multer to our controllers/tea.js file:

const multer = require('multer');

Step 2: Create Storage

Still in our controllers/tea.js file, we add the following code to create a storage where our uploaded images will be stored.

We use multer.diskStorage() and include its 2 properties:

  • destination: the path where the images will be stored. We shall set it as './uploads'.
  • filename: determines the name that would be saved in storage. We can just keep it as its original name.

Here's what it should look like:

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, './uploads');
      },
    filename: function (req, file, cb) {
        cb(null, file.originalname);
    }
});

Remember to create an 'uploads' folder in your root directory so it actually exists for the image to be stored there.

Step 3: Upload Image function

Below our const storage, we can initialize multer with multer() and pass storage in its storage property. Next, we have a .single() method which ensures that multer will accept only one file and store it as req.file.

The code will be:

const uploadImg = multer({storage: storage}).single('image');

In our newTea function, we have to change our image property to req.file.path instead of req.body.image because we want image to be our image file's path, not a string from req.body.image.

const newTea = new Tea({
     name:req.body.name,
     image: req.file.path,  //update this
     description: req.body.description,
     keywords: req.body.keywords,
     origin: req.body.origin,
     brew_time: req.body.brew_time,
     temperature: req.body.temperature,
})

Now we just have to export uploadImg to use in our routes/tea.js and include it as middleware. So include this function in our module.exports at the bottom, along with the rest.

module.exports = {
    getAllTea,
    uploadImg,  //include the new guy
    newTea,
    deleteAllTea,
    getOneTea,
    newComment,
    deleteOneTea
};

Now head over to our routes/tea.js file, find the POST /tea route and add uploadImg before newTea.

router.post('/tea', teaController.uploadImg /*insert this guy*/ , teaController.newTea);

Let's test it!

Let's try to POST a new tea with POSTman. Make sure the method is set to POST and the url is correct. Supply some values for each property. For image, set it to 'file' instead of text then upload an image.

set to file.PNG

POSTman should return our new tea object data with our image property saved as a path to our image.

POST image to uploads.PNG

If we check in our 'uploads' folder, the image that we uploaded should be there. That means it works! We can upload images to our tea object.

picture in uploads.PNG

What about GET?

It's pointless to be able to POST if you cannot GET the image right?

Let's try to get the image by entering http://localhost:3000/uploads/green.png as the url in POSTman and set the method to GET. You should see this error being returned:

GET fail.PNG

Why is this so?

Our 'uploads' folder cannot be accessed publicly and therefore the server cannot GET our image. To fix this, we have to make our uploads folder a static file.

Go to server.js and add this line of code:

app.use('/uploads', express.static('./uploads'));

Now let's retry that test on POSTman and it should return the image correctly.

GET tea.PNG

Congratulations!

Our API is now fully working and built! All there's left to do is to add some security and deploy it for use! That will be in our next and final part of the series. Thank you for reading and following this series, I hope it has been helpful. Stay tuned for the final part. In the meantime, please ask questions or concerns in the comments and refer to the resources below. Cheers!


Further Reading

Notes: All images used in this API is from freepik.com

Bolaji Ayodeji's photo

Yet another amazing one! 🤩

Victoria Lo's photo

Many thanks Bolaji 🙏

Fernando Torres's photo

Hi again!

There's a typo on Step 3 on this post. It's a missing closing parenthesis.

The code should be:

const uploadImg = multer({storage: storage}).single('image');
Rajat Verma's photo

Amazing one!!!

Richard Harris's photo

👏👏👏

Subha Chanda's photo

Awesome! Are you going to cover JWT?

Victoria Lo's photo

I was planning to... but I wanted to make it simple for beginners so this series only covers the basics on how to build an API.

Thanks for the input. I'll write a separate article on ways to protect API routes in detail if you don't mind :)