Managing File and Images in GraphQL with AWS Amplify and AWS AppSync

How to create & query images and files using GraphQL with AWS AppSync, AWS Amplify, and Amazon S3

Storing and querying for files like images and videos is a common requirement for most applications, but how do you do this using GraphQL?

One option would be to Base64 encode the image and send as a string in the mutation. This comes with disadvantages like the encoded file being larger than the original binary, the operation being computationally expensive, and the added complexity around encoding and decoding properly.

Another option is to have a separate server (or API) for uploading files. This is the preferred approach and the technique we will be covering in this tutorial.

How could we use this image field and make it work with our app to store and reference an image? Let's take a look at how this might work with an image stored in Amazon S3.

Using Amazon S3 there are two main types of access: private and public.

Public access means anyone with the file url can view or download it at any time. In this use case, we could reference the image url as the image field in the GraphQL schema. Since the image url is public anyway, we don't care who can view the image.

Private access means that only authenticated users can view or download the file. In this use case, we would only store a reference to the image key (i.e. images/mycoolimage.png) as the image field in the GraphQL schema. Using this key, we can fetch a temporary signed url to view this image on demand from S3 whenever we would like it to be viewed by someone.

In this tutorial, you'll learn how to do both.

Creating the client

In this tutorial I will be writing the client code in React, but you can use Vue, Angular, or any other JavaScript framework because there is minimal code that is React specific.

Create a new client project, change into the directory and install the amplify and uuid dependencies:

Public access

The first example we will create is a GraphQL API that has public image access.

The GraphQL type that we will be working with is a Product with an image field. We want this product's image to be public so it can be shared and visible to anyone viewing the app, regardless if they are signed in or not.

For mutations

Send a mutation to create the Product in the GraphQL API using the image reference along with the other product data

For Queries

Query the product data from the GraphQL API. Because the image url is public, we can just render the image field immediately.

Creating the services

To build this API, we need the following:

S3 bucket to store the image

GraphQL API to store the image reference and other data about the type

Authentication service to authenticate users (only needed in order to upload files to S3)

The first thing we will want to do is create the authentication service. To do so, we'll initialize an Amplify project and add authentication.

If you have not yet installed & configured the Amplify CLI, click here to view a video walkthrough.

amplify init
amplify add auth
? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in when using your Cognito User Pool? Username
? What attributes are required for signing up? Email

Next, we'll create the storage service (Amazon S3):

amplify add storage
? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)
? Please provide a friendly name for your resource that will be used to label this category in the project: gqls3
? Please provide bucket name: <YOUR_UNIQUE_BUCKET_NAME>
? Who should have access: Auth and guest users
? What kind of access do you want for Authenticated users?
❯◉ create/update
◉ read
◉ delete
? What kind of access do you want for Guest users?
◯ create/update
❯◉ read
◯ delete

Finally, we'll create the GraphQL API:

amplify add api
? Please select from one of the below mentioned services (Use arrow keys): GraphQL
? Provide API name: (gqls3)
? Choose an authorization typefor the API: API key
? Do you have an annotated GraphQL schema? N
? Do you want a guided schema creation? Y
? What best describes your project: Single object with fields
? Do you want to edit the schema now? Y

When prompted, update the schema located at /amplify/backend/api/gqls3/schema.graphql with the following:

Now that the services have been deployed, we need to update the S3 bucket to have a public /images folder so that anything stored in the folder can be viewed by anyone.

Warning: when making an S3 folder public, you should make sure that you never store any sensitive or private information here as the folder is completely open for anyone to view it. In this case, we are simulating an E-commerce app where we have public images for our products that would go on the main site.

To see the completed project code, click here and open the src/Products.js file.

Private Access

The next example we will create is a GraphQL API with a type that has a private image field.

This image can only be accessed by someone using our app. If someone tries to fetch this image directly, they will not be able to view it.

For the image field, we'll create a GraphQL type type that holds all of the information we need in order to create and read private files from an S3 bucket, including the bucket name and region as well as the key we'd like to read from the bucket.

The GraphQL type that we will be working with is a User with an avatar field. We want this avatar image to be private so it can be only be visible to someone signed in to the app.