Uploading images in your Rails — React project using Cloudinary API

Margarita Morozova
Level Up Coding
Published in
5 min readJan 18, 2021

--

Thinking of building an image repository? There are many ways to handle image uploading in your Rails/React project; such as using CarrierWave, Active Storage, and so on. In this post I am not going to talk about the advantages and disadvantages of one over the other. I am instead going to walk you through setting up the Cloudinary API, since this is a super easy way to handle uploading images.

Cloudinary is a cloud-based image and video management platform. It enables users to upload, store, manage, manipulate and deliver images and videos for websites. To start using Cloudinary you need to register on their website(cloudinary.com) to obtain your cloud information. You will need a Cloud Name, API key, and API secret to set it up in your project. All of this information you can find in the dashboard page of the account console.

Now let’s start setting up our backend using the Rails API.

Firstly, you need to add the cloudinary gem to your Gemfile. This gem simplifies the integration of Cloudinary API into your project.

Add

gem 'cloudinary'

to your Gemfile and run

bundle install

Then in the config/initializers folder create a file and name it cloudinary.rb

Next, add the following code to this file:

Cloudinary.config do |config| config.cloud_name = ENV['cloud_name'] config.api_key = ENV['cloud_api_key'] config.api_secret = ENV['cloud_api_secret'] config.secure = true config.cdn_subdomain = trueend

In this file we are setting the global configuration parameters. The cloud name, api_ key and api_secret are mandatory settings. The cloud name is used to build the public URL for all your media assets. Api_key and api_secret are used to communicate with the Cloudinary API and sign requests. Secure is an optional parameter which forces HTTPS URLs for asset delivery even if they are embedded in non-secure HTTP pages.

In the config folder you must already have the application.yml file (if you are using the gem ‘figaro’ to protect your API keys, if not you can create the application.yml manually). Add your Cloudinary keys to this file.

cloud_name: 'xxxxxxxxx'cloud_api_key: 'xxxxxxxx'cloud_api_secret: 'xxxxxxxxx'

N.B. Make sure your keys are stored in a string format and you add application.yml to your .gitignore (if you are using ‘figaro’ it adds application.yml to .gitignore automatically).

We are now done with setting up the keys for the cloud. Now let’s create a model for our photos. You can do it any way you like. I prefer using generators.

rails g model Photo image:string

Then create a controller:

rails g controller photos

And don’t forget about routes. In routes.rb file let’s add

resources :photos

If you are just practicing creating and deleting photos, update your routes respectively.

To upload an image which is sent from the client to the cloud we need the create method in our PhotosController. Once a user uploads a photo on our website, Cloudinary will send a response with a URL string of the photo uploaded to our cloud. In the Rails database we don’t store actual photos, we only store URL strings of the photos that are hosted by Cloudinary.

The Cloudinary gem gives us access to all Cloudinary methods. The one we are going to use to upload photos is

Cloudinary::Uploader.upload(file, options ={})

method which takes an image in as an argument.(As an example: Cloudinary::Uploader.upload(‘my_image.jpg’)).

Once the upload is finished, the uploaded image is available for manipulation. An upload call returns us an object; right now we only need to extract a value with the key [‘url’]. The URL gives us access to the uploaded photo stored in the cloud.

In PhotosController add the following code:

def create   result = Cloudinary::Uploader.upload(params[:image])  photo = Photo.create(user_id: current_user.id, image:   result['url'])     if photo.save        render json: photo     else        render json: photo.errors     endend

(In my example I have belongs_to relationships, that is why I am also passing a user_id on creation.)

Alright, so now we can move on to our React application and build an upload function for our frontend. First, let’s create a component that will render a form to upload a photo and will handle submitting the photo to the Rails API.

(Direct uploading from the browser is performed using XHR CORS requests. Make sure you are using the gem ‘rack-cors’ in your Rails backend and uncommented out code in cors.rb file, changing origins to ‘*’.)

Let’s create an input field with the type ‘file’ so the user can select a file from their local machine. Also, we include the accept attribute with a wild card(*) to denote that a type of any format is acceptable.

The input listens to the onChange event which will set a state for our photo. Instead of accessing the value through event.target.value, we access our attached file through event.target.files.

Also, we need to create a new FormData Object and append a new key-value pair to it. In our case the key is ‘photo’ and the value is the photo’s state. Once a new object is created we are ready to submit a request to our backend.

import React from 'react'
class NewPhoto extends React.Component { state = { photo: null } onChange = (e) => { e.persist() this.setState(() => { return { //Upload only one file, multiple uploads are not allowed
[e.target.name]: e.target.files[0]
} }) } onSubmit = (e) => { e.preventDefault() //create a FormData object
const formData = new FormData()
// Add the file to the AJAX request
formData.append('photo', this.state.photo)
this.props.handleUploadPhoto(formData)} render(){ return ( <form onSubmit={this.onSubmit} > <input type='file' name='photo' accept='image/*' onChange={this.onChange}/> <button type='submit' value='Submit>Submit</button> </form> ) }
}export default NewPhoto

I placed handleUploadPhoto in a different component but you can write it in the same. It’s a matter of personal preference.

We need to pass our object which is storing the photo state to handleUploadPhoto and make a POST request to the server. Pay attention that in body we are attaching our formData object. Once the user uploads a photo and hits the submit button, a request will be sent to the server, updating our state on the frontend and the user is taken directly to the photo they just uploaded.

handleUploadPhoto = (formData) =>{  fetch('http://localhost:3000/photos', {  method:'POST',  body: formData  }) .then(resp => resp.json()) .then(data => {   this.setState((prevState) =>({    photos: [...prevState.photos, data]   })) //scroll down to the uploaded photo window.scrollTo(0,document.body.scrollHeight)})}

And that is it! Now the user can upload photos that are going to be stored in our cloud, and on the backend the database stores all the URLs of these photos.

--

--