Connect Your React Application to a Rails API Using Active Storage (part 1)
By: Jennifer Ingram
Tags: Postgresql, Avatar, React, Active Storage, Rails

Several weeks ago I set out to learn all about web sockets. If you’re thinking that something must have derailed me, because this blog isn’t about web sockets, you’d be correct. I wanted to show off my app’s users on the frontend by giving them each a little avatar. It seemed easy enough to do — I had heard about many people using this thing called Active Storage with Rails to connect image files to a model — so I thought, ‘Hey! I’ll just get this little bit working real quick before I move on to web sockets!’ After falling down a huge Internet rabbit hole of documentation, blogs, Stack Overflow, and many more random sites, I’ve finally found a working solution. So now I present it to you, my friends, hoping that you can avoid the pain I went through.

Setting up the Backend

My Rails backend will only be used as an API to send and receive information from my database to and from my frontend. For this project, I’m going to use PostgreSQL for the database. From your terminal enter

rails new avatar_app --database=postgresql --api

*Note — you must have PostgreSQL installed on your computer and running for this command to work.

Now let’s get Active Storage all setup.

rails active_storage:install

This command creates a migration file for you that will populate your database with two new tables — active_storage_blobs & active_storage_attachments. According to Rails documentation —

“A blob is a record that contains the metadata about a file and a key for where that file resides on the service.”

The active_storage_attachments table is a join table that connects the blob with the model it belongs to (technically, it’s a polymorphic table, but I prefer to think of it as a simple join table).

Before we migrate these migrations, let’s add a user model to our application. This app is going to be super simple — user’s will be able to create an account, upload an avatar, and show off their avatar — that’s it!

rails g resource User

This command does a few things, including giving us a migration file to create a users table — so let’s fill it out.

Notice that even though each user is going to have an avatar, we didn’t include that as a column on our users table. Instead, on our user model, we’re going to indicate that each user will have an avatar attached to it (in the form of a file) like so —

Before we forget, let’s run those migrations —

rails db:create

rails db:migrate

Because we’ll only be running our Rails API from a local server, we don’t need to do any additional configuration here. If however, you want to set up active storage to keep your files on a remote server — such as Amazon S3, Microsoft Azure, or Google Cloud — there are special instructions for how to do that in the Rails documentation.

Before we move on to uploading an image file from the frontend, I think it’s important to show you how to attach a file to a model from your backend. Let’s move into the seeds.rb file, to create a new user.

Now, if I want to attach an image file to Lucy from my backend, that image file must exist somewhere within my backend directory. It makes most sense to me to place a folder, called avatars, inside of my public folder. Then I’ll drop my image files in there.

*If you’re looking for free avatar icons to play around with, here’s a great site (mine came from this site and specifically this author).

Awesome! Now let’s attach one of those images to our user named Lucy.

Because we’ve already declared in our User model that each user has_one(avatar)_attached, now we can use Active Storage’s attach method to attach the file we want. Notice that the path given to File.open() matches the location of my file in my directory — if you choose to place your image in a different location within your directory, your path will need to reflect that.

rails db:seed

If we’ve written our seed file correctly, we should see our user, our active_storage_blob, and our active_storage_attachment in our database.

Here’s our user, Lucy!

Here’s our blob!

And here’s our attachment!

Cool, cool, and cool.

Now let’s setup the wiring that will allow our React frontend (which we haven’t created yet) to communicate with our API. First, let’s make a controller for our User model.

rails g controller Users

Now let’s fill out the controller actions we’ll need for our application.

I’ve filled out the index action to render all users, but I’m going to leave our create, show and update actions blank for now. We need to do just a couple more things to let our frontend and backend communicate smoothly. First, uncomment the ‘rack-cors’ gem in your Gemfile.

Then, inside of config >> initializers >> cors.rb, uncomment the following code, and change line 10 to read like so…

Then re-run

bundle install

to install that ‘rack-cors’ gem.

Finally — let’s move on to the fun part — the frontend!

Setting up the Frontend

To create a React app from scratch, enter the command

npx create-react-app avatar_app_frontend

from terminal. Then cd into that directory and open up your code. In order to use Rails’ Active Storage with React (a JavaScript framework), we need to install it through our node package manager (npm) —

npm install activestorage

My app will only allow a user to do one of two things: first, if they already have an account, they can ‘login’ (with fake authentication) and see their avatar on their profile page, else, they can create an account, upload an avatar (image file), and then see their avatar on their profile page. Reply to this post if you’d like to offer a ton of moolah to support this startup.

Because I would like to create some routes in my application, I’m also going to install react-router-dom.

npm install react-router-dom

Here is a quick demonstration of how my app is set up, including routes to the various pages, that, as of yet, don’t show much. I’m going to save us a bit of time by not going to go into the deets of how to build this out in this post.

Now we’ll go ahead and build those pages out, beginning with a login form.

Getting Images from the Backend

Here’s a login form all built out — almost. If you’re familiar at all with React controlled forms, this should look pretty familiar to you…

We have a form with two input fields, one for the user to enter their name, and the other to enter their password. Both inputs are using the onChange event listener, and both listeners call on the handleOnChange() function. This function is changing local state as the user types in those input fields. Finally, when the user hits submit, we call on the handleSubmit() function.

Let’s think for a moment about what this function needs to do. In order to login a user, it needs to communicate with our backend and find the user with the (kinda fake) credentials they’ve typed in, then return that user to us. When we get that user, let’s set them as the CurrentUser (in state) for our application. We also want to grab that user’s avatar from the backend, so let’s set that piece of state as well. Because different pieces of our application need that information, let’s set state for those things in our App Component.

Okay, now going back to our handleSubmit() function in our Login Component, let’s build out the fetch call that will communicate with our Rails API. The first thing we need to figure out is what url to make the fetch call to. If we enter the rails routes command in our terminal, we see that we can’t make a GET request to our users controller’s show action, without knowing a user’s id, and we actually need to give our application some information (the user’s credentials).

This can mean only one thing… we need to make a custom route!

Cool. Now we have a route to fetch to. Place a byebug in your users controller’s show action, and finish building out your fetch call like so

If you go to your frontend and try to login our user, Lucy, you should be paused in the byebug from your users controller. Let’s check what params are at this point…

Great! Using those params we can find our user.

Now if we log Lucy in with the correct credentials, we see her user object logged into the console, and if we try to log her in with incorrect credentials (i.e. a bogus password) we get the ‘This user is not authenticated’ message. Perfect! We know that we can use this information to set state for our currentUser, but how can we get her avatar?

Well, conveniently enough, Rails provides us a url that we can link to do just that — rails_blob_path. This is how we can use it to pass Lucy’s avatar (or a link to her avatar image) to the frontend…

Now on our frontend, we see not only our user object logged into the console, but also her avatar url.

Awesome. Let’s use this to set state in our app.

In our App Component, we have to make a few changes — first, we need to write a function, setCurrentUser(), that will take in the result from our fetch call (data), and set state for our CurrentUser and CurrentAvatar. Then, we need to pass that function down as props to our Login Component. In order for the Login Component to receive props, we need to change how we render that component.

render={() => <Login setCurrentUser={this.setCurrentUser} />}

Then, in our Login Component, rather than simply logging our fetched data to the console, we’ll call on our function (passed down as props).

Now our app has the information it needs to render Lucy’s avatar on her profile page. Let’s build that out!

First, on our App Component, let’s import the withRouter function from react-router, and then add some conditional logic on our Profile Component. If this.state.CurrentUser exists (meaning we have someone logged in), then we’ll show the profile page and pass down our current user’s information as props, else, we’ll re-render our Login Component. Also, notice that I’m passing through Route props with my /login route as routerProps. I’ll use this after my user logs in to redirect them to their profile page.

Now, let’s build out our Profile Component to actually show our user’s information…

Notice that, in order to display our user’s avatar, we have to provide the full path (prepending the actual domain address of our server, which for us is http://localhost:3000/). Now, when we log Lucy in, we go directly to her profile page, and … we see her avatar!

Awesome! Now we have the ability to grab our users’ avatar images from the backend — but that’s only half of what we’d like to accomplish. In part 2 of this post, I’ll show you how to allow a user to upload an image from their own computer, and store it on the backend. Stay tuned!

You can find part 2 of this post here.