uploading images using cloudinary: a comparison between ruby/rails and node/express

e_papanicolas

Eleni Papanicolas

Posted on April 25, 2022

uploading images using cloudinary: a comparison between ruby/rails and node/express

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

<form onSubmit={handleSubmitNewImage} encType="multipart/form">
          <label htmlFor="image">
            <input
              type="file" name="image" accept="image/*"
            />
          </label>
          <input type="submit" value="submit" />
</form>
Enter fullscreen mode Exit fullscreen mode

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.

    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);
    });
Enter fullscreen mode Exit fullscreen mode

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, 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.

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

  has_one_attached :image

  def get_image_url
    url_for(self.image)
  end
end
Enter fullscreen mode Exit fullscreen mode

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.

const UserSchema = new Schema({
image: {
    url: {
      type: String,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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 of optional parameters. Params itself is also optional, so the only requirement is the Cloudinary API object.

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 });

Enter fullscreen mode Exit fullscreen mode

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}>

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)

Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
e_papanicolas
Eleni Papanicolas

Posted on April 25, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related