In this blog post, we'll explore how to use Active Storage with Amazon S3 to store image files and how to use React to display those images in a web application.
Setting up Amazon S3
The first step is to create an Amazon S3 bucket to store your image files. To do this, log in to your Amazon Web Services (AWS) account and navigate to the S3 dashboard. From there, click on the "Create bucket" button and follow the prompts to create your bucket.
Once your bucket is created, you'll need to generate an access key and secret key for your AWS account. These credentials will be used by Rails to access your S3 bucket. To generate your credentials, navigate to the AWS Identity and Access Management (IAM) dashboard and create a new user. Make sure to give the user the appropriate permissions to access your S3 bucket.
Once your bucket is set up, you will also want to add the following permission object into the CORS (Cross-origin resource sharing) configuration in your bucket settings:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
Setting up Active Storage
The next step is to set up Active Storage in your Rails application. To do this, first, add the following line to your Gemfile:
gem 'aws-sdk-s3'
This will install the necessary dependencies for using Active Storage with Amazon S3.
Next, run the following command to set up Active Storage:
rails active_storage:install
This will generate a migration that adds the necessary tables to your database.
Run the migration with the following command:
rails db:migrate
You will also need to uncomment Amazon storage code in your storage.yml file and fill in the appropriate data:
amazon:
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: your-region
bucket: your-bucket-name
Next, with this command you will be able to edit your credential files and include the access key and secret access key provided by AWS:
EDITOR="code --wait" rails credentials:edit
In your development.rb file in your environments, you will also need to change your config.active_storage.service to Amazon:
config.active_storage.service = :amazon
Finally, in your initializers, in your cors.rb file, uncomment out the necessary code, and change the origins to whatever your project requires. For the sake of this example, a wildcard will be included:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Now that Active Storage is set up, we can start using it to store our image files.
Uploading Files
To upload an image file, first, create a form in your React application that allows users to select a file to upload. Here's an example:
import React, { useState } from 'react';
function ImageUploadForm() {
const [file, setFile] = useState(null);
function handleChange(event) {
setFile(event.target.files[0]);
};
function handleSubmit(event) {
event.preventDefault();
const formData = new FormData();
formData.append('image', file);
fetch('/images', {
method: 'POST',
body: formData,
});
if (response.ok) {
console.log('Image uploaded successfully');
} else {
console.log('Error uploading image');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="file" onChange={handleChange} />
<button type="submit">Upload Image</button>
</form>
);
}
export default ImageUploadForm;
This form will allow users to select an image file to upload. Note that images must be appended to a new FormData as the information can not be sent as JSON.
Next, create a Rails controller action to handle the file upload. Here's an example:
class ImagesController < ApplicationController
def create
image = Image.new(image_params)
if image.save
render json: { image_url: url_for(image.image) }, status: :created
else
render json: image.errors, status: :unprocessable_entity
end
end
private
def image_params
params.permit(:image)
end
end
This controller action creates a new Image
object and assigns it the uploaded file. The image_params
method is used to permit only the image
parameter to be passed to the Image
model. The response includes the URL of the uploaded image, which we'll use in the React application to display the image.
Another way to access the image’s url is to define the image_url in the serializer. Here’s an example for that:
class ImageSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :image_url
def image_url
if object.image.attached?
url_for(object.image)
else
""
end
end
end
Note that the line “include Rails.application.routes.url_helpers” must be included to have access to the url_for helper.
Next, add the following code to your Image
model to use Active Storage to store the image file:
class Image < ApplicationRecord
has_one_attached :image
end
Displaying Files
Now that we have uploaded our images to S3 using Active Storage, we can display them in our React application. Here's an example of how to display an image in React:
import React from 'react';
function ImageDisplay({ imageUrl }) {
return (
<div>
<img src={imageUrl} alt="Uploaded Image" />
</div>
);
}
export default ImageDisplay;
This component takes the URL of the image as a prop and displays it using an img
element.
Finally, we can use our ImageUploadForm
and ImageDisplay
components together to create a complete image upload and display feature:
import React, { useState } from 'react';
import ImageUploadForm from './ImageUploadForm';
import ImageDisplay from './ImageDisplay';
function App() {
const [imageUrl, setImageUrl] = useState(null);
function handleImageUpload(formData) {
fetch('/images', {
method: 'POST',
body: formData,
});
if (response.ok) {
r.json().then((data) => setImageUrl(data.image_url));
} else {
console.log('Error uploading image');
}
};
return (
<div>
<ImageUploadForm onImageUpload={handleImageUpload} />
{imageUrl && <ImageDisplay imageUrl={imageUrl} />}
</div>
);
}
export default App;
This component creates an ImageUploadForm
and an ImageDisplay
component. When an image is uploaded, the handleImageUpload
function is called, which sends a POST request to the Rails API and sets the imageUrl
state to the URL of the uploaded image. The ImageDisplay
component is only shown if imageUrl
is not null.
And that's it! You should now have a fully functioning image upload and display feature in your React application using Active Storage with Amazon S3.
Top comments (1)
If you are looking to integrate ActiveStorage w/ React I'd suggest using a hook library to simplify the direct to provider (AWS / Google / Azure / etc) transfer of files. If you are curious I published an NPM package react-activestorage to handle that integration.