~ 5 min read

Next.js Image Upload to Amazon S3 2023

Share:

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.

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.

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.