Storing image from React formData to Cloudinary using Node/Express API

storing-image-from-react-formdata-to-cloudinary-using-node/express-api

I won’t waste your time. Chances are if you’ve ended up here you’ve run into the same issue that plagued me for 3 months or you’re reading this because I mentioned it in my Twitter.

Prerequisites:

  1. You’re making a fullstack app using MERN (and using MVC)
  2. You’re trying to send form data that includes image file data in your POST request
  3. Something is wrong with the incoming request, particularly the image data (parsing the path)

I’m going to assume you have everything else working and you understand your folder structure. I used Axios to send my requests from my React frontend to my backend. You don’t need to.

Backend:

  1. Install Cloudinary and Multer dependencies (if you haven’t already)

  2. Grab your Cloundiary CLOUD_NAME, API_KEY and API_SECRET from your Cloudinary dashboard.

    Navigating to your Cloudinary dashboard:

    navigation on cloudinary to dashboard

    cloudname key, api key and api secret key on cloudinary dashboard

  3. Grab these 3 values and save them in your .env.

    cloudname, api key, and secret key saved to variabled in .env file

  4. Set up your Cloudinary middleware file. I named mine cloudinary.js. Here you’ll use your keys for the configuration. Make sure you’re using the right file path for your project to access those keys in your .env file.

    Ex:

    const cloudinary = require("cloudinary").v2;
    
    require("dotenv").config({ path: "./config/.env" });
    
    cloudinary.config({
      cloud_name: process.env.CLOUD_NAME,
      api_key: process.env.API_KEY,
      api_secret: process.env.API_SECRET,
    });
    
    module.exports = cloudinary;
    
  5. Set up your Multer middleware file. Mine is named multer.js and it’s in the same ‘middleware’ file as my cloudinary.js. I used the following configuration:

    const multer = require("multer");
    const path = require("path");
    
    module.exports = multer({
      storage: multer.diskStorage({}),
      fileFilter: (req, file, cb) => {
        let ext = path.extname(file.originalname);
        if (ext !== ".jpg" && ext !== ".jpeg" && ext !== 
    ".png" && ext !== ".PNG") {
          req.fileValidationError = "Forbidden extension";
          return cb(null, false, req.fileValidationError);
          // cb(new Error("File type is not supported"), false);
          // return;
        }
        cb(null, true);
      },
    });
    
  6. Set up your POST route for creating a new post/item/whatever, to grab the image file.
    *** I used the MVC file structure so my routes are in their own ‘routes’ folder. All my routes for posts are in a file called ‘posts.js’.

    You’ll add upload.single() after the URL in your POST route. Note that I have multer listening for my image data that will come through under the name “file”. You can name yours whatever you want but it needs to match what you named that input in your form in the frontend.

    const express = require("express");
    const router = express.Router();
    const postsController =  require("../controllers/posts")
    const upload = require("../middleware/multer")
    
    router.post("https://dev.to/addPost", upload.single("file"), postsController.addPost)
    
    module.exports = router;
    

    For reference, here’s what my input looks like in my React frontend. I’ve set the name to “file”, as well.

    Image description

  7. Set up your controller function for adding the new item. I have my controller functions in a post.js file in a controller folder.

    I used destructuring to grab my values. You don’t have to do this. You can just use req.body.myProperty, for example.

    NOTE: All values that are NOT the file data are preceded with req.body. To grab the file path you can destructure the way I did or you use req.file.path.

    const Post = require("../models/Post");
    const mongoose = require("mongoose");
    const cloudinary = require("../middleware/cloudinary");
    
    module.exports = {
        addPost: async (req, res) => {
    const { prompt, media, size, canvas, description } = 
    req.body
    
           const { path } = req.file;
        },
    }
    
  8. Right under that, I ran my body of code through a try/catch block. Since you’ll need the unique image URL and the CloudinaryID to save to your db, you’ll want to do that first and wait for it to return a result.

    try{
        const result = await cloudinary.uploader.upload(path);
    }catch(err){
        res.status(400).json({err: err.message})
        console.error(err)
    }
    
  9. After awaiting the result from cloudinary, we’ll want to create our new post. I have a property in my schema for file, which holds the result.secure_url (this is the unique URL for my image), and cloudinaryId, holding result.public_id — the id for that image now stored in my media library in cloudinary. I need the id in order to delete later on.

    let newPost = await Post.create({
               prompt: prompt,
               media: media,
               size: size,
               canvas: canvas,
               file: result.secure_url,//don't forget to append secure_url to the result from cloudinary
               cloudinaryId: result.public_id,//append publit_id to this one you need it to delete later
               description: description,
               user: req.user.id,
    });
    

    The whole code block for my addPost function looks like this:

    addPost: async (req, res) => {
         const { prompt, media, size, canvas, description } = req.body
    
            const { path } = req.file;
    
            try{
                const result = await cloudinary.uploader.upload(path);
    
                let newPost = await Post.create({
                    prompt: prompt,
                    media: media,
                    size: size,
                    canvas: canvas,
                    file: result.secure_url,//don't forget to append secure_url to the result from cloudinary
                    cloudinaryId: result.public_id,//append publit_id to this one you need it to delete later
                    description: description,
                    user: req.user.id,
                });
                res.status(200).json(newPost)
            }catch(err){
                res.status(400).json({err: err.message})
                console.error(err)
         }
     },
    

    Okay, hopefully, that’s working for you. I’m sorry if you get red text in the console screaming at you.

Frontend:

  1. Install Axios (if you want) and import it.

    import axios from "axios";
    
  2. Make sure you have your form coded out. Here’s a simplified example of what I had in my AddPostForm React component:
    (I left out the arrays I was mapping through to populate my selection elements)

    const AddPostForm = () => {
    return(
      
    ) } export default AddPostForm
  3. Import useRef from ‘react’, call it at the top of your component to declare a ref, and add it to your form element.

    If you don’t know much about the useRef hook and/or have never used it before you can read more about it here. I got this tip from a senior dev who took a look at my code with me. He suggested I use useRef to grab my form data.

    import { useRef } from 'react';
    const AddPostForm = () => {
    const formRef = useRef();
    
        return( 
             
    ....
    ) } export default AddPostForm
  4. Declare and define your handleSubmit function.
    Create a new FormData object and pass in the ref we declared earlier, appending the current property.

    const handleSubmit = async (e) => {
        e.preventDefault()
        const formData = new FormData(formRef.current)
    }
    
  5. Send and await the result of the POST request. I set mine within a try/catch block. I honestly don’t know if it’s necessary but I’m so used to doing it now.

  • The first argument in axios’ post() is the URL of your POST route. Make sure yours matches whatever you’ve set it as.

  • The second argument is the data you want to send off. In this case, the formData we grabbed earlier.

NOTE: With Axios you don’t have to append .json() to parse the response as JSON because Axios does that for you.

NOTE2: After getting the response I reset the form data with: formRef.current.reset();

```
const handleSubmit = async (e) => {
 e.preventDefault()
 const formData = new FormData(formRef.current)

 try {
    const res = await axios.post('/post/addPost', formData)
    console.log(res.data)
    formRef.current.reset();
}catch (err){
    console.log(err.response.data.err)
   }
}
```

I hope to God this worked for you. Aside from Axios I was also using React’s state container library, Redux. Redux holds the state, in this case, my posts, and makes them available globally after wrapping my App.js in a context. It’s not necessary to send the form data but it makes it so I don’t have to manually refresh the page to see updates to my DB.

I was thinking about posting more about how I solve issues but dealing with markdown text editors is more of a pain than it’s worth and I swear I spent more time editing this to get it formatted just the way I wanted it than I did actually figuring out my steps and writing them out.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
monorepo-vs-microrepo:-how-to-choose-the-best-repository-structure-for-your-code

Monorepo vs Microrepo: How to Choose the Best Repository Structure for Your Code

Next Post
diagramming-and-database-design

Diagramming and Database Design📊🔍💡

Related Posts