The purpose is - -> to generate signed url in api/fileupload.js file to upload the file to GCS. -> Obtain the signed url from Nextjs server via nextjs API - localhost://3000/api/fileupload -> Uploading file to gcs using the generated signed url in index.jsx file
Signed URL is generated successfully. But while uploading the image body as form data to GCS, an error of 403 code is occured. Here is the response body.
body : (...)
bodyUsed : false
headers :
Headers {}
ok : false
redirected : false
status : 0
statusText : ""
type : "opaque"
url : ""
Is the way to uploading file as formdata correct in index.jsx file? or what am I missing here?
The two files are given below -
index.jsx for nextjs file -
import { useState } from "react";
export default function Home() {
const [url, setUrl] = useState("");
const [file, setFile] = useState<any>(null);
const [dataloaded, setDataloaded] = useState(true);
const handleSubmit = async (e: any) => {
setDataloaded(false);
e.preventDefault();
let formData = new FormData();
formData.append("file", file.data);
formData.append("Content-Type", `${file.data.type}`);
console.log(file);
const response = await fetch("http://localhost:3000/api/fileupload", {
method: "POST",
body: formData
});
const responseWithBody = await response.json();
console.log(responseWithBody);
setDataloaded(true);
if (response.status === 200) {
setUrl(responseWithBody.url);
} else {
console.log("error in generating url");
}
const response1 = await fetch(
responseWithBody.url,
{
mode: "no-cors",
method: "POST",
body: formData,
headers: {
"Access-Control-Allow-Origin": "*",
"content-type": "image/png"
}
}
);
console.log(response1);
};
const handleFileChange = (e: any) => {
const img = {
preview: URL.createObjectURL(e.target.files[0]),
data: e.target.files[0]
};
setFile(img);
};
return (
<>
<div className="form-container">
<form onSubmit={handleSubmit}>
<div className="image-preview-container">
{file ? (
<img src={file.preview} alt="File to upload" />
) : (
<img
src="https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg"
alt="Fallback"
/>
)}
</div>
<div className="file-name">
{file && file.data.name}
{url && (
<a href={url} target="_blank" rel="noreferrer">
FiExternalLink
</a>
)}
</div>
<input
type="file"
name="file"
onChange={handleFileChange}
className="custom-file-input"
></input>
<button
className="submit-button"
type="submit"
disabled={!file}
onClick={handleSubmit}
>
Submit
</button>
</form>
</div>
</>
);
}
fileupload.js in api/ folder -
import { Storage } from "@google-cloud/storage";
import multer from "multer";
import type { NextApiRequest, NextApiResponse } from "next";
const storage = new Storage({
keyFilename: `service_account_key.json`,
projectId: "my-project-id"
});
const bucketName = "my-bucket-name";
async function parseFormData(
req: NextApiRequest & { files?: any },
res: NextApiResponse
) {
const storage = multer.memoryStorage();
const multerUpload = multer({ storage });
const multerFiles = multerUpload.any();
await new Promise((resolve, reject) => {
multerFiles(req as any, res as any, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
return {
fields: req.body,
files: req.files
};
}
export default async function handler(
req: NextApiRequest & { files?: any },
res: NextApiResponse<any>
) {
const options = {
version: "v4",
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: "application/octet-stream"
} as any;
const result = await parseFormData(req, res);
// console.log(result);
const file = storage
.bucket(bucketName)
.file(result?.files[0]?.originalname || "new-file.png");
const [url]: any = await file.getSignedUrl(options);
console.log("Generated PUT signed URL:");
console.log(url);
res.status(200).json({ url: url });
}