I am trying to upload an image file to an AWS S3 bucket. I am using Node/Express to generate a presigned URL to upload said image directly to s3 from the React frontend. The thing is I am able to upload the file as a file (same size as on local PC) but it does not have the file extension. That is, it has only the signed character e.g. 9c9743b9-4dd7-4afa-af06-c060fb0fb175
with file type of file instead of 9c9743b9-4dd7-4afa-af06-c060fb0fb175.png
with png file type
Here is a brief snippet of what I have tried so far
Backend
// My controller
try {
const payload = await AwsS3Service.getSignedUrlService();
res.status(200).send({
success: true,
data: payload
});
...
const s3 = new aws.S3({
region: config.awsS3.region,
accessKeyId: config.awsS3.accessKeyId,
secretAccessKey: config.awsS3.secretAccessKey,
signatureVersion: config.awsS3.signatureVersion
})
const getSignedUrlService = async () => {
const imageName = crypto.randomUUID()
const params = ({
Bucket: config.awsS3.bucketName,
Key: imageName,
Expires: 120 // in sec
})
const uploadUrl = await s3.getSignedUrlPromise('putObject', params)
return uploadUrl
}
Frontend
const [final, setFinal] = useState<FileList | any>('')
const [img, setImg] = useState<typeof formState.file>(null)
const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files && e.target.files[0]
if (e.target.files) {
setFinal(e.target.files[0]!)
}
const { type, name } = file as File
setImgName(name)
setImgType(type)
const fileReader = new FileReader()
fileReader.onload = (e) => {
setImg(e.target?.result)
}
fileReader.readAsDataURL(file as Blob)
}
const handleFormSubmit: SubmitHandler<IImage> = async (data: IImage) => {
try {
const signedUrl = await axios({ method: 'GET', url: `${config.API_URL}/awss3` })
const formData = new FormData()
formData.append('file', final[0])
const resUpload = await fetch(`${signedUrl.data.data}`, {
method: 'PUT',
headers: { 'Content-Type': 'multipart/form-data' },
body: final
})
if (resUpload.status === 200) {
// redirect
}
} catch(err: any) {
alert(`An error occured. ${err.message}`)
}
}
return (
<form onSubmit={e => e.preventDefault()}>
<Stack>
<Box>
<InputLabel htmlFor='imgfile'>Image File</InputLabel>
<picture>
{ img
? <img src={img} alt='Image preview' height={90} width={160} />
: <Box sx={{ height: 90, width: 160, backgroundColor: '#555' }}></Box>
}
</picture>
<input type='file' accept='image/*' id='imgfile' name='imgFile' onChange={handleImageChange} />
</Box>
</Stack>
<Button type='submit' onClick={handleSubmit(handleFormSubmit)}>
Submit
</Button>
</form>
)
I have been unable to solve this for a while now. Any help is much appreciated. Thank you for reading.
CodePudding user response:
Posting this as a self-answer just in case anyone is interested.
I managed to work around this by adding the file name, type, and extension when making the sign request as a POST request instead and attaching the file name and file type in the request body.
// Frontend
const signedUrl = await axios({
method: 'POST',
url: `${config.API_URL}/awss3`,
headers: { 'Content-Type': 'application/json' },
data: { name: imgName, type: imgType }
})
And added them in the Key
// Backend
const getSignedUrlService = async (name, type) => {
// name and type from req.body
const imageName = crypto.randomUUID() '-' name
const params = ({
Bucket: config.awsS3.bucketName,
Key: imageName,
ContentType: type,
Expires: 120 // in sec
})
const uploadUrl = await s3.getSignedUrlPromise('putObject', params)
return uploadUrl
}
Thanks to everyone who helped