# Fullstack App With TypeScript, PostgreSQL, Next.js, Prisma & GraphQL: Image upload URL: https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v Published: 2022-03-30 Authors: Mahmoud Abdelwahab, Alex Ruheni Tags: education Series: Building a fullstack app using TypeScript, PostgreSQL, Next.js, GraphQL and Prisma Description: Learn how to build a fullstack app using TypeScript, PostgreSQL, Next.js, GraphQL and Prisma. In this article you are going to learn how to add image upload using AWS S3 > Blog posts describe Prisma as it was on the date shown and are not kept current. Before implementing from a post, check the documentation index at https://www.prisma.io/docs/llms.txt and the changelog at https://www.prisma.io/changelog.md. > For an index of every published post, fetch https://www.prisma.io/blog/llms.txt. This article is the fourth part of the course where you build a fullstack app with Next.js, GraphQL, TypeScript, Prisma and PostgreSQL. In this article, you will learn how to add image upload using AWS S3. ## Table of Contents [#table-of-contents] * [Introduction](#introduction) * [Development environment](#development-environment) * [Clone the repository](#clone-the-repository) * [Seed the database](#seeding-the-database) * [Project structure and dependencies](#project-structure-and-dependencies) * [Using AWS S3 to add support for image upload](#using-aws-s3-to-add-support-for-image-upload) * [Create an Identity Access Management user ](#create-an-identity-access-management-user) * [Create and configure a new S3 bucket ](#create-and-configure-a-new-s3-bucket) * [Add image upload functionality to your application](#add-image-upload-functionality-to-your-application) * [Summary and next steps](#summary-and-next-steps) ## Introduction [#introduction] In this course you will learn how to build "awesome-links", a fullstack app where users can browse through a list of curated links and bookmark their favorite ones. In [part 3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv), you added authentication to the app and created a page for adding new links using placeholder images. This guide will teach you how to add support for image upload using AWS S3. If you're following along from part 3, you can skip project setup and jump into the [using AWS S3 to add support for image upload](#using-aws-s3-to-add-suport-for-image-upload) section. > Note: The example code used for part 4 differs slightly by including functionality such as bookmarking favorite links. Feel free to refer to the [part 4 branch](https://github.com/prisma/awesome-links/tree/part-4) to update your current application to match what is used in this tutorial. ## Development environment [#development-environment] To follow along with this tutorial, ensure you have [Node.js](https://nodejs.org/en/download/) and the [GraphQL extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) installed. You will also need a PostgreSQL database running. > Note: you can set up PostgreSQL [locally](https://www.prisma.io/docs/orm/v7/more/troubleshooting/nextjs) or a hosted instance on [Heroku](https://dev.to/prisma/how-to-setup-a-free-postgresql-database-on-heroku-1dc1). Note that you will need a remote database for the deployment step at the end of the course. ## Clone the repository [#clone-the-repository] You can find the [complete source code](https://github.com/prisma/awesome-links) for the course on GitHub. > Note: Each article has a corresponding branch. This way, you can follow along as you go through it. By checking out the part-4 branch, you have the same starting point as this article. There might be a few differences between each branch, so to not run into any issues, it is recommended that you clone the branch for this part To get started, navigate into the directory of your choice and run the following command to clone the repository: ```shell git clone -b part-4 https://github.com/prisma/awesome-links.git ``` Navigate into the cloned application and install the dependencies: ```shell cd awesome-links npm install ``` ## Seeding the database [#seeding-the-database] After setting up a PostgreSQL database, rename the `env.example` file to `.env` and set the connection string for your database. After that, run the following command to create the tables in your database: ```shell npx prisma migrate dev --name init ``` If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` > Refer to [Part 1 – Add Prisma to your Project](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw#add-prisma-to-your-project) for more details on the format of the connection string. This command will run the `seed.ts` file in the `/prisma` directory. `seed.ts` creates four links and one user in your database using Prisma Client. > Note: Refer to [part 3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv) for how to set up Auth0. ### Project structure and dependencies [#project-structure-and-dependencies] You will see the following folder structure: ```txt awesome-links/ ┣ components/ ┃ ┣ Layout ┃ ┗ AwesomeLink.tsx ┣ data/ ┃ ┗ links.ts ┣ graphql/ ┃ ┣ types/ ┃ ┣ context.ts ┃ ┣ schema.graphql ┃ ┗ schema.ts ┣ lib/ ┃ ┣ apollo.ts ┃ ┗ prisma.ts ┣ pages/ ┃ ┣ api/ ┃ ┃ ┣ auth ┃ ┃ ┃ ┣ [...auth0].ts ┃ ┃ ┃ ┗ hook.ts ┃ ┃ ┗ graphql.ts ┃ ┣ link/ ┃ ┣ _app.tsx ┃ ┣ admin.tsx ┃ ┣ favorites.tsx ┃ ┗ index.tsx ┣ prisma/ ┃ ┣ migrations/ ┃ ┣ schema.prisma ┃ ┗ seed.ts ┣ public/ ┣ styles/ ┃ ┗ tailwind.css ┣ .env.example ┣ .gitignore ┣ next-env.d.ts ┣ package-lock.json ┣ package.json ┣ postcss.config.js ┣ README.md ┣ tailwind.config.js ┗ tsconfig.json ``` This is a Next.js application that uses the following libraries and tools: * [Prisma](https://www.prisma.io/) for database access/CRUD operations * [Next.js](https://nextjs.org) as the fullstack React framework * [TailwindCSS](https://tailwindcss.com) for styling * [Pothos](https://pothos-graphql.dev) as the GraphQL schema construction library * [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) as the GraphQL server * [Apollo Client](https://www.apollographql.com/docs/react) as the GraphQL client * [Auth0](https://auth0.com) for authentication and authorization. The `pages` directory contains the following files: * `index.tsx`: page that displays all links in the app. Supports pagination * `link/[id].tsx`: page that displays an individual link and allows users to bookmark it. * `admin.tsx`: admin page that requires the logged in user to have the `ADMIN` role. This page allows admins to create new links. * `favorites.tsx`: page that shows a user's bookmarked links. * `_app.tsx`: global app component. Allows you to persist layout between page changes and keeping state when navigating pages. * `api/graphql.ts`: GraphQL endpoint using Next.js's [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes). * `api/auth/[...auth0].ts`: [dynamic API route](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes) Auth0 creates to handle authentication. * `api/auth/hook.ts`: API route that handles creating a user record in the database. ## Using AWS S3 to add support for image upload [#using-aws-s3-to-add-support-for-image-upload] In the application's current state, an admin can create a link. However, the admin cannot attach an image to the created link. This guide will teach you how you can leverage [AWS S3](https://aws.amazon.com/s3) – an object storage service – to upload images. > Note: To continue with the tutorial, you need an AWS account. You can create an account [here](https://portal.aws.amazon.com/billing/signup). AWS will require you to provide a credit card to complete the signup. AWS provides a [free tier](https://aws.amazon.com/free) that will allow you to explore different AWS services – including S3 – for free. ### Create an Identity Access Management user [#create-an-identity-access-management-user] To interact with AWS resources, you have to create an [Identity Access Management (IAM) user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) with the proper permissions. The IAM user will allow you to interact with resources on AWS programmatically. To do that, select **Security Credentials** from the dropdown menu located in the top right corner of the page – where your username is. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-management-console.png) Next, select the **Users** option from the **Access Management** dropdown located in the left sidebar. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-users.png) Next, click the **Add users** button to create a new user. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-all-users.png) Provide a recognizable username for the newly created user and check the **Access key - programmatic access** checkbox. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-pick-username.png) Next, you need to specify what the user can do with different AWS resources by setting permissions. Select the **Attach existing policies directly** option and type "S3" in the search filter. Select **AmazonS3FullAccess**. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-permissions.png) You can optionally define tags for newly created IAM users, but you do not need to do that for this project, so click **Next: Review**. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-optional-tags.png) After reviewing that the user has the correct permissions and username, click **Create user**. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-create-user-screen.png) Finally, copy the "Access Key ID" and the "Secret Access Key" and store them in your `.env` file ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-iam-user-credentials.png) > Note: These credentials can only be viewed once, so in case you lose these credentials or want to change them, you will need to generate new ones. ```shell #.env APP_AWS_ACCESS_KEY = '' APP_AWS_SECRET_KEY = '' ``` ### Create and configure a new S3 bucket [#create-and-configure-a-new-s3-bucket] The next step is to create an AWS S3 bucket which will store the uploaded objects. You can find the S3 service by looking it up in the search bar or by going to [https://s3.console.aws.amazon.com/](https://s3.console.aws.amazon.com/). ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-select-s3.png) > Note: there are different ways you can provison resources in AWS, you are using the AWS console for simplicity Next, click on **Create bucket** to create a new bucket. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3.png) Pick a name and a region for your bucket. Save these values in your `.env` file. ```shell # .env APP_AWS_REGION = '' AWS_S3_BUCKET_NAME = '' # Will be used in an API route. NEXT_PUBLIC_AWS_S3_BUCKET_NAME = '' # Will be used on the client-side ``` > Note: The bucket name has to be unique and must not contain any spaces or uppercase letters. Go ahead and create the bucket by navigating to the bottom of the page and clicking the **Create bucket** button. You can stick with the defaults settings for now, but you'll update them in the following steps. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-create-bucket.png) Once S3 has provisioned the Bucket, navigate to it by selecting it in the table. Navigate to the **Permissions** tab and click on the **Edit** button in the **Block public access (bucket settings)** section. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-bucket-permissions.png) Uncheck **Block *all* public access** and click on **Save changes**. You need to allow public access because your application needs to access the images uploaded on AWS S3. ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-access-settings.png) Next, update the resource policy to grant the application access to the Bucket and its contents. In the **Permissions** of your S3 Bucket, navigate to the **Bucket policy** section. Select **Edit** and add the following while changing "name-of-your-bucket" placeholder to the name of your Bucket: ```json { "Version": "2008-10-17", "Statement": [ { "Sid": "AllowPublicRead", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::name-of-your-bucket/*" } ] } ``` ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-bucket-policy.png) Next, you need to allow your application, which will be on a different domain, to access the stored images. In the **Permissions** tab of your bucket, scroll to the **Cross-origin Resource Sharing (CORS)** section at the bottom and add the following to it: ```json [ { "AllowedHeaders": ["*"], "AllowedMethods": ["PUT", "POST", "DELETE"], "AllowedOrigins": ["http://localhost:3000"], "ExposeHeaders": [] } ] ``` ![AWS S3](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-cors.png) > Note: Before deploying your application, ensure you update the "AllowedOrigins" array with the URL pointing to your application. ### Add image upload functionality to your application [#add-image-upload-functionality-to-your-application] Now that you've set up S3, the next step is adding image upload functionality to your application. You'll create an API endpoint and update the frontend to handle image upload. First, install the `aws-sdk` package by running the following command: ```shell npm install aws-sdk ``` Next, create a new file called `upload-image.ts` located in the `pages/api/` directory and add the following code to it: ```ts // pages/api/upload-image.ts import aws from 'aws-sdk' import type { NextApiRequest, NextApiResponse } from 'next' export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { // 1. const s3 = new aws.S3({ accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, }) // 2. aws.config.update({ accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, signatureVersion: 'v4', }) // 3. const post = await s3.createPresignedPost({ Bucket: process.env.AWS_S3_BUCKET_NAME, Fields: { key: req.query.file, }, Expires: 60, // seconds Conditions: [ ['content-length-range', 0, 5048576], // up to 1 MB ], }) // 4. return res.status(200).json(post) } catch (error) { console.log(error) } } ``` 1. Creates a new instance of the S3 Bucket 2. Updates the main configuration class with the region, credentials, and additional request options 3. Generates a presigned URL allowing you to write to the S3 Bucket 4. Return the presigned URL that will be used for file upload Finally, update the `pages/admin.tsx` file with the following code: ```tsx // pages/admin.tsx import React from 'react' import { type SubmitHandler, useForm } from 'react-hook-form' import { gql, useMutation } from '@apollo/client' import toast, { Toaster } from 'react-hot-toast' import type { GetServerSideProps } from 'next' import { getSession } from '@auth0/nextjs-auth0' type FormValues = { title: string; url: string; category: string; description: string; image: FileList; } const CreateLinkMutation = gql` mutation($title: String!, $url: String!, $imageUrl: String!, $category: String!, $description: String!) { createLink(title: $title, url: $url, imageUrl: $imageUrl, category: $category, description: $description) { title url imageUrl category description } } ` const Admin = () => { const [createLink, { data, loading, error }] = useMutation(CreateLinkMutation) const { register, handleSubmit, formState: { errors }, } = useForm() // Upload photo function const uploadPhoto = async (e: React.ChangeEvent) => { if (!e.target.files || e.target.files.length <= 0) return const file = e.target.files[0] const filename = encodeURIComponent(file.name) const res = await fetch(`/api/upload-image?file=${filename}`) const data = await res.json() const formData = new FormData() Object.entries({ ...data.fields, file }).forEach(([key, value]) => { // @ts-ignore formData.append(key, value) }) toast.promise( fetch(data.url, { method: 'POST', body: formData, }), { loading: 'Uploading...', success: 'Image successfully uploaded!🎉', error: `Upload failed 😥 Please try again ${error}`, }, ) } const onSubmit: SubmitHandler = async (data) => { const { title, url, category, description, image } = data const imageUrl = `https://${process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${image[0]?.name}` const variables = { title, url, category, description, imageUrl } try { toast.promise(createLink({ variables }), { loading: 'Creating new link..', success: 'Link successfully created!🎉', error: `Something went wrong 😥 Please try again - ${error}`, }) } catch (error) { console.error(error) } } return (

Create a new link

) } export default Admin // getServerSideProps code remains unchanged ``` The form includes a new input field to handle file upload. The input field accepts images of either `.png` or `.jpeg` formats. Whenever an image is uploaded, the `uploadPhoto` function sends a request to the `/api/upload-image` API endpoint. A toast will be shown as the request is being resolved by the API – success, loading, or error states. When the form is submitted, the URL of the image is included as a variable in the `createLink` mutation. A toast will appear as the mutation is being executed. ## Summary and next steps [#summary-and-next-steps] You learned how to add support for image upload using AWS S3. In the [next part](https://www.prisma.io/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c), you will deploy your app to Vercel and learn how you can use the Prisma Data Proxy to manage your database connection pool to ensure your application doesn't run out of connections.