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

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

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.

We have installed it in the previous part so 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 and do the following:

  • Remove these lines, as we don't need them anymore.
    const multer = require('multer');
    const upload = multer();
    
  • Find the POST /tea route and replace upload.none() to teaController.uploadImg.

The final route should look like:

router.post('/tea', teaController.uploadImg , 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

Interested in reading more such articles from Victoria Lo?

Support the author by donating an amount of your choice.

Recent sponsors
Bolaji Ayodeji's photo

Yet another amazing one! 🀩

Victoria Lo's photo

Many thanks Bolaji πŸ™

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 :)

Richard Harris's photo

πŸ‘πŸ‘πŸ‘

Rajat Verma's photo

Amazing one!!!

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');
Chuong Tang's photo

Love it! Thanks for the very thorough tutorial .

Victoria Lo's photo

Thanks for reading! Feel free to ask questions if you are unsure of anything πŸ˜ƒ

Sidharth Kumar's photo

Hi Victoria Lo , Awesome tutorial.Its really very good tut. I have problem while using image upload from postman. Only when i use form-data method 'post' in POSTMAN i'm getting error while using image type file. Getting this error name": "ValidatorError", "message": "Path name is required.". But all the pervious code with out image is working fine with raw data with json data type. I try to debuge it .It happens only when i use form-data then in code req.body.name is not working but while using raw data using json its working. Should i need to use this

const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }))

app.use(bodyParser.json())

Screenshot from 2021-02-02 19-52-51.png Can you please help me upload image using form-data using POSTMAN ?

Show +1 replies
Sidharth Kumar's photo

Hi Victoria Lo,

Its working. Thanks a lot, Simple error on my side on routes. The way you explain the tutorial is amazing. Thanks once again for your support.

Victoria Lo's photo

Glad it's all good now Sidharth Kumar! Feel free to ask any questions if there's anything you're unsure of.

Thanks for reading and following along the series! Hope you'll get to build your own amazing API after this :)

M. Γ‡ağlar TUFAN's photo

Hey there! That's a good Express REST API tutorial. I came here for only upload part of the application and I found this useful, thank you for your effort. I have a question if you spare few minutes. I implemented the same logic in my application. The task that my router should do is called after uploadImage middleware called, so If any error occurs in my router due to validation, but the application still uploads the image. I removed uploadImage middleware and did the upload part inside my router if validations are passed succesfully. Is this the correct method? And lastly, I couldn't see any way to implement async/await for upload method into the uploadImage logic, I checked out multer's API and couldn't find a method that does the same job but returns Promise. My current implementation has a callback hell, I'm not pretty happy about it. What do you think about it? Thanks in advance and keep amazing work! :)