Sending DataForm containing a pdf file from a node.js file - node.js

I am trying to send a FormData containing a pdf file from a node.js script to a server. The request fail with a status code 400 in response. In the server side, it seems that he do not consider the data as a pdf file, or maybe that that the whole data is undefined.
This is what my node.js script does : (I've tried other ways to send the request but without success)
import axios from "axios"
import fs from "fs-extra"
import FormData from 'form-data'
const formData = new FormData()
const diplomaBuffer = await fs.readFile(`../data/fake/foo`+i+`.pdf`)
formData.append("diploma", diplomaBuffer)
formData.append("diplomaId", 1)
axios({
method: 'post',
processData: false,
contentType: 'multipart/form-data',
cache: false,
url: 'http://localhost:8080/certificates',
data: formData,
config: { headers: formData.getHeaders() }
})
// const response = await axios
// .post(`http://localhost:8080/certificates`, {
// body: formData
// })
// const response = await axios
// .post(`http://localhost:8080/certificates`
// , formData
// , {headers: formData.getHeaders()})
This is the function called in the server side :
app.post('/certificates', async (req, res) => {
const files = req.files
if(!files || !files.diploma || !files.diplomaId) {
res.status(400).send(new Error("No file provided"))
}
if(files.diploma.mimetype !== "application/pdf") {
res.status(400).send(new Error("diploma is not a pdf"))
}
// operations made with the received pdf (not relevant for the question)
const certificateId = uniqid()
const outputDiploma = `data/diplomas/${certificateId}.pdf`
await Promise.all([
fs.writeFile(outputDiploma, files.diploma.data),
])
await pdf.sign(outputDiploma, `${sha("sha256").update(certificateId + diplomaSecret).digest("hex")} : ${date()}`)
const diplomaBuffer = await fs.readFile(outputDiploma)
const certificate_hash = verify_pdf.hashBuffer(diplomaBuffer)
const certificateRegistry = await app.bizNetworkConnection.getAssetRegistry("consortium.supchain.assets.Certificate")
const certificate = app.factory.newResource("consortium.supchain.assets", "Certificate", certificateId)
certificate.diploma = app.factory.newRelationship("consortium.supchain.assets", "Diploma", req.body.diplomaId)
certificate.hashInfo = certificate_hash
await app.registry.certificate.add(certificate)
res.status(200).send("ok")
})

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);
});

Not able to pass JSON data using FETCH

I am having an API Service build on node js running on port 3001 and a web UI using REACT & running on port 3000. I have to POST details from Web page to the API in JSON Format. I am able to hit the API, however I could not get the JSON on the API. Its being received as {}. However the API is working fine from postman.
Could some one please share light on where i am missing.
Source code of API
const util = require('util')
const request = require("request");
const express = require('express')
const app = express();
const port = 3001
app.use(express.json());
app.use(express.urlencoded( {extended: false}));
const bodyParser = require('body-parser');
const { json } = require('express/lib/response');
app.post('/PostPatronDetailsone',(req,res) => {
async function run() {
try {
console.log('Request received from REACT ONE');
// parse application/json
app.use(bodyParser.json())
console.log(req.body);
// Respond back
res.send('Transaction Sent');
} finally {
// Ensures that the client will close when you finish/error
}
}
run().catch(console.dir);
});
app.listen(port, () => console.log('Server is listening at port ' + port));
Code Snippet of Web making the HTTP POST
import React from 'react';
class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
postId: null
};
}
async componentDidMount() {
// Simple POST request with a JSON body using fetch
let payload = {'first_name': 'TEST' };
var data = new FormData();
data.append( "json", JSON.stringify( payload ) );
const requestOptions = {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'application/json','Accept':'application/json' },
body: data,
redirect: 'follow'
};
var response = await fetch('http://localhost:3001/PostPatronDetailsone', requestOptions);
var data1 = await response.json();
var data2 = data1.text();
alert(data2);
//this.setState({ postId: data1.insertedid });
}
render() {
const { postId } = this.state;
return (
<div className="card text-center m-3">
<h5 className="card-header">Simple POST Request</h5>
</div>
);
}
}
export default Signup;
Console Output of API Service
Server is listening at port 3001
Request received from REACT ONE
{}
Request received from REACT ONE
{}
It is because you send formData, instead stringify the object and send it to body like this:
async componentDidMount() {
// Simple POST request with a JSON body using fetch
let payload = {first_name: 'TEST' };
const requestOptions = {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'application/json','Accept':'application/json' },
body: JSON.stringify(payload ),
redirect: 'follow'
};
var response = await fetch('http://localhost:3001/PostPatronDetailsone', requestOptions);
var data1 = await response.json();
var data2 = data1.text();
alert(data2);
//this.setState({ postId: data1.insertedid });
}
Please change the code as follows:
let payload = {'first_name': 'TEST' };
fetch('http://localhost:3001/PostPatronDetailsone',{
body:JSON.stringify(payload),
method:'post',
mode: 'no-cors',
headers: {
"Content-Type": "application/json"
},
}).then((res)=>res.json()).then((data)=>console.log(data));
And from controller in backend remove that line as you are already using express.json() which is equivalent to that line:
app.use(bodyParser.json()) // remove it
You have 3 key problems here:
You are posting a FormData object (which gets converted to a multipart/form-data encoding) and not JSON
You are setting no-cors mode which causes the browser to silently discard any instructions to do anything that would require CORS permission (such as setting the application/json content-type or reading the response)
There won't be a text() method on anything parsed from JSON
const data = {'first_name': 'TEST' };
const payload = JSON.stringify(data)
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json','Accept':'application/json' },
body: payload,
redirect: 'follow'
};
const response = await fetch('http://localhost:3001/PostPatronDetailsone', requestOptions);
const parsed_json = await response.json();
console.log(parsed_json);
You will also need to change the server-side code to grant permission to the JavaScript to read the response and send the JSON formatted request. Use the cors module for that.
app.use(cors());
Also note that body parsing middleware needs to be set up when the application starts and not in the middle of a route. The body-parser module has been obsoleted as Express has its features built-in now.
The following is good:
app.use(express.json());
The following is bad:
const bodyParser = require('body-parser');
and
app.use(bodyParser.json())
Your route code doesn't need to be any more complex than:
app.post('/PostPatronDetailsone', async (req,res) => {
console.log('Request received from REACT ONE');
console.log(req.body);
res.json('Transaction Sent');
});
Note that since you are trying to parse the response as JSON in the client side code, I've had to change send to json so that the server outputs JSON instead of plain text.

Create a Blob from a returned nodeJS buffer in JSON format

In nodeJS I have a Buffer variable.
From the client I fetch to the server to get this variable. This Buffer has been converted to json format with Buffer.toJSON().
How can I create a Blob in the client with this response?
In nodeJS (with Express):
app.route('/api').post( async (req, res) => {
const buffer = Buffer.from('This is a test buffer')
const jsonBuffer = Buffer.toJSON()
res.json(response)
})
In the client (with fetch):
const rawResponse = await fetch('/api', {
method: 'POST',
headers: {"Content-type": "application/json; charset=UTF-8"},
body: JSON.stringify('nothing')
})
const response = await rawResponse.json()
const blob = new Blob(response.data, {type: 'text/html'}) // This doesn't work
console.log(blob)

IPFS Pinata service not accepting file

I have a code as shown below that uploads files from the browser and saves in the server, once it has been saved to the server, I want the server to connect to the Pinata API so the file can also be saved to the IPFS node.
let data = new FormData();
const fileBuffer = Buffer.from(`./public/files/${fileName}`, 'utf-8');
data.append('file', fileBuffer, `${fileName}`);
axios.post('https://api.pinata.cloud/pinning/pinJSONToIPFS',
data,
{
headers: {
'Content-Type': `multipart/form-data; boundary= ${data._boundary}`,
'pinata_api_key': pinataApiKey,
'pinata_secret_api_key': pinataSecretApiKey
}
}
).then(function (response) {
console.log("FILE UPLOADED TO IPFS NODE", fileName);
console.log(response);
}).catch(function (error) {
console.log("FILE WASNT UPLOADED TO IPFS NODE", fileName);
console.log(error);
});
The issue i'm having is that after creating a buffer of my file and wrapping it in a formdata, the pinata API returns an error :
data: {
error: 'This API endpoint requires valid JSON, and a JSON content-type'
}
If i convert the data to string like JSON.stringify(data) and change the content-type to application/json, the file buffer will be uploaded successfully as string.
I hope explained it well to get a solution. Thanks.
It looks like you're attempting to upload a file to the pinJSONToIPFS endpoint, which is intended to purely be used for JSON that is passed in via a request body.
In your situation I would recommend using Pinata's pinFileToIPFS endpoint
Here's some example code based on their documentation that may be of help:
//imports needed for this function
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
export const pinFileToIPFS = (pinataApiKey, pinataSecretApiKey) => {
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
//we gather a local file for this example, but any valid readStream source will work here.
let data = new FormData();
data.append('file', fs.createReadStream('./yourfile.png'));
return axios.post(url,
data,
{
maxContentLength: 'Infinity', //this is needed to prevent axios from erroring out with large files
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
'pinata_api_key': pinataApiKey,
'pinata_secret_api_key': pinataSecretApiKey
}
}
).then(function (response) {
//handle response here
}).catch(function (error) {
//handle error here
});
};
The proper code to pin any file to IPFS is as below.
Apparently, even Pinata support staff didn't know this.
You need to set an object with the property name filepath as your last parameter. The name doesn't matter, it can be a duplicate, it can be the same as others, or it can be unique.
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
const fileContents = Buffer.from(bytes);
const data = new FormData();
data.append("file", fileContents, {filepath: "anyname"});
const result = await axios
.post(url, data, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
"pinata_api_key": userApiKey,
"pinata_secret_api_key": userApiSecret,
"path": "somename"
}
});
Code to upload a file on IPFS using Pinata.
There are two methods available to upload files/images on Pinata. One is with Pinata SDK and the second is the pinFileToIPFS endpoint.
If you are uploading files from Next.js then you cannot convert your image into binary using fs.createReadStream or Buffer.from. These packages support the Node side. So if you want to upload the file with Next.js on Pinata then you can use this code.
// convert file into binary
const data = new FormData();
data.append("title", file.name);
data.append("file", file);
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
// pass binary data into post request
const result = await axios.post(url, data, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: "your_pinata_key",
pinata_secret_api_key:
"your_pinata_secret",
path: "somename",
},
});
console.log("RESULT", result);
this will upload a file to ipfs under the path ipfs://{cid}/images/{fileId}
const PINATA_BASE_URL = "https://api.pinata.cloud";
const PINATA_PIN_URI = "/pinning/pinFileToIPFS";
const fileExt = file.type.split("/")[1];
let nftId = 1
// creates a 64byte string '0000...0001' to follow ERC-1155 standard
const paddedId = createPaddedHex(nftId);
const ipfsFileId = `${paddedId}.${fileExt}`;
const ipfsImageFilePath = `/images/${ipfsFileId}`;
const fileUploadData = new FormData();
// this uploads the file and renames the uploaded file to the path created above
fileUploadData.append("file", file, ipfsImageFilePath);
fileUploadData.append(
"pinataOptions",
'{"cidVersion": 1, "wrapWithDirectory": true}'
);
fileUploadData.append(
"pinataMetadata",
`{"name": "${ipfsImageFilePath}", "keyvalues": {"company": "Pinata"}}`
);
const pinataUploadRes = await axios.post(
PINATA_BASE_URL + PINATA_PIN_URI,
fileUploadData,
{
headers: {
Authorization: `Bearer ${PINATA_JWT}`,
},
}
);
const ipfsCID = pinataUploadRes.data.IpfsHash;

How to post form data using Axios in node

EDIT Changing the title so that it might be helpful to others
I am trying to upload an image to imgbb using their api using Axios, but keep getting an error response Empty upload source.
The API docs for imgbb shows the following example:
curl --location --request POST "https://api.imgbb.com/1/upload?key=YOUR_CLIENT_API_KEY" --form "image=R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
My node code to replicate this is:
const fs = require('fs')
const FormData = require('form-data')
const Axios = require('axios').default
let file = '/tmp/the-test.png'
let url = 'https://api.imgbb.com/1/upload?key=myapikey'
var bodyData = new FormData();
let b = fs.readFileSync(file, {encoding: 'base64'})
bodyData.append('image', b)
Axios({
method: 'post',
url: 'url',
headers: {'Content-Type': 'multipart/form-data' },
data: {
image: bodyData
}
}).then((resolve) => {
console.log(resolve.data);
}).catch(error => console.log(error.response.data));
But I keep getting the following error response..
{
status_code: 400,
error: { message: 'Empty upload source.', code: 130, context: 'Exception' },
status_txt: 'Bad Request'
}
Not sure exactly where I am failing.
For the sake of completion and for others, how does the --location flag translate to an axios request?
The issue was in the headers. When using form-data, you have to make sure to pass the headers generated by it to Axios. Answer was found here
headers: bodyData.getHeaders()
Working code is:
const fs = require('fs');
const FormData = require('form-data');
const Axios = require('axios').default;
let file = '/tmp/the-test.png';
var bodyData = new FormData();
let b = fs.readFileSync(file, { encoding: 'base64' });
bodyData.append('image', b);
Axios({
method : 'post',
url : 'https://api.imgbb.com/1/upload?key=myapikey',
headers : bodyData.getHeaders(),
data : bodyData
})
.then((resolve) => {
console.log(resolve.data);
})
.catch((error) => console.log(error.response.data));

Resources