Integrating Cloud Storage for Image Uploads
Tags: cloud, opensource, react, node
Published: 2023-12-13 07:00:10

In my last [post](https://dev.to/amnish04/progress-with-cloudinary-5pp), I discussed about my progress in figuring out how to integrate [Cloudinary](https://cloudinary.com/ip/gr-sea-gg-brand-home-base?utm_source=google&utm_medium=search&utm_campaign=goog_selfserve_brand_wk22_replicate_core_branded_keyword&utm_term=1329&campaignid=18164753405&adgroupid=144188713167&keyword=cloudinary&device=c&matchtype=e&adposition=&gad_source=1&gclid=CjwKCAiApuCrBhAuEiwA8VJ6JqBNvBp0t3o84SCTQ9u9nAUQ-ryLtRlQmX91T1eEG5eEuzc_3ZMQrRoCLI8QAvD_BwE) for image storage in a project built using [MERN](https://www.mongodb.com/mern-stack) stack.

If you haven't read that yet, I **highly** recommend going through [that](https://dev.to/amnish04/progress-with-cloudinary-5pp) first for **full context**.

In this post, I'll discuss how I was successfully able to execute my plan and **migrate** an application's image storage from **disk** to [**cloud**](https://www.cloudflare.com/en-gb/learning/cloud/what-is-the-cloud/).

## Table of Contents
&nbsp;1. [Configuring Cloudinary](#configuring-cloudinary)
&nbsp;2. [Modifying Route Handlers](#modifying-route-handlers)
&nbsp;3. [Updating the UI](#updating-the-ui)
&nbsp;4. [Testing the changes 🧪](#testing-the-changes)
&nbsp;5. [The Pull Request](#the-pull-request)
&nbsp;6. [Conclusion 🎊](#conclusion)

## Configuring Cloudinary

Even though I already discussed in [last post](https://dev.to/amnish04/progress-with-cloudinary-5pp#exploring-the-sdk) how I was able to upload a test image to cloudinary, that was not the best way to do it.

Which is why, I moved it to a new file and named it `cloudinary.js`.

![cloudinary.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h740w1jkdmdbt2zpjjg5.png)

This is where I configure the [SDK](url) with **credentials**, and expose the `uploadImage` method that uses the configured object.

```js
// controllers/cloudinaryController.js
import cloudinary from 'cloudinary';
import dotenv from 'dotenv';

dotenv.config();

// Configure Cloudinary with your credentials
cloudinary.v2.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

// Controller function to handle image upload
const uploadImage = async (req, res, next) => {
    try {
      // Use Cloudinary SDK to upload the image
      // The buffer data we are using from request object was attached by multer
      await cloudinary.v2.uploader.upload_stream(
        { resource_type: 'image' }, // Specify resource type if necessary
        async (error, result) => {
          if (error) {
            console.error(error);
            return res.status(500).json({ error, message: "Image upload failed" });
          }
  
          // Save the public_id or URL in your database
          const picturePath = result.secure_url;
  
          // Attach the picturePath to the request
          // This will be saved in the User object when saving to the database
          req.picturePath = picturePath;
  
          // Continue to the next middleware or route handler
          next();
        }
      ).end(req.file.buffer);
    } catch (error) {
      console.error(error);
      res.status(500).json({ error, message: 'Image upload failed' });
    }
};

export const cloudinaryController = {
    uploadImage,
}
```

The `uploadImage` method is meant to be used as a [middleware](https://expressjs.com/en/guide/using-middleware.html) function, that takes the image [blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) processed by [multer](https://www.npmjs.com/package/multer) middleware, upload it to **cloudinary**, and attach the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the uploaded image resource to the [request](https://expressjs.com/en/api.html#req) object. This url of the uploaded image can later be used by the route handler.

## Modifying Route Handlers

Now that I had cloudinary correctly configured, it was time to go and modify the route handlers.

Previously, it was saving the image data locally on the disk as discussed in last post.

**Here's a recap!**
![Old way](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79415lkls8saekqdmbq3.png)

In order to save images to cloudinary though, I needed several **adjustments**.

The first thing I did was configure `multer` such that the images were stored in [memory](https://en.wikipedia.org/wiki/Computer_memory) instead of writing to disk.

```js
// Store files in memory for Cloudinary upload later
const storage = multer.memoryStorage(); 
const upload = multer({ storage });
```

Now, once the image has been parsed and loaded into memory, we can run the `request` object through the `uploadImage` middleware we talked about above.

```js
/* ROUTES WITH FILES */
app.post("/auth/register", upload.single("picture"), cloudinaryController.uploadImage, register);
app.post("/posts", verifyToken, upload.single("picture"), cloudinaryController.uploadImage, createPost);
```

The request object is **piped** through all the middleware functions where it is accessed/modified and certain operations happen.

I've create a diagram to better explain the flow

![Request Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx5f83auaox407ttokx8.png)

After the `uploadImage` function uploads the image to cloudinary, and the url of resource is attached, the request object goes to the `register` method, taking an example of the `/auth/register` route.

Here, I updated the `User` data to store the correct `picturePath` which would be the **cloudinary url** in this case.

```js
/* REGISTER USER */
export const register = async (req, res) => {
  try {
    const {
      firstName,
      lastName,
      email,
      password,
      friends,
      location,
      occupation,
    } = req.body;
    const picturePath = req.picturePath;

    const salt = await bcrypt.genSalt();
    const passwordHash = await bcrypt.hash(password, salt);

    const newUser = new User({
      firstName,
      lastName,
      email,
      password: passwordHash,
      picturePath,
      friends,
      location,
      occupation,
      viewedProfile: Math.floor(Math.random() * 10000),
      impressions: Math.floor(Math.random() * 10000),
    });
    const savedUser = await newUser.save();
    res.status(201).json(savedUser);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};
```

And I repeated the same process for `create posts` functionality.

## Updating the UI

Now that the Server was correctly setup to use **cloud storage** for images, I had to make a minor tweak to the UI to **use** the images from **cloudinary** instead of local storage of server.

Taking `UserImage` component as an example,
I changed the src property

**From:**
```jsx
src={`https://chatroomapi-v1.onrender.com/assets/${image}`}
```

**To:**
```jsx
// image is the cloudinary url here
src={image}
```

Apart from that, I noticed that the maintainer was using hard-coded strings for `API_URL`, which isn't a good practice. So I replaced all those instances with an environment variable to make my local testing easier as well.

Here's the commit for that
[Use environment variable for API url ](https://github.com/danielmarv/ChatRoom/commit/12bb7b5dbb205db0835a53bfd55d24b3523e1cf5)

## Testing the changes 🧪

Its not like I tested everything after writing all the code. I was testing throughout the entire process at each step, while debugging any issues.

But, to keep the blog short, I'll just demonstrate the working state of the application.

I have already made a **post** that successfully used cloudinary at all stages from **frontend** to **backend**.

![My profile page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x20qyhzbjfssqmilijw3.png)

But let's walk through the **process** one more time for a **demo**.

**Creating a Post**

![Creating a Post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6q24iylmjq7rn31umtq5.png)

**Successfully Created**

![Successfully Created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxgzz3ksnm3nbmnq8u4q.png)

**Viewing the Post**

![Viewing the Post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1d8n1c5kbwgwjs0chtxv.png)

**Resources on Cloudinary**

![Resources on Cloudinary](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdohscojzidrcmjqcqd4.png)


**Sweet!**

## The Pull Request

At this point, everything was working as expected throughout the application and it was time to make my [pull request](https://github.com/danielmarv/ChatRoom/pull/8) **ready for review**.

![Pull Request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/baa4o1udaaa04bfs9jge.png)

And it was merged the very next day!

## Conclusion 🎊

In this post, I shared my experience integrating **cloud storage** for images in a **MERN** project, that I started planning since this [post](https://dev.to/amnish04/planning-for-a-contribution-55i9).
Throughout the semester, I've worked on several **open-source** contributions, and all of them gave me **valuable** experience about how the open source **community** works. 
But, this was the only one where I actually **learned** someting new and useful. Not only that, I worked on both frontend and backend together this time, making it an even worthwhile experience overall.

Hope you enjoyed reading!
And I'll see you soon in another one of my posts.