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 that will be covered in this guide. ## How it all works You typically would need a few things to make this work: 1. A GraphQL API 2. A storage service or database for saving your files 3. A database to store the GraphQL data including a reference to the location of the file Take for example the following schema for a product in an E-commerce app: ```graphql type Product { id: ID! name: String! description: String price: Int image: ? } ``` How could you use this `image` field and make it work with your app to store and reference an image? #### For mutations 1. Store the image in S3 2. Send a mutation to create the Product in the GraphQL API using the image reference along with the other product data #### For Queries 1. Query the product data from the API (including the image reference) 2. Get a signed URL for the image from S3 in another API call Let's take a look at how to implement this using AWS Amplify, AWS AppSync, and Amazon S3. ## Creating the services To build this API, you need the following: 1. S3 bucket to store the image 2. GraphQL API to store the image reference and other data about the type 3. Authentication service to authenticate users (only needed in order to upload files to S3) The first thing you will want to do is create the authentication service. To do so, you'll initialize an Amplify project and add authentication. ```sh amplify init # Follow the steps to give the project a name, environment name, and set the default text editor. # Accept defaults for everything else and choose your AWS Profile. 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? Username ? Do you want to configure advanced settings? No, I am done. ``` Next, you'll create the storage service (Amazon S3): ```sh 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: ? 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? read ``` Next you will be creating a GraphQL API with a type that has an image field. This image can only be accessed by someone using your app. If someone tries to fetch this image directly, they will not be able to view it. ```sh amplify add api ? Please select from one of the below mentioned services: > GraphQL ? Here is the GraphQL API that we will create. Select a setting to edit or continue: > Name ? Provide API name: > gqls3 ? Here is the GraphQL API that we will create. Select a setting to edit or continue: Authorization modes: > API key (default, expiration time: 7 days from now) ? Choose the default authorization type for the API: > Amazon Cognito User Pool ? Configure additional auth types? > Yes ? Choose the additional authorization types you want to configure for the API: > API key ? Enter a description for the API key: > public ? After how many days from now the API key should expire (1-365): > 365 (or your preferred expiration) ? Here is the GraphQL API that we will create. Select a setting to edit or continue: > Continue ? Choose a schema template: > Single object with fields (e.g., “Todo” with ID, name, description) ? Do you want to edit the schema now? > Yes ``` When prompted, update the schema located at __/amplify/backend/api/gqls3/schema.graphql__ with the following: ```graphql type Product @model @auth(rules: [ { allow: owner }, { allow: private, operations: [read] }, { allow: public, operations: [read] } ]) { id: ID! name: String! description: String price: Int image: String } ``` The above schema assumes a combination of Amazon Cognito User Pools and API key authentication types. It will allow authenticated users to create products, and both authenticated and unauthenticated users to read products. Go back to the CLI and press __Enter__. Next, deploy the services using the Amplify `push` command: ```sh amplify push --y ``` ### Interacting with the API from a client application Now that the backend is created, how can you interact with it to save and read products and images from it? Here is the code that you could use to save a product and image to the API: ```js import { Storage, API } from 'aws-amplify'; import { createProduct } from './graphql/mutations'; async function saveProduct() { const fileName = "product-image-1"; await Storage.put(fileName, file); const product = { name: "Product 1", description: "Black dress", price: 200, image: fileName }; await API.graphql({ query: createProduct, variables: { input: product }}); } ``` Here is the code that you could use to query a product and corresponding image from the API: ```javascript import { Storage, API } from 'aws-amplify'; import { getProduct } from './graphql/mutations'; async function getProductById () { const product = await API.graphql({ query: getProduct, variables: { id: "12345" }}); const s3Image = await Storage.get(product.data.getProduct.image); product.data.getProduct.s3Image = s3Image; console.log('product:', product); } ``` Here is the code that you could use to query an array of products and images from the API: ```javascript import { Storage, API } from 'aws-amplify'; import { listProducts } from './graphql/mutations'; async function listProductsWithImages () { const productData = await API.graphql({ query: listProducts }); const products = await Promise.all(productData.data.listProducts.items.map(async product => { const image = await Storage.get(product.image) product.s3Image = image return product })) console.log('products: ', products) } ```