How can I test file-upload with hapi? - node.js

I have this payload format in my hapi route that accepts only multipart/form-data data and the output is set as a stream:
payload: {
maxBytes: 1024,
output: 'stream',
parse: true,
allow: 'multipart/form-data',
}
I want to test that route and my payload is this:
const FD = new FormData();
FD.append('field1', 'value');
FD.append('field2', 'value');
The hapi inject method looks like this:
const res = await server.inject({
method,
url,
headers: {
...
'Content-Type': 'multipart/form-data; boundary=--SEPARATION--',
},
payload: FD,
});
I am getting
Invalid multipart payload format
I tried to set a stream using a Steam object too but it doesn't work. Also I tried to send a File object.
At this point I just want to send something to the route that will not result in an error. It's not necessary to be a FormData. Anything that will be accepted by the route is fine as long as I can add some custom fields to test it further.
Thanks in advance.

As far as I know, FormData doesn’t exist in node.js, so I assume you’re using the form-data module?
In that case, you need to ask form-data for the buffer and headers separately, like this:
const FormData = require('form-data');
const FD = new FormData();
FD.append('field1', 'value');
FD.append('field2', 'value');
const response = await server.inject({
method,
url,
headers: {
<your other headers>
...FD.getHeaders(),
},
payload: FD.getBuffer(),
});
If you want to test sending files, you also need to provide the filename property to append(…) for the binary data to be decoded correctly by the server:
const fileContent = <any Buffer>;
FD.append('form_file_property', fileContent, {filename: 'a testfile'});

Related

Axios : send image with FormData on Prestashop API

I'm trying to upload on image on the Prestashop API with form-data/axios.
For that, i just need to send a post request with the images joined in an "image" parameter.
I did this simple node.js script (mine is much more complicated but i simplified for here):
const FormData = require("form-data");
const fs = require("fs");
const axios = require("axios");
// Read the image file into a buffer
const imageBuffer = fs.readFileSync("image.jpg");
// Create a FormData object
const form = new FormData();
// Append the image buffer to the form data
form.append("image", imageBuffer);
// Make an HTTP POST request to the PrestaShop API
axios({
method: "POST",
url: "https://XXX/api/images/products/15924",
data: form, // set the request body using the data field
params: {
ws_key: "XXX",
},
headers: {
"Content-Type": "multipart/form-data; boundary=" + form.getBoundary(),
},
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error.response.data);
});
But i get this answer:
<message><![CDATA[Please set an "image" parameter with image data for value]]></message>
I tried a LOT of things. With fs.createReadStream instead of formData, with http instead of axios, with a buffer or a file, etc ... and i alway end with this error.
I would be glad if someone has an idea :-)
Thx !
I finally succeed!
Two changed were needed:
Adding the filename in the append (thanks Dmitriy Mozgovoy for pointing that out in the comment)
Adding the size in the headers with "Content-Length": formData.getLengthSync()
Without these 2 missing elements, it seems that PHP received an empty $_FILES variable.
Final version:
const FormData = require("form-data");
const fs = require("fs");
const axios = require("axios");
// Read the image file into a buffer
const imageBuffer = fs.readFileSync("image.jpg");
// Create a FormData object
const form = new FormData();
// Append the image buffer to the form data
form.append("image", imageBuffer, "image.jpg");
// Make an HTTP POST request to the PrestaShop API
axios
.post("https://example.com/api/images/products/15924", form, {
params: {
ws_key: "EXAMPLE",
},
headers: {
...form.getHeaders(),
"Content-Length": form.getLengthSync(),
},
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error.response.data);
});

How to send a file to backend in react-native using node.js

I tried
const myData = uri; // this looks like file:///data/expo/...
const myDataResponse = await API.SendFile({
myData: myData
});
But file is not being recieved in the backend. Is there any other way to do this?
IN the backend I get
console.log(req.body)
// I get a string same as uri of file
console.log(req.file)
// undefined
Backend is setup perfectly. I am getting successful response when using postman.
Any help is appreciated. I am a begginer.
You should use FormData.
const data = new FormData();
// first argument is the key name received on you API
// second argument is your file path
data.append('file', filePath)
// then your request should look like this :
await fetch(url, {
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
body: data,
})

form-data request body is empty

I'm trying to send an image and some data from an API to another. The image is stored in memory with multer. But when I want to send it, the body is just empty. I tried the same request with postman and it worked perfectly.
postman test
postman test image
server test
server test image
Here is some code. I removed some of it so you can read it better
export const saveImage = async ({ image, name, folder, options }: { image: any, name?: any, folder: string, options?: any }) => {
try {
const fd = new FormData();
fd.append("image", image.buffer, image.originalname);
if(options) {
fd.append("options[resize][height]", options?.resize?.height);
fd.append("options[resize][width]", options?.resize?.width);
}
if(name) fd.append("name", name);
fd.append("folder", folder);
fd.append("servideId", IMAGES_ID);
fd.append("serviceSecret", IMAGES_SECRET);
console.log(fd)
const formHeaders = fd.getHeaders();
const request = await axios.post(`${IMAGES_URL}/api/images`, {
headers: formHeaders,
body: fd
});
return request.data.id;
} catch (error) {
const { response } = error;
console.log(response.request.data)
if(error?.response?.data?.error) {
throw { statusCode: error.response.status, message: error.response.data.error }
}
console.error("Images API", error);
throw new InternalError("Something gone wrong");
}
}
When I log the FormData, I can see in _streams, the data that I'm sending, but the Images API receives an empty body.
FormData screenshot
If you need more information tell me, please! Thank you
The axios API for the post method is: axios.post(url[, data[, config]]). The second argument must always be the data you send along.
In your case axios thinks { headers: formHeaders, body: fd } is the body and the request ends up being application/json. To send a file with data using axios in Node.js, do the following:
const response = await axios.post(`${IMAGES_URL}/api/images`, fd, {
headers: {
...formHeaders,
'X-Custom-Header': 'lala', // optional
},
});
Your question inspired me to turn this answer into an article — Send a File With Axios in Node.js. It covers a few common pitfalls and you'll learn how to send files that are stored as a Buffer or coming from a Stream.
With Axios, you can directly use the form data without having to deal with headers.
axios.post("/api/images", fd)
If you wish to modify headers at some point in the future, you should pass the formData to the `data` field instead of `body`.
axios.post("/api/images", { headers: formHeaders, data: fd })
Correction in comments.
It can also be done using the Axios API syntax.
axios({method: 'post', url: 'url', data: fd, headers: {} })
In the backend, multer will add your files to req.file instead of req.body, if you have properly configured it to do so.

Making a POST request using puppeteer with JSON payload

I'm trying to make a POST request using puppeteer and send a JSON object in the request, however, I'm getting a timeout... if I'm trying to send a normal encoded form data that at least a get a reply from the server of invalid request...
here is the relevant part of the code
await page.setRequestInterception(true);
const request = {"mac": macAddress, "cmd": "block"};
page.on('request', interceptedRequest => {
var data = {
'method': 'POST',
'postData': request
};
interceptedRequest.continue(data);
});
const response = await page.goto(configuration.commandUrl);
let responseBody = await response.text();
I'm using the same code to make a GET request (without payload) and its working
postData needs to be encoded as form data (in the format key1=value1&key2=value2).
You can create the string on your own or use the build-in module querystring:
const querystring = require('querystring');
// ...
var data = {
'method': 'POST',
'postData': querystring.stringify(request)
};
In case you need to submit JSON data:
'postData': JSON.stringify(request)
If you are sending json, you need to add "content-type": "application/json". If you don't send it you can receive an empty response.
var data = {
method : 'POST',
postData: '{"test":"test_data"}',
headers: { ...interceptedRequest.headers(), "content-type": "application/json"}
};
interceptedRequest.continue(data);

NodeJS, Axios - post file from local server to another server

I have an API endpoint that lets the client post their csv to our server then post it to someone else server. I have done our server part which save uploaded file to our server, but I can't get the other part done. I keep getting error { message: 'File not found', code: 400 } which may mean the file never reach the server. I'm using axios as an agent, does anyone know how to get this done? Thanks.
// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
Update
As axios doc states as below and the API I'm trying to call requires a file
// data is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no transformRequest is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
Is there any way to make axios send a file as a whole? Thanks.
The 2 oldest answers did not work for me. This, however, did the trick:
const FormData = require('form-data'); // npm install --save form-data
const form = new FormData();
form.append('file', fs.createReadStream(file.path));
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
...form.getHeaders()
}
};
return axios.post(url, form, request_config);
form.getHeaders() returns an Object with the content-type as well as the boundary.
For example:
{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }
I'm thinking the createReadStream is your issue because its async. try this.
Since createReadStream extends the event emitter, we can "listen" for when it finishes/ends.
var newFile = fs.createReadStream(file.path);
// personally I'd function out the inner body here and just call
// to the function and pass in the newFile
newFile.on('end', function() {
const form_data = new FormData();
form_data.append("file", newFile, "filename.ext");
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
});
This is what you really need:
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios
.post(url, form_data, request_config);
In my case, fs.createReadStream(file.path) did not work.
I had to use buffer instead.
const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);
const config = {
headers: {
Authorization: `Bearer ${auth.access_token}`,
...form.getHeaders(),
},
};
axios.post(api, form.getBuffer(), config);
I have made an interceptor you can connect to axios to handle this case in node: axios-form-data. Any feedback would be welcome.
npm i axios-form-data
example:
import axiosFormData from 'axios-form-data';
import axios from 'axios';
// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);
// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
method: 'POST',
url: 'http://httpbin.org/post',
data: {
nonfile: 'Non-file value',
// if there is at least one streamable value, the interceptor wraps the data into FormData
file: createReadStream('somefile'),
},
});
// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);
I had a same issue, I had a "pdf-creator-service" for generate PDF document from html.
I use mustache template engine for create HTML document - https://www.npmjs.com/package/mustache
Mustache.render function returns html as a string what do I need to do to pass it to the pdf-generator-service ? So lets see my suggestion bellow
//...
async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))
mustache.render(temlateFile, props)
const readableStream = this.getReadableStreamFromString(htmlString)
const formData = new FormData() // from 'form-data'
formData.append('file', options.file, { filename: options.fileName })
const formHeaders = formData.getHeaders()
return await axios.send<Buffer>(
{
method: 'POST',
url: 'https://pdf-generator-service-url/pdf',
data: formData,
headers: {
...formHeaders,
},
responseType: 'arraybuffer', // ! important
},
)
}
getReadableStreamFromString(str: string): Readable {
const bufferHtmlString = Buffer.from(str)
const readableStream = new Readable() // from 'stream'
readableStream._read = () => null // workaround error
readableStream.push(bufferHtmlString)
readableStream.push(null) // mark end of stream
return readableStream
}
For anyone who wants to upload files from their local filesystem (actually from anywhere with the right streams architecture) with axios and doesn't want to use any external packages (like form-data).
Just create a readable stream and plug it right into axios request function like so:
await axios.put(
url,
fs.createReadStream(path_to_file)
)
Axios accepts data argument of type Stream in node context.
Works fine for me at least in Node v.16.13.1 and with axios v.0.27.2

Resources