'Uploading a Blob held in State to AWS S3

I have implemented a mic-recorder-to-mp3 component in a ReactJS NextJS app which stores a voice-memo recorded in the browser by the user and saves the resulting blob to React state, as well as a resulting MP3 to state as well.

I am struggling to upload either the blob or the MP3 file to AWS S3 - the problem is evident in that I cannot parse the req.body string which is received by the API.

Here is some code! This is the function that stores the raw audio as state:

const [audioBlob, setAudioBlob] = useState(null)
const [blobURL, setBlobUrl] = useState(null)
const [audioFile, setAudioFile] = useState(null)


const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, 'audio.mp3', {
          type: blob.type,
          lastModified: Date.now()
        })
        setAudioBlob(blob)
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch(e => console.log(e))
  }

And this is the function that sends the payload to the API:

  const submitVoiceMemo = async () => {
    const filename = encodeURIComponent(audioFile)    
    const res = await fetch(`/api/upload-url?file=${filename}`)
    const { url, fields } = await res.json()
    const formData = new FormData()

    Object.entries({ ...fields, audioFile }).forEach(([key, value]) => {
      formData.append(key, value)
    })
    const upload = await fetch(url, {
      method: 'PUT',
      body: formData
    })
    if (upload.ok) {
      console.log('Uploaded successfully!')
    } else {
      console.error('Upload failed.')
      console.error()
    }
  }

This is the upload-url API Route:

module.exports = async (req, res) => {
  try {
    aws.config.update({
      accessKeyId: process.env.AWS_ACCESS_KEY_1,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_ID,
      region: 'us-east-2',
      signatureVersion: 'v4'
    })

    const s3 = new aws.S3()
    const post = await s3.createPresignedPost({
      Bucket: 'waveforms',
      Key: `voicememo/${req.query.file}`,
      ContentType: 'audio/mpeg',
      ACL: 'public-read',
      Expires: 60
    })
    res.status(200).json(post
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
}

It currently returns a 400 Bad Request error.


This is an alternative solution, which does upload an MP3 successfully to S3, however the file is not playing when accessed via the S3 console, although it does have a filesize.

module.exports = requireAuth(async (req, res) => {
  try {
    AWS.config.update({
      accessKeyId: process.env.AWS_ACCESS_KEY_1,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_ID,
      region: 'us-east-2',
      signatureVersion: 'v4'
    })

    const s3 = new AWS.S3();

    const uuid = randomUUID()
    const s3Params = {
      Bucket: 'waveforms',
      Key: `voicememo/${uuid}.mp3`,
      Body: req.body,
      ContentType: 'audio/mp3',
      ACL: 'public-read'
    }

    await s3.upload(s3Params).promise()
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
})

And this is the accompanying fetch request.

const submitVoiceMemo = async () => {
    try {
      await fetch('/api/createVoiceMemo', {
        method: 'PUT',
        body: audioBlob
      })
    } catch (error) {
      console.error(error)
    }
  }


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source