All Articles

File upload in GraphQL-ruby using Active Storage Direct Upload

Every modern web application nowadays tends to have a requirement to upload files. This feature may be for uploading an avatar for a user profile or adding attachments.

File upload in GraphQL-ruby is a tricky problem. You can do file upload using multipart form request but GraphQL doesn’t allow multipart form request. I have spent countless hours to get multipart request working in GraphQL but the solution didn’t map the files properly in Active Storage so I had to let it go.

File upload can also be done using Direct Upload to the S3. We can achieve direct upload using two ways. We’ll discuss the best and the easiest way to achieve the direct upload in this blog.

Direct Upload using @rails/activestorage

Firstly, install the Active Storage NPM package if not installed already.

yarn add @rails/activestorage
# OR
npm install @rails/activestorage

Now let’s build a simple react component to upload the profile picture. In the following example, we would consider the following example:

class User < ApplicationRecord
  has_one_attached :avatar
end
import React, { Fragment, useState } from "react";
import { DirectUpload } from "@rails/activestorage";

const DIRECT_UPLOAD_URL = "/rails/active_storage/direct_uploads";

export default function UploadProfilePicture(props) {
  const [blob, setBlob] = useState({});
  const isBlobPresent = Object.keys(blob).length === 0;

  const handleFileUpload = (event) => {
    const file = event.target.files[0];
    const upload = new DirectUpload(file, DIRECT_UPLOAD_URL);

    upload.create((error, blob) => {
      if (error) {
        # Handle the error.
      } else {
        # Set the blob local state or in the global form state you are using.
        # Add a hidden field for the associating uploaded blob to the ActiveRecord.
        # Example: User has_one_attached avatar
        setBlob(blob);
      }
    });
  }

  return (
    <Fragment>
      {
        isBlobPresent ? (
          # TODO: Change the name to ActiveStorage association
          <input type="hidden" name="avatar" value={blob.signed_id} />
        ) : null
      }
      <input type="file" accept="images/*" />
    </Fragment>
  )
}

In the above component you need to take care of the following things:

  1. We have considered the User model with avatar association. Please make sure to update the hidden input name attribute.
  2. If multiple files are attached, then direct upload all the files and generate the ActiveStorage::Blob and send it with the form_data on form submit.
  3. The form_data on form submit should have avatar: <blob.signed_id> value to associate the uploaded file in the database.

Happy Coding!!