title: Proxy multipart form data using NodeJS and node-fetch
body: <p>How can I convert an incoming NodeJS/Express Request (req) object into something that can be sent over in a proxy request?</p>
  <p>My NodeJS/express service uses node-fetch to proxy requests to a separate server. The incoming curl request looks like this (Firefox POSIX curl)</p>
  <pre><code>curl 'https://localhost:8080/bluecost/spreadsheet/upload/SSCSpreadsheetUpload-09192020.xlsx' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Content-Type: multipart/form-data; boundary=---------------------------37552304939101372432093632492' -H 'Origin: https://localhost:8080' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: https://localhost:8080/spreadsheet-upload' -H 'Cookie: connect.sid=s%3ARxnRck1VzcCXk05LKV1CJ5PeslCK1sWC.WqqK%2B0CHsAP0MpcFzRFbHTh19YOoeBi0mIAKCPQ%2BSGU; access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcHJpbnQtbWlkZGxld2FyZS1pc3N1ZXIiLCJzdWIiOiJCZW4uUHJhY2h0MUBpYm0uY29tIiwibm90ZXNJZCI6IkJlbiBQcmFjaHQiLCJzZXJpYWxOdW0iOiI4Njc1NTU4OTciLCJleHAiOjE2MTQ4MjgwNzksImJsdWVHcm91cHMiOlsiQkxVRUNPU1RfU1BSRUFEU0hFRVRfVVBMT0FEX1RFU1QiXSwiaWF0IjoxNjE0ODIwODk3fQ.jWhvsHZiJLRvAHG7SDTmOkdcpMRHxiaLUWDhfJmRXv0' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' --data-binary $'-----------------------------37552304939101372432093632492\r\nContent-Disposition: form-data; name=&quot;file&quot;; filename=&quot;SSCSpreadsheetUpload-09192020.xlsx&quot;\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n-----------------------------37552304939101372432093632492--\r\n'
  </code></pre>
  <p>A simplified version of the routing looks like this:</p>
  <pre><code>app.use('/multipart',(req,res,next) =&gt; {
              const URL = require('url').URL;
              var spreadsheet_client_url = new URL(config.spreadsheet_client_host);
              spreadsheet_client_url.pathname = req.originalUrl;
              // spreadsheet_client_url.pathname = spreadsheet_client_url.pathname.replace('/spreadsheet-upload', '/');
              logger.debug('/spreadsheet-upload: Generating token based on proxyReqOpts.user=' + req.user);
  
              logger.info(`@@Request ${req.method} ${spreadsheet_client_url.href}`);
              var accessToken = generateToken(req.user);
              /**
               * Set bearer token based on code from Sprint-API's lua code
               */
              // req.setHeader('Authorization', 'Bearer ' + accessToken);
              logger.debug('Route: /spreadsheet-upload: adding Authorization: Bearer ' + accessToken);
              var newHeaders = req.headers;
              newHeaders['Authorization'] = 'Bearer ' + accessToken;
              if (isMultipartRequest(req)) {
                  newHeaders['Content-Length'] = req.body.length;
                  // build a string in multipart/form-data format with the data you need
                  const formdataUser =
                      `--${request.headers['content-type'].replace(/^.*boundary=(.*)$/, '$1')}\r\n` +
                      `Content-Disposition: form-data; name=&quot;reqUser&quot;\r\n` +
                      `\r\n` +
                      `${JSON.stringify(req.user)}\r\n`
  
              } else {
  
                  const options = {
                      port: spreadsheet_client_url.port,
                      path: spreadsheet_client_url.pathname,
                      body: req.body,
                      method: req.method,
                      headers: newHeaders,
                      redirect: true
                  };
                  console.log('@@@');
                  console.log('@@@ url: ' + spreadsheet_client_url.href + 'post-options: ' + JSON.stringify(options));
                  console.log('@@@');
                  const fetch = require('node-fetch');
                  fetch(spreadsheet_client_url, options).then(r =&gt; {
                      console.log('@@ reached fetch result retrieved');
                      return r.text();
                  }).then(fetchText =&gt; {
                      console.log('@@ reached fetch text result retrieved');
                      /*
                      // This code complains that headders can't be set after sending to client
                      res.writeHead(res.statusCode,res.headers);
                      res.send(fetchText);
                      */
                      // res.body=fetchText;
                      res.status(200).send(fetchText);
  
                      next();
                  }
              }
  
              )
          .catch(err =&gt; { console.log('Error fetching ' + err) });
  })
  
  function isMultipartRequest(req) {
      let contentTypeHeader = req.headers['content-type'];
      return contentTypeHeader &amp;&amp; contentTypeHeader.indexOf('multipart') &gt; -1;
  }
  
  </code></pre>
  <p>So I have an incoming request, and I believe I can reliably distinguish between normal and multi-part requests. Many examples suggest creating this &quot;form-data&quot; object, then sending it over like this:</p>
  <pre><code>const FormData = require('form-data');     
  const form = new FormData();
  
  const buffer = // e.g. `fs.readFileSync('./fileLocation');
  const fileName = 'test.txt';
  
  form.append('file', buffer, {
    contentType: 'text/plain',
    name: 'file',
    filename: fileName,
  });
  
  fetch('https://httpbin.org/post', { method: 'POST', body: form })
      .then(res =&gt; res.json())
      .then(json =&gt; console.log(json));
  </code></pre>
  <p>I'd like to keep using node-fetch because it's the devil I know best. Should I use a form-data object and pass it over like above? Is there a better way? I'd like to know how to build a form-data object from a request object given I only know that it's multi-part, nothing else.</p>
  <p>The ultimate destination is a Java Spring-Boot server that takes a MultipartFile file as follows:</p>
  <pre><code>    @PostMapping(&quot;/spreadsheet/upload/{filename}&quot;)
      public ResponseEntity&lt;HashMap&lt;String,Object&gt;&gt; uploadSpreadsheet(@RequestBody MultipartFile file, @PathVariable(&quot;filename&quot;) String filename) {
  </code></pre>
  <p>With normal non-multi-part form requests, it matches enough to reach this method, but the &quot;file&quot; object is null.</p>