Calling post api with file in the body - node.js

I want to call a rest API which takes an image as input. But I am not able to pass the file. We can send the URL of the image as an input to the API. Following is the body format.
{ 'url': 'String URL of the Image'}
When I call the same API from the postman it works fine. But not able to understand how can we call it from the nodeJS.
If any one could help me in that.
Thanks & Best Regards,
Sagar

Use axios package to send your POST request here is an exemple :
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("image", imagefile.files[0]);
axios.post('URL_TO_API', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
HTML
<form id="uploadForm" role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file">
<input type=submit value=Upload>
</form>

Related

How to send data from react.js to node?

I am a beginner in web devolopment and I'm developing an application using react and nodejs. I've been looking for ways to send the data from front end i.e, react(data received from the user) to nodejs code so that I can process it and send it back to the UI. I saw some resources mentioning that I can use fetch and axios but I can't quite follow it. So basically my application is about executing the pipe commands of linux. There will be few buttons to choose which command to execute(Like sort, uniq etc). There will be a text area to get the input text and a label to display the output. So how can I send the input data to the nodejs function so that I can process it with some built-in modules and return the output to the label.My code for text area looks like this
import { useState } from "react";
const Text_area = () =>{
const[text,setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const data = {text};
}
return(
<>
<form onSubmit={handleSubmit}>
<label>Input here</label>
<textarea value= {text} required onChange={(e)=>setText(e.target.value)}/>
<button>OK</button>
</form>
</>
);
}
Share your thoughts please!
I assume you have only index route, that's why fetch is pointing to index.
const[text,setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const newText= { text };
fetch("/", {
method: "POST",
headers: {"Content-Type": "application/JSON"},
body: JSON.stringify(newText)
})
}
Above, fetch method is used to send your data to relevant route in your node.js file.
And in your server.js file, you can code something like this to see if it works:
app.post("/", function(req, res){
res.send(req.body);
});
Please let me know if it works for you.
Think of your problem like a Form HTML element. How can a form send data to server?
Basically, they use HTTP GET/POST method. If you don't know it, Google! But for now, let just understand that: To send data from react.js to node, you need do something with HTTP GET/POST method.
<form action="/send_form" method="post">
<input type="text" id="fname" name="fname">
<input type="text" id="lname" name="lname">
<input type="submit" value="Submit">
</form>
form element has done it for you. This is why you see people mentioned axios because what if I don't use form? How can I do the same thing without form?
Then how do server receive information? You will do that by code something in Node.js. Googled yourself :>.
Then people mentioned Express.js. Think of it as "React" of Node.js, which mean it's a framework. You don't need to know "Express.js" to receive information sent from React.

POST s3 presigned url call in postman returns SignatureDoesNotMatch

When calling a presigned POST endpoint returned from my node.js server I receive what looks like a valid response:
Note I am setting environment variables to make the POST call easier to repeat.
When calling the presigned URL however I receive a 403 response with SignatureDoesNotMatch error code. I am having a lot of difficulty finding resources about using the POST presigned url. So my questions are as follows:
Am I calling the presigned URL correctly (see below)?
How can I debug to understand what is happening here (I've tried turning on the bucket logs, but nothing is logged)?
What might I look at further to resolve this issue?
When performing the post it is my understanding that the call is to POST to the url returned and using form-data include all "fields" in the body with the final key being labelled as file with the file to be uploaded attached. From the above response I have therefore used the following call in postman:
Headers:
Body (I have tried both with and without Content-Type included):
However when I make this call I receive a 403 forbidden response and a SignatureDoesNotMatch error code:
Here is the code to generate the presigned URL (using "#aws-sdk/client-s3": "^3.25.0",
"#aws-sdk/s3-presigned-post": "^3.25.0" within package.json):
const { createPresignedPost } = require("#aws-sdk/s3-presigned-post");
const { S3Client } = require("#aws-sdk/client-s3");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
signatureVersion: "v4",
region: "eu-west-2",
});
async function getSignedUrl() {
const params = {
Bucket: "richbits-test",
Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
Conditions: [["eq", "$Content-Type", "image/jpeg"]],
};
console.log(params);
const signedUrl = await createPresignedPost(s3, params);
return signedUrl;
}
I've double checked the bucket and keys, but would appreciate some advice as to how I might move forward or any useful resources which can help me understand the use of these presigned urls better please.
Woah, the documentation from AWS on these headers is terrible. Here is what I figured out.
The output of createPresignedPost() is an object with two keys: url and fields. The fields object contains all the form fields and respective values that you must use when submitting the post. You would copy these fields and only these fields to your Postman request.
There are a few fields that the AWS SDK will add automatically, including bucket, key, policy, and several fields required to generate the signature. With the JavaScript SDK, these were X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, and X-Amz-Signature (though for the Python SDK, these were AWSAccessKeyId and signature). These will be returned in the fields key from createPresignedPost(), so you do not need to manually generate them.
The Fields parameter of createPresignedPost() is for additional fields that you want to add to the form. The entire set of fields that could possibly be added is listed here. However, the SDK handles many of these for you, particularly the required ones. According to the Python SDK docs, the fields that you could include in Fields are acl, Cache-Control, Content-Type, Content-Disposition, Content-Encoding, Expires, success_action_redirect, redirect, success_action_status, and x-amz-meta-.
The Fields and Conditions parameters of createPresignedPost() need to be synchronized such that any field in Fields also has a condition in Conditions and vice versa.
Putting this altogether, I believe the problem with your getSignedUrl() function was that it was missing a Fields parameter for createPresignedPost() that had a key-value pair for Content-Type (since you have a condition for Content-Type). Without the Content-Type in the Fields parameter, the calculated signature would not include the Content-Type field. If you then left out the Content-Type field your signature might match, but your policy conditions would fail because it required Content-Type to be present and equal to image/jpeg. If you included Content-Type, then your signature would not match.
Here is could that should work for you.
const { createPresignedPost } = require("#aws-sdk/s3-presigned-post");
const { S3Client } = require("#aws-sdk/client-s3");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
signatureVersion: "v4",
region: "eu-west-2",
});
async function getSignedUrl() {
const params = {
Bucket: "richbits-test",
Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
Conditions: [["eq", "$Content-Type", "image/jpeg"]],
Fields: {"Content-Type": "image/jpeg"},
};
console.log(params);
const signedUrl = await createPresignedPost(s3, params);
return signedUrl;
}
getSignedUrl().then(res => {
console.log(res)
})
The output of this should be
{
Bucket: 'richbits-test',
Key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Conditions: [ [ 'eq', '$Content-Type', 'image/jpeg' ] ],
Fields: { 'Content-Type': 'image/jpeg' }
}
{
url: 'https://s3.eu-west-2.amazonaws.com/richbits-test',
fields: {
'Content-Type': 'image/jpeg',
bucket: 'richbits-test',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
'X-Amz-Credential': '<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request',
'X-Amz-Date': '20211005T111446Z',
key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Policy: '<SOME_BASE64_ENCODED_STRING>',
'X-Amz-Signature': '<THE_SIGNATURE>'
}
}
Then your POST request would include the form fields Content-Type, bucket, X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, Policy, and X-Amz-Signature. Here is what an HTML form would look like.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<form action="https://s3.eu-west-2.amazonaws.com/richbits-test" method="post" enctype="multipart/form-data">
Bucket:
<input type="input" name="bucket" value="richbits-test" /><br />
Key to upload:
<input type="input" name="key" value="d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f" /><br />
Content-Type:
<input type="input" name="Content-Type" value="image/jpeg" /><br />
<input type="text" name="X-Amz-Credential" value="<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request" />
<input type="text" name="X-Amz-Date" value="20211005T111446Z" />
<input type="hidden" name="Policy" value="<SOME_BASE64_ENCODED_STRING>" />'
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Signature" value="<THE_SIGNATURE>" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
As far as debugging this issue, I did not find anything useful from the POST request to S3. I either got no response or an unauthorized status, without any indication what the problem was.

Why is my image file being converted to a string?

Edit, original question has been resolved as to why file was converted to string. The code has been edited to reflect these corrections. The API handler is now outputting object as data type and buffer as the value of request.payload.file.
I'm using Aurelia to make a Single Page App. There is a HTML form that accepts two input fields, one for a file, one for text. These fields are bound to variables (selecetdImage and title) in an associated TypeScript view model. In the viewmodel they are used as arguments in a function that appends them to formData and sends a http post request with the formData to an Node/js Hapi framework API handler.
When I console.log(typeof(selectedImage) in the Aurelia app, it states object, but when I console log typeOf(selecetdImage) in the handler, I get String. I'm guessing this is why my function isn't working and giving 500 error messages
The handler itself works. I used it in a MVC server based web app. In that context, HTML form triggers a post request, and the MVC handler successfully receives the file, writes it to local/temp.img and uploads it to cloudinary.
But with the API version, where I assembled the form data myself as above, the file isn't written to local/temp.img and the cloudinary upload fails.
Edit.
I changed the viewmodel variables to
title = null;
files = null;
and I changed the formData append function to:
formData.append('file', File, files[0]);
As per the example here. The code below is now modified to match this update.
Now when I console log the value of file in the API handler, the output is:
<Buffer ff d8 ff e0 00 10.......
I'm not sure what to do with this data. I assume it's the image binary data in octal? And if so, does anyone know how to write it as an image in node?
The payload is no longer of type string, now it's type object.
<form submit.delegate="uploadPhoto()" enctype="multipart/form-data">
<div class="two fields">
<div class="field">
<label>File</label>
<input type="file" name="file" accept="image/png, image/jpeg" files.bind="files">
</div>
<div class="field">
<label>Title</label> <input value.bind="title">
</div>
</div>
<button type="submit"> Upload </button>
</form>
//photoUpload.ts// (view model associated with above html
#inject(POIService)
export class PhotoForm {
#bindable
photos: Photo[];
title = null;
files = null;
constructor (private poi: POIService) {}
uploadPhoto() {
const result = this.poi.uploadPhoto(this.title, this.files);
console.log(this.title);
console.log(result);
}
//POIService (where contains injected function to create HTTP request
async uploadPhoto(title: string, files){
console.log("now uploading photo for: " + this.currentLocation)
let formData = new FormData();
formData.append("title", title);
formData.append("location", this.currentLocation); //don't worry about this variable, it's set by another function every time a page is viewed
formData.append("file", files[0]);
const response = await this.httpClient.post('/api/locations/' + this.currentLocation + '/photos', formData);
console.log(response);
}
//API Handler (accepts HTTP request, writes the image to a local folder, the uploads to cloudinary and returns the url and photo_id which are stored in a Mongod document
create: {
auth: false,
handler: async function(request, h) {
const photo = request.payload.file;
await writeFile('./public/temp.img', photo);
const result = await cloudinary.v2.uploader.upload('./public/temp.img', function(error, result) {
console.log(result)
});
const newPhoto = new Photo({
title: request.payload.title,
url: result.url,
public_id: result.public_id,
location: request.params.name
})
await newPhoto.save();
return newPhoto;
}
},
Is it a very long string, containing "data:b64" in the first 15 or so characters? If so, that means it's the base64 data.

NodeJS form submit issues

I am attempting to submit data in my NodeJS app. I've been using Postman with a single name entry in json and my app is able to detect the post body data. Screen shot below:
My problem is I can't get my html markup to submit data successfully. I provide the data to the form but the nodejs function receiving the submission shows that the request data is empty.
The following is the form markup:
<form id="join_queue" action="/join_queue" method="post" enctype="application/json">
<label for="">Please provide a name: </label>
<input type="text" name="name" id="name">
<input type="submit" value="Join">
</form>
And here is my Nodejs function responding to the form submission:
app.post('/join_queue', (req, res) => {
console.debug('Post body: ', req.body)
console.debug('Post param: ', req.params)
res.render('join.ejs')
})
All I get is Post body: {} and the same for params.
What am I doing wrong or missing here? How can I get my html form to behave the same as the postman form?
I needed to include the body-parser middleware in order to get form submission data as indicated here.
I.E:
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }));

Post multipart/form-data to endpoint

I'm trying to create an upload form for excel files with angular 6. I have implemented a file chooser with which i want to upload (post) excel files to a certain endpoint that expects "MULTIPART_FORM_DATA". Now i have read that you should not set the content type in the header for angular versions above 5 but if i do not include the content-type in the header the angular application automatically sets it to
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", which the server does not expect and hence results in a "bad request". So how can i implement a valid "post" for multipart/form-data with angular 6?
the endpoint looks something like this:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadExcel(
#FormDataParam("file") InputStream inputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader){...}
the angular component looks something like this:
handleFileInput(event: any): void {
if (!event.target.files.length) {
return;
}
this.fileToUpload = event.target.files.item(0);}
private uploadFile(): void {
this.fileService.uploadFile(this.fileToUpload).subscribe(
(res: any) => {
console.log('upload succeeded');
}
);}
the html form looks something like this:
<form (submit)="uploadFile()" enctype="multipart/form-data">
<label for="file">choose excel file to upload: </label>
<input type="file" name="file" id="file" accept=".xls,.xlsx" (change)="handleFileInput($event)">
<input type="submit" name="submit" class="submitButton">
and the service looks like this:
uploadFile(file: File): Observable<any> {
const fd: FormData = new FormData();
fd.append('file', file, file.name);
return this.http.post(this.fileURL, file);
}
I found the mistake I've made: I passed the wrong argument to the http.post call.
The service should of course look like this:
uploadFile(file: File): Observable<any> {
const fd: FormData = new FormData();
fd.append('file', file, file.name);
return this.http.post(this.fileURL, fd);
}

Resources