How to post multipart/form-data with node.js superagent - node.js

I am trying to send the content-type in my superagent post request to multipart/form-data.
var myagent = superagent.agent();
myagent
.post('http://localhost/endpoint')
.set('api_key', apikey)
.set('Content-Type', 'multipart/form-data')
.send(fields)
.end(function(error, response){
if(error) {
console.log("Error: " + error);
}
});
The error I get is:
TypeError: Argument must be a string
If I remove the:
.set('Content-Type', 'multipart/form-data')
I don't get any error but my back end is receiving the request as content-type: application/json
How can I force the content type to be multipart/form-data so that I can access req.files()?

First, you do not mention either of the following:
.set('Content-Type', 'multipart/form-data')
OR
.type('form')
Second, you do not use the .send, you use .field(name, value).
Examples
Let's say you wanted to send a form-data request with the following:
two text fields: name and phone
one file: photo
So your request will be something like this:
superagent
.post( 'https://example.com/api/foo.bar' )
.set('Authorization', '...')
.accept('application/json')
.field('name', 'My name')
.field('phone', 'My phone')
.attach('photo', 'path/to/photo.gif')
.then((result) => {
// process the result here
})
.catch((err) => {
throw err;
});
And, let's say you wanted to send JSON as a value of one of your fields, then you'd do this.
try {
await superagent
.post( 'https://example.com/api/dog.crow' )
.accept('application/json')
.field('data', JSON.stringify({ name: 'value' }))
}
catch ( ex ) {
// .catch() stuff
}
// .then() stuff...

Try
.type('form')
instead of
.set('Content-Type', 'multipart/form-data')
See http://visionmedia.github.io/superagent/#setting-the-content-type

It is not clear what is in the fields variable that you are sending, but here is some information that may help you determine where your problem lies.
To begin with, if you are actually trying to build a multi-part request, this is the official documentation for doing so: http://visionmedia.github.com/superagent/#multipart-requests
as for the error that you got...
The reason is that during the process of preparing the request, SuperAgent checks the data to be sent to see if it is a string. If it is not, it attempts to serialize the data based on the value of the 'Content-Type', as seen below:
exports.serialize = {
'application/x-www-form-urlencoded': qs.stringify,
'application/json': JSON.stringify
};
which is used here:
// body
if ('HEAD' != method && !req._headerSent) {
// serialize stuff
if ('string' != typeof data) {
var serialize = exports.serialize[req.getHeader('Content-Type')];
if (serialize) data = serialize(data);
}
// content-length
if (data && !req.getHeader('Content-Length')) {
this.set('Content-Length', Buffer.byteLength(data));
}
}
this means that to set a form 'Content-Type' manually you would use
.set('Content-Type', 'application/x-www-form-urlencoded')
or
.type('form') as risyasin mentioned
any other 'Content-Type' will not be serialized, and Buffer.byteLength(data) will subsequently throw the TypeError: Argument must be a string exception if the value of your fields variable is not a string.

Here is what worked for me. I had a single field form, that was uploading a file. I turned the form into a HTML5 FormData element and then did it as follows:
var frm = new FormData(document.getElementById('formId'));
var url = 'url/here';
superagent.post(url)
.attach('fieldInFormName', frm.get('fieldInFormName'))
.end( function (error, response) {
//handle response
});
Please note, I tried various ways of setting the 'Content-Type' manually in superagent, and it never worked correctly because of the multipart identifier needed in the Content-Type.

Related

Angular Convert POST request data to array

I have a post request that sends some data to a NodeJS app and then that returns an array. My problem is that I can't turn the data I receive as a response from the POST request to an array.
My Angular is set up like this:
newWord = '';
keyword = '';
onClick() {
const headers = new HttpHeaders()
.set('Authorization', 'my-auth-token')
.set('Content-Type', 'application/json');
this.http.post('http://localhost:3000/search',
{ keyword: this.keyword },
{
headers: headers
})
.subscribe((data) => {
console.log(data);
})
}
}
The data inside the node app looks like this:
I want to get this data in angular as an array so I can iterate over it or do anything else with the fields. Any help would be appreciated!
Provide the type to the http request. Otherwise angular expects any, which is not necessarily an array. That is where the error comes from.
this.http.post<{ id: string, text: string }[]>('http://localhost:3000/search',
{ keyword: this.keyword },
{
headers: headers
})
.subscribe((data) => {
console.log(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.

Can't read body of request but can print it

I am pulling my hairs off. On http request, i can print a body object but can not access its content.
So i am sending a request like this from web:
return fetch('https:xxxxxxx/xxxx', {
method: 'post',
body: JSON.stringify(saleObject),
headers: {
'Accept': 'application/json',
'contentType':"application/json",
'dataType':"json",
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID;
});
}
On my server (nodejs express Firebase ) , I am trying to read it in multiple ways :
exports.payPalIntent = functions.https.onRequest(async(req, res) => {
return cors(req, res, async () => {
console.log("req.body",req.body); //this print AN OBJECT, A REAL OBJECT NOT A STRING
console.log("req address",req.body.address); //=undefined, there is address property inside which
return res.send(200);
});
So, the first one print this :
req.body {"address":{"city":"some city","zip":"345334","area":"USA","street":"Hai 13", ........
The second says Cannot read property 'address' of undefined
Of course, i tried also JSON.stringify(req.body) , which print a string object that i can't access.
How do you access this object ?
JSON.stringify turns a JavaScript object into a JSON string. You already have a JSON string and you want to covert it to a JavaScript object, so use the reverse: JSON.parse(req.body). Then you should be able to access the properties using the dot operator.

Sending Form Data with the native node module

for my current project I have to send form-data from my lambda function to an api endpoint. The api endpoint essentially expects two images (that it compares with one another) and a key. As mentioned before, I somehow seem unable to send the correct form-data to the api endpoint. I checked out postman, and it seems to have worked alright, but something doesn't seem to work in my function. I presume it must be related the form-data string that I'm sending. Below you can find a shortened version of the function (I excluded the two image files), but somehow I'm getting an error back telling me that the api cannot read the key property:
const http = require('http');
const https = require('https');
const httpPromise = (protocol, params, postData) => {
return new Promise((resolve, reject) => {
const requestModule = protocol === 'http' ? http : https;
const req = requestModule.request(params, res => {
// grab request status
const statusCode = res.statusCode;
if(statusCode < 200 || statusCode > 299) {
throw new Error('Request Failed with Status Code:', statusCode);
}
let body = '';
// continuosly update data with incoming data
res.setEncoding('utf8');
res.on('data', data => body += data);
// once all data was received
res.on('end', () => resolve(body));
})
// write data to a post request
if(typeof(params.method) === 'string' && params.method === 'POST' && postData) {
req.write(postData)
}
// bind to the error event
req.on('error', err => reject(err));
// end the request
req.end();
})
}
const controller = async () => {
const apiKey = "00000000";
const options = {
hostname: '***"
port: 80,
path: '***'
method: 'POST',
headers: {"content-type": "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"}
}
const postData = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\00000000\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
let result = await httpPromise('http', options, postData)
console.log(result);
}
yeah, so somehow it just doesn't seem to recognise the key in the postData string. I have tried various different combinations but just can't seem to get this to work.
The default http and https libraries are kind of wordy and annoying.
Would recommend using the request library instead. Read more here
In which case, to make the request, you can simply write it as :
var request = require('request');
var formData = {
// Pass a simple key-value pair
my_field: 'my_value',
}
request.post({url:'http://service.com/upload', formData: formData}, (err, response, body) => {
// Handle response here
});
Alright, so for anyone who might also face the same issue, it took me a little but figured out what the issue was. I didn't set the Content-Length header, which then in turn meant that node automatically added the Transfer-Encoding Header and set its value to chunk. This broke the receiving api and resulted in the issue. Setting the Content-Length header to the correct length and setting the Transfer-Encoding Header to an empty string solved my issue here (but I think one could also simply omit the transfer-encoding header once you defined the Content-Length Header).

nodejs - first argument must be a string or Buffer - when using response.write with http.request

I'm simply trying to create a node server that outputs the HTTP status of a given URL.
When I try to flush the response with res.write, I get the error: throw new TypeError('first argument must be a string or Buffer');
But if I replace them with console.log, everything is fine (but I need to write them to the browser not the console).
The code is
var server = http.createServer(function (req, res) {
res.writeHead(200, {"Content-Type": "text/plain"});
request({
uri: 'http://www.google.com',
method: 'GET',
maxRedirects:3
}, function(error, response, body) {
if (!error) {
res.write(response.statusCode);
} else {
//response.end(error);
res.write(error);
}
});
res.end();
});
server.listen(9999);
I believe I should add a callback somewhere but pretty confused and any help is appreciated.
I get this error message and it mentions options.body
I had this originally
request.post({
url: apiServerBaseUrl + '/v1/verify',
body: {
email: req.user.email
}
});
I changed it to this:
request.post({
url: apiServerBaseUrl + '/v1/verify',
body: JSON.stringify({
email: req.user.email
})
});
and it seems to work now without the error message...seems like bug though. I think this is the more official way to do it:
request.post({
url: apiServerBaseUrl + '/v1/verify',
json: true,
body: {
email: req.user.email
}
});
response.statusCode is a number, e.g. response.statusCode === 200, not '200'. As the error message says, write expects a string or Buffer object, so you must convert it.
res.write(response.statusCode.toString());
You are also correct about your callback comment though. res.end(); should be inside the callback, just below your write calls.
Request takes a callback method, its async! So I am assuming, by the time the callback is executed the res.end() might get called. Try closing the request within the callback.
Well, obviously you are trying to send something which is not a string or buffer. :) It works with console, because console accepts anything. Simple example:
var obj = { test : "test" };
console.log( obj ); // works
res.write( obj ); // fails
One way to convert anything to string is to do that:
res.write( "" + obj );
whenever you are trying to send something. The other way is to call .toString() method:
res.write( obj.toString( ) );
Note that it still might not be what you are looking for. You should always pass strings/buffers to .write without such tricks.
As a side note: I assume that request is a asynchronous operation. If that's the case, then res.end(); will be called before any writing, i.e. any writing will fail anyway ( because the connection will be closed at that point ). Move that line into the handler:
request({
uri: 'http://www.google.com',
method: 'GET',
maxRedirects:3
}, function(error, response, body) {
if (!error) {
res.write(response.statusCode);
} else {
//response.end(error);
res.write(error);
}
res.end( );
});
if u want to write a JSON object to the response then change the header content type to application/json
response.writeHead(200, {"Content-Type": "application/json"});
var d = new Date(parseURL.query.iso);
var postData = {
"hour" : d.getHours(),
"minute" : d.getMinutes(),
"second" : d.getSeconds()
}
response.write(postData)
response.end();
And there is another possibility (not in this case) when working with ajax(XMLhttpRequest), while sending information back to the client end you should use res.send(responsetext) instead of res.end(responsetext)
Although the question is solved, sharing knowledge for clarification of the correct meaning of the error.
The error says that the parameter needed to the concerned breaking function is not in the required format i.e. string or Buffer
The solution is to change the parameter to string
breakingFunction(JSON.stringify(offendingParameter), ... other params...);
or buffer
breakingFunction(BSON.serialize(offendingParameter), ... other params...);
The first argument must be one of type string or Buffer. Received type object
at write_
I was getting like the above error while I passing body data to the request module.
I have passed another parameter that is JSON: true and its working.
var option={
url:"https://myfirstwebsite/v1/appdata",
json:true,
body:{name:'xyz',age:30},
headers://my credential
}
rp(option)
.then((res)=>{
res.send({response:res});})
.catch((error)=>{
res.send({response:error});})

Resources