uploading images using cloudinary: a comparison between ruby/rails and node/express
Published: 2022-04-25 21:56:54

Uploading images on the front end, using a form in React, is fairly easy, when compared with the back end set up. I learned how to do it in Ruby on Rails first, and now using Node.js and Express. In both, despite pouring over numerous tutorials and tons of documentation, I found it wasn't exactly as straightforward as all these resources made it look. Through tons of debugging though, I finally found my solution. I wanted to share some thoughts and some of the differences I found when learning how to configure Cloudinary with different back end languages and frameworks.

## FRONT END FIRST

```javascript
<form onSubmit={handleSubmitNewImage} encType="multipart/form">
          <label htmlFor="image">
            <input
              type="file" name="image" accept="image/*"
            />
          </label>
          <input type="submit" value="submit" />
</form>
```
Here I have set up up a form which will render a file chooser. The `enctype="multipart/form"` is necessary because when we are attaching an image to the request object, we lose the ability to use `JSON.stringify()`, and we must use `new FormData()` and append our image file. Then in the body we just send the formData as it is.

```javascript
    const formData = new FormData();
    formData.append("image", image);

    const response = await fetch(`<your-endpoint>`, {
      method: "POST",
      body: formData,
    });

    await response.json().then((data) => {
      console.log(data);
    });
```
 
## CLOUDINARY

Cloudinary is an exceptionally easy to use cloud storage solution for uploading images and storing a url path in a database. I used postgreSQL, a relational database, when I uploaded images using Rails, and I used mongoDB, a non relational database, when I did the same with Node and Express. Cloudinary worked great in both environments. I created an account for free [on Cloudinary's website](https://cloudinary.com/users/register/free), and they even provide a large amount of storage for free! After that I was navigated to my dashboard where I found the SDK Setup, which gave you code snippets for many languages, that help the configuration process.

## RAILS

For our Rails setup, I ran `gem install cloudinary`, and then in a `storage.yml` file, added `cloudinary: service: Cloudinary`. Rails comes fully loaded with Active Storage as a built in method for handling image uploads, and running `rails active_storage:install` and `rails db:migrate`, abstracts away the set up for a polymorphic join table between `active_storage_blobs` and `active_storage_attachments`, which you will notice now exists in the schema. We won't touch these tables going forward, but this stores lots of information about the upload, and an attachment to it.

In the model, we will use this reference to an attachment, where we set `has_one_attached :image` instead of creating a column in the database. We add  `Rails.application.routes.url_helpers` to the code at the top of the model, which can be used to generate URLs given a set of parameters. In the serializer, I will call the `get_image_url` method as an attribute to send the client a url generated for `self.image`.

```ruby
class User < ApplicationRecord
  include Rails.application.routes.url_helpers

  has_one_attached :image

  def get_image_url
    url_for(self.image)
  end
end
```

Then on my React front end, I can simply set an `<img src={user.get_image_url}>`!

## NODE & EXPRESS

Using Node, Express, and MongoDB (with Mongoose) has been a fun process. I ran into a lot of errors and got to have some fun debugging by going over lots of juicy documentation. I landed on using a few packages: `npm install cloudinary multer multer-storage-cloudinary`.

In a non relational database, we still use a Schema to organize and validate data. Using mongoose seemed like a good way to go. A new Schema is quickly available by importing `mongoose` and `{Schema}`. The same way we only stored information about the uploaded image, instead of the image data in the database, we set the image object to contain a url, with a type of `String`.

 ```javascript
const UserSchema = new Schema({
image: {
    url: {
      type: String,
    },
  },
});
```

After creating the Schema, we are ready to configure Cloudinary. In the code below, `cloudinary.config` refers to the section above on the SDK Setup. 

Multer is a piece of middleware for handling `multipart/form-data`, primarily for uploading files. It creates a body object, and a file object and adds them to the request object. The file object is what we will use to upload the image, but the body will make sure that other pieces of data sent in the request are processed properly for their data types as well. Multer-storage-cloudinary is a storage engine that easily allows you to configure storage, using options passed in as `params`. Choose from an [extensive list](https://cloudinary.com/documentation/image_upload_api_reference#upload_method) of optional parameters. `Params` itself is also optional, so the only requirement is the Cloudinary API object.

```javascript
const multer = require("multer");
const cloudinary = require("cloudinary").v2;
const { CloudinaryStorage } = require("multer-storage-cloudinary");

cloudinary.config({
  cloud_name: "eleni",
  api_key: "<your-api-key>",
  api_secret: "<your-api-secret>",
});

const storage = new CloudinaryStorage({
  cloudinary: cloudinary,
  params: {
    folder: "users",
  },
});

const parser = multer({ storage: storage });

```

We created the `parser` which is the last part of our middleware, and it is inserted into the route, where it will capture the incoming request, attach a file, and pass it along. Here in the route we create an `image` object, and set the url (which we created in the Schema) to one of the properties of the file object, the path, which is a url pointing to where the image is stored on Cloudinary! And then we create the rest of the new user and send it to the client, where it can be accessed by adding `<img src={user.image.url}>`

```javascript
router.post("/<your-endpoint>", parser.single("image"), (req, res, next) => {
  const image = {};
  image.url = req.file.path;
  const user = new User({ ...req.body, image });
  user.save();
  res.json(user)

```

## ONE LAST TIP
My biggest & silliest hang up working this, was because in my `server.js` file, I was using `body-parser` and running `app.use(bodyParser.urlencoded({ extended: false }))`. When I switched to using `app.use(bodyParser.urlencoded({ extended: true }))` ... it was a pretty glorious moment! 

## THE RESULTS

I honestly don't know which I prefer. I wrote this with hopes that it would help me decide if I preferred either, and the answer to that is still `undefined` 😂. Both languages and environments handle image uploads beautifully. Rails and Active Storage require less packages and configuration, but Node and Express allowed more flexibility as far as control of the data. Comment and let me know which you like better!