Search code examples
javascriptnode.jskoa

Upload file without FormData


I am using XMLHttpRequest to upload large files from the browser directly to Amazon S3 like this (which works):

export const fileUploader = async (url, file) => {
  const xhr = new XMLHttpRequest()

  xhr.upload.addEventListener('load', () => {
    // ...
  })
  xhr.upload.addEventListener('error', () => {
    // ...
  })
  xhr.upload.addEventListener('abort', () => {
    // ...
  })
  xhr.upload.addEventListener('progress', () => {
    // ...
  })

  xhr.open('PUT', url)
  xhr.setRequestHeader('content-type', file.type)
  xhr.setRequestHeader('x_file_name', file.name)
  xhr.send(file)
}

For local development and testing I'd like to create a route in my node.js server which will accept files for upload like this.

Server-side, the request.body is always empty:

router.put('/image-upload', koaBody(), async (ctx) => {
  console.log(ctx.request)

  // { method: 'PUT',
  // url: '/image-upload',
  // header:
  //  { host: 'localhost:3500',
  //    connection: 'keep-alive',
  //    'content-length': '324285',
  //    pragma: 'no-cache',
  //    'cache-control': 'no-cache',
  //    origin: 'http://localhost:3000',
  //    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.10 Safari/537.36',
  //    x_file_name: 'l1lnRw.jpg',
  //    'content-type': 'image/jpeg',
  //    accept: '*/*',
  //    referer: 'http://localhost:3000/gallery/4',
  //    'accept-encoding': 'gzip, deflate, br',
  //    'accept-language': 'en-US,en;q=0.9,fr;q=0.8' } }


  console.log(ctx.request.body) // {}
  console.log(ctx.req.body) // undefined
})

How do I go about uploading a file 'directly' to a Koa node.js server without wrapping it in FormData()? Thanks.


Solution

  • Here is how you upload a file without wrapping in FormData() in Koa:

    import getRawBody from 'raw-body'
    
    router.put('/image-upload', async (ctx) => {
      const file = await getRawBody(ctx.req)
      const bufferStream = new stream.PassThrough()
      const writeStream = fs.createWriteStream(`${config.staticDir}/file.jpg`)
      bufferStream.end(file)
      bufferStream.pipe(writeStream)
    
      ctx.body = {
        status: 'uploaded!'
      }
    })