Images in base64 with carrierwave blog binarappsr

During the last few years of development practice in our company we have started developing common scopes of technologies helpful when dealing with some particular app functionalities. This way we found out that we tend to use Carrierwave for handling file uploads in our applications.

On the other hand, there is a general trend to decouple application backend (API) from the frontend side (HTML, React.js, Angular, etc.) and to introduce JSON data for client-to-server communication, as it is lightweight and easy to read/write.

Thus, it is vital to know how to deal with file uploads in such environment. The following post covers the backend part of the process.

Solutions

As we decided to use JSON data for our communication, the most obvious issue is how to send a file (an image in most cases), which is binary data, via JSON encoded string. The simplest (not the most efficient one, as stated in a WalmartLabs blogpost) solution is to encode a file to a Base64 representation, send it along with other data and decode it on the server side.

At this point we need to be aware that, with Carrierwave introduced to our project, it is necessary to decode a Base64 string to a file prior to the params assingment process.

Your own solution

Of course one can write their own solution for that. There are plenty of examples that can be found on Github: 

https://gist.github.com/shrikanthkr/10097999

Carrierwave-base64 gem

However, for those who are looking for more compact solution, there is the carrierwave-base64 gem made by Yury Lebedev. It can be found here. It works on top of carrierwave so you just need to create a normal carrierwave uploader and mount it to the model, indicating you want to use base64 decoding.

The usage of the library is really simple (assuming you have installed and configured carrierwave properly).

Put the gem in your Gemfile:

gem 'carrierwave-base64'

Then create a carrierwave uploader:

rails generate uploader Image

This will create a image_uploader.rb file inside app/uploaders directory:

class ImageUploader < CarrierWave::Uploader::Base

  ...

  storage :file
  
  ...
  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  ...
end

Next, mount the uploader to your model (indicating that you want to use the base64 version):

mount_base64_uploader :image, ImageUploader

And that's mainly all. It should work. The gem assumes your file data comes in a form:

data:image/jpg;base64,(base64 encoded data)

The important part is the content type data:image/jpg.

The gem uses mime-types gem and content type from the uploaded string to identify the file extension. You can register your own mime type if needed.

Testing

To test the solution you can just upload an image (we advise you to delegate that to a helper module and include it in rails_helper.rb), decode it and send it to the server.

describe 'POST create' do
  let(:image_data) { Base64.encode64(File.open(File.join(Rails.root, '/spec/support/files/avatar.gif'), &:read)) }
  let(:image) { "data:image/gif;base64,#{ image_data }" }

  context 'with valid params' do
    let(:params) { { photo: { image: image } } }

    before do
      post :create,
           params: params,
           'Content-Type' => 'application/json',
            format: :json
    end

    it 'returns photo object' do
      expect(parsed_response_body(response.body)[:image][:url]).not_to be_empty
    end

    # be careful with the store_dir path!!!
    # unless you have store_dir paths separated based on environment
    # you may end up deleting production files too.
    after do
        FileUtils.rm_rf(File.join(Rails.root, user.photo.store_dir))
    end
  end
end

The above example presents a controller spec to ensure the whole process of decoding file data and creating an object is successful. You can also encapsulate the creation process in a command or a service object and only test the decoding part.

Last but not least, you need to remember to clean up. You can either delete the file directly from a spec (like in the above code snippet) or define a global callback in RSpec as described on carrierwave github page.

Summary

In my opinion this is the quickest way to handle the file upload in a distributed system in which communication is based on JSON. It is perfect for prototypes, but also works in real life applications. However, if you need a solution that will guarantee the best performance, I suggest reading more about combining multipart and JSON data in one request.

Feel free to leave a comment or ask a question, I will be glad to answer them.

Below I enclose full list of links quoted in the article:

https://medium.com/walmartlabs/rest-easy-with-json-and-binary-f218f7e141be

https://gist.github.com/shrikanthkr/10097999

https://github.com/lebedev-yury/carrierwave-base64

https://github.com/carrierwaveuploader/carrierwave/wiki/how-to:-cleanup-after-your-rspec-tests

Post tags:

Join our awesome team
Check offers

Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate