I'm trying to POST data to my server using the fetch API and every time I do I end up with an empty body.
As far as I can tell from the fetch documentation, to post data you need to pass an object with a method key set to "POST" and a body key with a string or FormData object (or some others).
Here's a simple example that is just not working as I would expect:
var formData = new FormData();
formData.append("test", "woohoo");
formData.append("foo", "bar")
// prints out contents of formData to show that it has contents!
formData.forEach(function(key, value) {
console.log(`${key}: ${value}`);
});
fetch("/fetch", {
method: "POST",
body: formData
}).then(function(response) {
return response.json();
}).then(function(json) {
// Logs empty object
console.log(json);
}).catch(function(err) {
console.log(err);
});
I also have an express server on the back of this that logs the request body and echoes it in the response.
app.post("/fetch", function(req, res) {
// Logs empty object :(
console.log(req.body);
res.send(JSON.stringify(req.body));
});
I add the formData as the body of the fetch request, but nothing ever seems to go through.
Please find my test files in this gist here and help!
This is because your node server isn't understanding mulitpart forms correctly (which is what FormData) will send by default.
You can use node-muliparty on your server you can read the form easily without touching the frontend code.
Something like:
var multiparty = require('multiparty');
var app = express();
app.post("/fetch", function(req, res) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
console.log(fields);
res.send(JSON.stringify(fields));
});
});
in your index.js will show you it working correctly. There might be a way to make this work with body-parser but I don't know how myself.
Related
I am trying to get this working now for days -.-
Using a simple NodeJS express server, I want to upload an image to a Django instance through Post request, but I just can't figure out, how to prepare the request and embed the file.
Later I would like to post the image, created from a canvas on the client side,
but for testing I was trying to just upload an existing image from the nodeJS server.
app.post('/images', function(req, res) {
const filename = "Download.png"; // existing local file on server
// using formData to create a multipart/form-data content-type
let formData = new FormData();
let buffer = fs.readFileSync(filename);
formData.append("data", buffer); // appending the file a buffer, alternatively could read as utf-8 string and append as text
formData.append('name', 'var name here'); // someone told me, I need to specify a name
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
axios.post("http://django:8000/images/", formData, config)
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
});
What am I doing wrong or did I just miss something?
EDIT
I just found a nice snippet with a slighlty other approach on the npm form-data page, on the very bottom (npmjs.com/package/form-data):
const filename = "Download.png"; // existing local file on server
let formData = new FormData();
let stream = fs.createReadStream(filename);
formData.append('data', stream)
let formHeaders = formData.getHeaders()
axios.post('http://django:8000/images/', formData, {
headers: {
...formHeaders,
},
})
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
sadly, this doesn't change anything :( I still receive only Bad Request: No file was submitted
I don't really have much Django code just a basic setup using the rest_framework with an image model:
class Image(models.Model):
data = models.ImageField(upload_to='images/')
def __str__(self):
return "Image Resource"
which are also registered in the admin.py,
a serializer:
from .models import Image
class ImageSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Image
fields = ('id', 'data')
using automatic URL routing.
I wrote a simple test script and put the same image on the django server, to verify that image uploads works, and it does:
import requests
url = "http://127.0.0.1:8000/images/"
file = {'data': open('Download.png', 'rb')}
response = requests.post(url, files=file)
print(response.status_code) # 201
I had a similar problem: I used the same Django endpoint to upload a file using axios 1) from the client side and 2) from the server side. From the client side it worked without any problem, but from the server side, the request body was always empty.
My solution was to use the following code:
const fileBuffer = await readFile(file.filepath)
const formData = new FormData()
formData.append('file', fileBuffer, file.originalFilename)
const response = await fetch(
urlJoin(BACKEND_URL),
{
method: 'POST',
body: formData,
headers: {
...formData.getHeaders(),
},
}
)
A few relevant references that I found useful:
This blog post, even though it seems the author manages to send form data from the server side using axios, I did not manage to reproduce it on my case.
This issue report in the axio repository, where one comment suggests to use fetch.
In your node.js express server instead of adding the image to the form data, try directly sending the stream in the API post.
const filename = "Download.png"; // existing local file on server
//let formData = new FormData();
let stream = fs.createReadStream(filename);
//formData.append('data', stream)
let formHeaders = formData.getHeaders()
axios.post('http://django:8000/images/', stream, {
headers: {
...formHeaders,
},
})
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
I still didn't manage to get this working with axios so I tried another package for sending files as post requests, namely unirest, which worked out of the box for me.
Also it is smaller, requires less code and does everything I needed:
const filename = "Download.png"; // existing local file on server
unirest
.post(url)
.attach('data', filename) // reads directly from local file
//.attach('data', fs.createReadStream(filename)) // creates a read stream
//.attach('data', fs.readFileSync(filename)) // 400 - The submitted data was not a file. Check the encoding type on the form. -> maybe check encoding?
.then(function (response) {
console.log(response.body) // 201
})
.catch((error) => console.log(error.response.data));
If I have some spare time in the future I may look into what was wrong with my axios implementation or someone does know a solution pls let me know :D
I am using the express server as an API gateway to my microservices, in which I am processing images and store them in a database.
the problem summarizes as follows:
in the express server, I need to forward the image received in a post request from the client and forward it to the web service that can handle image processing.
It turns out that I need to parse the image before I can forward it to the next API, the error I am getting is
Unexpected token - in JSON at position 0
Her is my client that sends the image to the express server(my gateway)
function uploadWithFormData() {
const formData = new FormData();
formData.append("file", file);
fetch('/upload', {
method: "POST",
headers: {
"content-type": "application/json",
},
body: data
}).then(handleResponse)
.catch(handleError);
uploadImage(formData);
}
and this is my express serve that should handle the request and forward it to another web service
app.post("/upload", function (req, res) {
const form = formidable({ multiples: true });
form.parse(req, (err, fields, files) => {
const response = res.json({ fields, files });
let formData = new FormData();
formData.append('file', fs.createReadStream(req.files.file.path), req.files.file.name);
uploadImageAPI(response).then(
res.status(201).send()
).cache(res.status(412).send())
});
I have tried to make some consol.log inside the function to see the req but the request fails before it enters the function, because express could not pars the image.
I saw some people doing this using the multer library but I could not mange that in my case
any suggestions?
You're posting a FormData object, which will be converted to multipart/form-data.
You aren't posting JSON.
You have set "content-type": "application/json" so you claim you are posting JSON.
The server tries to decode the "JSON", fails, and throws the error.
Don't override the Content-Type header. fetch will generate a suitable one automatically when you pass it a FormData object.
Do use a body parsing module which supports multipart requests. body-parser does not, but its documentation links to several that do.
Part of the solution is to remove the JSON header as #Quentin said in his answer. However, the code that solved the problem is as following :
const multer = require('multer');
const upload = multer();
app.post("/upload", upload.any(), (req, res) => {
const { headers, files } = req;
const { buffer, originalname: filename } = files[0];
headers['Content-Type'] = 'multipart/form-data';
const formFile = new FormData();
formFile.append('file', buffer, { filename });
fetch(URL, {
method: "POST",
body: data
}).then(handleResponse)
.catch(handleError);
});
I create server (node.js / express / boby-parser)
and I need to get array of objects 'users'.
its part of code from server.js file:
let users = [{
name: 'user1',
}];
app.get('/users/', (req, res) => {
const filePath = path.join(pth.dir, 'build', 'index.html');
res.json(users);
res.sendFile(filePath);
});
Its my code from frontend:
const handleResponse = (response) => {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
};
const getAll = (baseUrl) => {
const requestOptions = {
method: 'GET'
};
return fetch(baseUrl, requestOptions).then(handleResponse);
};
Something wrong with my code at server. (I just dodnt know how to use express server).
when I use getAll function I got JSON text replace my page. Can anyone help? How should I write app.get() in server.js. Or do I need write in server part one app.get() to get page or another app.get() to get JSON data?
Why are you trying to send a file in the response?:
res.sendFile(filePath);
For starters, the response content can either be JSON or a file (or any of a variety of other things of course), but not both. With data like JSON or XML it's possible to combine multiple objects into one larger object for a single response, but this won't work if the content types are entirely different as it is with a file.
Looking at your client-side code, you're not even doing anything with that file. You only read the JSON data:
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
So the simplest approach here would just be to not try to send back the file:
app.get('/users/', (req, res) => {
res.json(users);
});
Edit: Based on comments below, you seem to be struggling with the different requests the client makes to the server. The browser loading the page is one request with one response. If that page includes JavaScript that needs to fetch data, that would be a separate AJAX request with its own response containing just that data.
It’s possible to use JSON (or any data) server-side to populate a page template and return a whole page with the data. For that you’d need to use (or build) some kind of templating engine in the server-side code to populate the page before returning it.
The res.json() represents the HTTP response that an Express app sends when it gets an HTTP request. On the other hand, res.sendFile() transfers the file at the given path.
In both cases, the flow is essentially transferred to client who might have made the request.
So no, you cannot use res.sendFile and res.json together.
var options = {
headers: {
'name': 'user1',
}
};
res.sendFile(path.join(__dirname, 'build', 'index.html'), options);
Thats really the closest you can do to achieve the desired task.
How to properly post data to server using Sapper JS lib ?
Saying : I have a page 'board-editor' where I can select/unselect tiles from an hexagonal grid written in SVG, and adds/substract hex coordinates in an store array.
Then user fills a form, with board: name, author, and version... Clicking on save button would POST the form data plus the array in store. The server's job, is to store the board definition in a 'static/boards/repository/[name].json' file.
Today, there's few details on the net to use correctly Sapper/Svelte with POSTing data concerns.
How to proceed ? Thanks for replies !
EDIT:
to avoid reposting of the whole page, which implies to loss of the app state, I consider using a IFRAME with a form inside.... but how to init a copy of sapper inside the IFRAME to ensure I can use the this.fetch() method in it ?
I use Sapper + Svelte for a website, it's really amazing! In my contact form component, data are sent to the server. This is how it's done without iframe. The data sent and received is in JSON format.
On the client side (component):
var data = { ...my contact JSON data... }
var url = '/process/contact' // associated script = /src/routes/process/contact.js
fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
})
.then(r => {
r.json()
.then(function(result) {
// The data is posted: do something with the result...
})
})
.catch(err => {
// POST error: do something...
console.log('POST error', err.message)
})
On the server side:
script = /src/routes/process/contact.js
export async function post(req, res, next) {
/* Initializes */
res.setHeader('Content-Type', 'application/json')
/* Retrieves the data */
var data = req.body
// Do something with the data...
/* Returns the result */
return res.end(JSON.stringify({ success: true }))
}
Hope it helps!
Together with the solution above, you might get undefined when you try to read the posted data on the server side.
If you are using the standard degit of Sapper you are using Polka. In order to enable body-parse in Polka you can do the following.
npm install body-parser
In server.js, add the following
const { json } = require('body-parser');
And under polka() add imported
.use(json())
So that it in the end says something like
...
const { json } = require('body-parser');
polka() // You can also use Express
.use(json())
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
I'm new to node.js so I'll try my best to explain the problem here. Let me know if any clerification is needed.
In my node.js application I'm trying to take a code (which was received from the response of the 1st call to an API), and use that a code to make a 2nd request(GET request) to another API service. The callback url of the 1st call is /pass. However I got an empty response from the service for this 2nd call.
My understanding is that after the call back from the 1st call, the function in app.get('/pass', function (req, res).. gets invoked and it sends a GET request. What am I doing wrong here? Many thanks in advance!
Here is the part where I try to make a GET request from node.js server and receive an empty response:
app.get('/pass', function (req, res){
var options = {
url: 'https://the url that I make GET request to',
method: 'GET',
headers: {
'authorization_code': code,
'Customer-Id':'someID',
'Customer-Secret':'somePassword'
}
};
request(options, function(err, res, body) {
console.log(res);
});
});
Im a little confused by what you are asking so ill just try to cover what i think you're looking for.
app.get('/pass', (req, res) => {
res.send("hello!"); // localhost:port/pass will return hello
})
Now, if you are trying to call a get request from the request library when the /pass endpoint is called things are still similar. First, i think you can remove the 'method' : 'GET' keys and values as they are not necessary. Now the code will be mostly the same as before except for the response.
app.get('/pass', (req, res) => {
var options = {
url: 'https://the url that I make GET request to',
headers: {
'authorization_code': code,
'Customer-Id':'someID',
'Customer-Secret':'somePassword'
}
};
request(options, function(err, res, body) {
// may need to JSONparse the body before sending depending on what is to be expected.
res.send(body); // this sends the data back
});
});