54

How do I attach a file in Node or Node Fetch POST request? I am trying to invoke an API which will import a CSV or XLS file. Is this possible using Node or Node Fetch?

DᴀʀᴛʜVᴀᴅᴇʀ
  • 7,681
  • 17
  • 73
  • 127
Rocky
  • 857
  • 2
  • 8
  • 15
  • just to be clear: you want to create an endpoint which will accept file as input and store / process it on your nodejs server using [node-fetch](https://www.npmjs.com/package/node-fetch) ? – Kunal Parekh Jun 19 '17 at 07:54
  • 2
    Hmm here is my understanding of the question: it does not involve a nodejs server. They want to POST a file to a service (which service is not important) using `node-fetch` from within a nodejs program (so that program would be an http client from that perspective. It could also be a server for other purposes but that is irrelevant). – Hugues M. Jun 21 '17 at 17:38

2 Answers2

70

README.md says:

Use native stream for body, on both request and response.

And sources indicate it supports several types, like Stream, Buffer, Blob... and also will try to coerce as String for other types.

Below snippet shows 3 examples, all work, with v1.7.1 or 2.0.0-alpha5 (see also other example further down with FormData):

let fetch = require('node-fetch');
let fs = require('fs');

const stats = fs.statSync("foo.txt");
const fileSizeInBytes = stats.size;

// You can pass any of the 3 objects below as body
let readStream = fs.createReadStream('foo.txt');
//var stringContent = fs.readFileSync('foo.txt', 'utf8');
//var bufferContent = fs.readFileSync('foo.txt');

fetch('http://httpbin.org/post', {
    method: 'POST',
    headers: {
        "Content-length": fileSizeInBytes
    },
    body: readStream // Here, stringContent or bufferContent would also work
})
.then(function(res) {
    return res.json();
}).then(function(json) {
    console.log(json);
});

Here is foo.txt:

hello world!
how do you do?

Note: http://httpbin.org/post replies with JSON that contains details on request that was sent.

Result:

{
  "args": {}, 
  "data": "hello world!\nhow do you do?\n", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip,deflate", 
    "Connection": "close", 
    "Content-Length": "28", 
    "Host": "httpbin.org", 
    "User-Agent": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
  }, 
  "json": null, 
  "origin": "86.247.18.156", 
  "url": "http://httpbin.org/post"
}

If you need to send a file as part of a form with more parameters, you can try:

  • npm install form-data
  • pass a FormData object as body (FormData is a kind of Stream, via CombinedStream library)
  • do not pass header in the options (unlike examples above)

and then this works:

const formData = new FormData();
formData.append('file', fs.createReadStream('foo.txt'));
formData.append('blah', 42);
fetch('http://httpbin.org/post', {
    method: 'POST',
    body: formData
})

Result (just showing what is sent):

----------------------------802616704485543852140629
Content-Disposition: form-data; name="file"; filename="foo.txt"
Content-Type: text/plain

hello world!
how do you do?

----------------------------802616704485543852140629
Content-Disposition: form-data; name="blah"

42
----------------------------802616704485543852140629--
Hugues M.
  • 19,846
  • 6
  • 37
  • 65
  • Is there way of forming the above structure without using form-data module, that can be then consumed by the node-fetch body parameter? – Avinash Mar 02 '20 at 14:31
  • @Avinash You would have to implement [the spec](https://www.ietf.org/rfc/rfc2388.txt) ‍♂️. If your input is not arbitrarily complex, it might be straightforward. [Here](https://ec.haxx.se/http/http-multipart) is an easier to read explanation. – Hugues M. Mar 14 '20 at 18:42
  • @HuguesM. How did 'filename="foo.txt" get into the body? I only see the data being added. – Jim B. Dec 14 '20 at 03:18
  • How would you fetch the multipart portion from an incoming request object? – Woodsman Mar 04 '21 at 15:50
43

I was looking for how to use node-fetch to upload files via multipart/form-data and their GitHub docs actually shows how to do this. Below is modified example showing how to attach a buffer to FormData and upload that.

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 => res.json())
    .then(json => console.log(json));

Sharing this for anyone else who googled "node-fetch upload file multipart" like I did.

Rico Kahler
  • 17,616
  • 11
  • 59
  • 85
  • Nice thanks @rico-kahler, I've removed my down vote. Note to others, personally wouldn't ever use a buffer or read files synchronously, since large files will exceed RAM and take a significant amount of time to read. Unfortunately that form-data package doesn't seem to work well with streams, I think the issue is caused by the lack of a content length in the request header and content length is somewhat complex to calculate. – Ryan Smith May 09 '19 at 20:19
  • It works for me, but instead of append('file') need to add append('upload') – Ruslan Korkin Aug 14 '19 at 11:44
  • the above request doesn't work if we want to send the request with headers. Could you show how to send it with headers? – Chandara Chea Apr 07 '20 at 06:50
  • @ChandaraChea add headers to the fetch API options: https://github.com/node-fetch/node-fetch#fetch-options e.g. `fetch('/blah', { headers: { /* ... */} })` – Rico Kahler Apr 07 '20 at 21:04
  • 1
    passing the contentType, name and fileName options are what made the API I was POSting to accept the file. Thank you! – Iainure Mar 11 '21 at 16:04