Next.js Image Upload to Amazon S3 2023

Code drop for uploading images via a Next.js API route to Amazon AWS S3 using React, Next.js and Amazon S3


This tutorial demonstrates how to use a pre-signed URL to upload an image to Amazon/AWS S3 and store the link in a Prisma database.

Table of contents

Backend API Route code

import { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../client";
import { getSession } from "next-auth/client";
import aws from "aws-sdk";
 
export default async function (req: NextApiRequest, res: NextApiResponse) {
	const session = await getSession({ req });
 
	// Update AWS configuration with the provided credentials
	aws.config.update({
		region: "eu-west-2",
		accessKeyId: process.env.AWS_ACCESS_KEY,
		secretAccessKey: process.env.AWS_SECRET,
	});
 
	const s3Bucket = process.env.AWS_BUCKET;
 
	// Create a new instance of S3
	const s3 = new aws.S3();
	const fileName = req.body.fileName;
	const fileType = req.body.fileType;
 
	const s3Params = {
		Bucket: s3Bucket,
		Key: `businesslogos/${fileName}`,
		ContentType: fileType,
		ACL: "public-read",
	};
 
	try {
		// Get a signed URL from S3 for uploading an object
		s3.getSignedUrl("putObject", s3Params, async (err, data) => {
			if (err) {
				return res.json({ success: false, error: err });
			}
			const returnData = {
				signedRequest: data,
				url: `https://${s3Bucket}.s3.amazonaws.com/businesslogos/${fileName}`,
			};
			const imageUrl = await prisma.user.update({
				where: {
					email: session.user.email,
				},
				data: {
					business: {
						update: {
							businessLogo: returnData.url,
						},
					},
				},
			});
 
			return res.status(200).json(returnData);
		});
	} catch (err) {
		return res.status(500).json(err);
	}
}
 
const handleUpload = (ev) => {
	let file = uploadInput.current.files[0];
	// Split the filename to get the name and type
	let fileParts = uploadInput.current.files[0].name.split(".");
	let fileName = fileParts[0];
	let fileType = fileParts[1];
	axios
		.post("/api/awsimageupload", {
			fileName: fileName,
			fileType: fileType,
		})
		.then((res) => {
			const signedRequest = res.data.signedRequest;
			const url = res.data.url;
			setUploadState({
				...uploadState,
				url,
			});
 
			var options = {
				headers: {
					"Content-Type": fileType,
				},
			};
			axios
				.put(signedRequest, file, options)
				.then((_) => {
					setUploadState({ ...uploadState, success: true });
					mutate();
				})
				.catch((_) => {
					toast("error", "We could not upload your image");
				});
		})
		.catch((error) => {
			toast("error", "We could not upload your image");
		});
};

Front end component

Below is the code for the front-end component. It includes an input for selecting a file, a button to trigger the upload, and a function to handle the upload process.

import { useRef, useState } from "react";
import axios from "axios";
import { toast } from "react-toastify";
 
const ImageUpload = () => {
  const uploadInput = useRef(null);
  const [uploadState, setUploadState] = useState({});
 
  const handleUpload = (ev) => {
    let file = uploadInput.current.files[0];
    // Split the filename to get the name and type
    let fileParts = uploadInput.current.files[0].name.split(".");
    let fileName = fileParts[0];
    let fileType = fileParts[1];
 
    // Post the file information to the server to obtain a signed URL
    axios
      .post("/api/awsimageupload", {
        fileName: fileName,
        fileType: fileType,
      })
      .then((res) => {
        const signedRequest = res.data.signedRequest;
        const url = res.data.url;
        setUploadState({
          ...uploadState,
          url,
        });
 
        // Perform the actual upload using the signed URL
        const options = {
          headers: {
            "Content-Type": fileType,
          },
        };
        axios
          .put(signedRequest, file, options)
          .then((_) => {
            setUploadState({ ...uploadState, success: true });
            toast("success", "Image uploaded successfully");
          })
          .catch((_) => {
            toast("error", "We could not upload your image");
          });
      })
      .catch((error) => {
        toast("error", "We could not upload your image");
      });
  };
 
  return (
    <div>
      <input
        type="file"
        ref={uploadInput}
        onChange={handleUpload}
        style={{ display: "none" }}
      />
      <button onClick={() => uploadInput.current.click()}>Upload Image</button>
      {uploadState.success && <p>Image uploaded successfully!</p>}
    </div>
  );
};
 
export default ImageUpload;

Now, your Next.js application can upload images to Amazon S3 and store the link in a Prisma database. The backend API route handles generating a pre-signed URL for uploading to S3 and updating the Prisma database, while the front-end component handles user interactions and image uploads.

Copyright Adam Richardson © 2024 - All rights reserved
𝕏