Home > database >  Unable to upload image file to s3 from React with Node presigned url
Unable to upload image file to s3 from React with Node presigned url

Time:12-29

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

  • Related