Fetch http POST request sending EMPTY object to Node server - javascript

I'm trying to make an HTTP Post request to my Node server from an html form. I use the Body Parser and everything but when I try to fetch the request, and log the req.body in my server, it's an EMPTY object. Do you know how I can fix this?
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const form = document.querySelector('form');
form.addEventListener('submit', e => {
e.preventDefault();
const email = document.querySelector('#email').value;
const password = document.querySelector('#password').value;
const formData = { email, password };
const options = {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(formData),
mode: 'no-cors'
};
fetch('http://localhost:3000/users/login', options)
.then(res => {
console.log(res);
res.json();
})
.then(data => {
console.log(data);
});

You said mode: 'no-cors' so everything that requires CORS permission is ignored (including setting the Content-Type header to application/json).
Remove mode: 'no-cors' (and use the cors middleware on the server).

Related

How can I receive a json in express server

I need to receive a JSON from my front in React. But the JSON comes to me in a strange way (an object with the data in string), I don't know how to return it to type object again.
I send this.
const data = {
email: 'emailc#email.com',
password: 'test'
}
const options = {
method: 'POST',
body: JSON.stringify(data),
mode: 'no-cors',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
const onsubmit = (e) => {
//evitamos que se haga la peticion en automatico
e.preventDefault();
fetch('http://localhost:3001/user/signin',options)
.then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
console.log('send on submit');
}
and I get this on the express server:
[Object: null prototype] {
'{"email":"emailc#email.com","password":"test"}': ''
}
My server is configured in this way:
const express = require('express');
const app = express();
const morgan = require('morgan');
const cors = require('cors');
const {mongoose} = require('./src/database/connection')
const Parser = require("body-parser").urlencoded({ extended: false });
//config
app.set('port', process.env.PORT || 3001);
//middlewares
app.use(morgan('dev'));
app.use(Parser);
app.use(cors()); //accepts connection from all directions
//Routes
app.use(require('./src/test'));
app.use(require('./src/routes/users'));
//Server
app.listen(app.get('port'), (req, res) => {
console.log(`Server on port ${app.get('port')}`);
})
I think I have misconfigured the body-parser, please help, it is my first API.
'Content-Type': 'application/x-www-form-urlencoded'
If you tell the server you are sending Form Encoded data then it is going to try to parse what you send as Form Encoded data.
Don't lie.
If you are sending JSON, say so:
'Content-Type': 'application/json'
const Parser = require("body-parser").urlencoded({ extended: false });
You also need a body parser that supports JSON.
const Parser = require("body-parser").json();
But Express has a built-in body parser and variable names starting with capital letters are traditionally reserved for constructor functions / classes.
const parser = express.json();
Aside:
mode: 'no-cors'
If you are making a cross-origin request then that will break it.
If you are making a same-origin request then that will do nothing.
Remove it.

React, axios, content-type

I have already searched a lot, but none of the solutions found work: Cannot send content-type by axios. but if I use the postman interceptor and I 'send' the request generated by axios this time it works: the node.js / express server correctly receives the request and body-parser works normally!
React side:
const API_URL = "http://localhost:8800/auth/";
const headers = {
accept: 'application/json, text/plain, */*',
'content-type': 'application/json;charset=UTF-8'
};
class AuthService {
register(pseudo, email, password) {
return axios.post(API_URL + "signup/",
{ pseudo, email, password },
{ headers: headers})
.then(response => {
if (response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
});
}
server side
const app = express();
app.use(function (req, res, next) {
console.log( req.headers);
next();
});
app.use( bodyParser.urlencoded({ extended: true }), bodyParser.json());
Usually when I use axios I send the headers in a config variable like this and I stringify the body so it sends as JSON object and not a JS object.
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({arguments});
try {
const res = await axios.post(/url, body, config);
...
Here's a link to the docs for a little more reading about it:
https://github.com/axios/axios

Unable to access cookie from fetch() response

Even though this question is asked several times at SO like:
fetch: Getting cookies from fetch response
or
Unable to set cookie in browser using request and express modules in NodeJS
None of this solutions could help me getting the cookie from a fetch() response
My setup looks like this:
Client
export async function registerNewUser(payload) {
return fetch('https://localhost:8080/register',
{
method: 'POST',
body: JSON.stringify(payload),
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
}
...
function handleSubmit(e) {
e.preventDefault();
registerNewUser({...values, avatarColor: generateAvatarColor()}).then(response => {
console.log(response.headers.get('Set-Cookie')); // null
console.log(response.headers.get('cookie')); //null
console.log(document.cookie); // empty string
console.log(response.headers); // empty headers obj
console.log(response); // response obj
}).then(() => setValues(initialState))
}
server
private setUpMiddleware() {
this.app.use(cookieParser());
this.app.use(bodyParser.urlencoded({extended: true}));
this.app.use(bodyParser.json());
this.app.use(cors({
credentials: true,
origin: 'http://localhost:4200',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
credentials: true
}));
this.app.use(express.static(joinDir('../web/build')));
}
...
this.app.post('/register', (request, response) => {
const { firstName, lastName, avatarColor, email, password }: User = request.body;
this.mongoDBClient.addUser({ firstName, lastName, avatarColor, email, password } as User)
.then(() => {
const token = CredentialHelper.JWTSign({email}, `${email}-${new Date()}`);
response.cookie('token', token, {httpOnly: true}).sendStatus(200); // tried also without httpOnly
})
.catch(() => response.status(400).send("User already registered."))
})
JavaScript fetch method won't send client side cookies and silently ignores the cookies sent from Server side Reference link in MDN, so you may use XMLHttpRequest method to send the request from your client side.
I figured it out. The solution was to set credentials to 'include' like so:
export async function registerNewUser(payload) {
return fetch('https://localhost:8080/register',
{
method: 'POST',
body: JSON.stringify(payload),
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
}
After that I needed to enabled credentials in my cors middleware:
this.app.use(cors({
credentials: true, // important part here
origin: 'http://localhost:4200',
optionsSuccessStatus: 200
})
And then finally I needed to remove the option {httpOnly: true} in the express route response:
response.cookie('token', '12345ssdfsd').sendStatus(200);
Keep in mind if you send the cookie like this, it is set directly to the clients cookies. You can now see that the cookie is set with: console.log(document.cookie).
But in a practical environment you don't want to send a cookie that is accessible by the client. You should usually use the {httpOnly: true} option.

Express.js httpOnly cookie not being set

I've set up an API with a create user and an auth route. The auth route should set an httpOnly cookie containing a JWT, and should send JSON for the client to store in localhost.
In the front-end I'm doing a simple fetch.
The server responds 200 and with the JSON I expect, but somehow, the cookie doesn't get set.
However, in Postman, the cookie does indeed get set.
Express server
const express = require('express')
const cors = require('cors')
// boilerplate stuff
app.use(express.json())
app.use(cors({ origin: 'http://localhost:3000', credentials: true }))
app.post('auth', (req, res) => {
// fetch user from db, validation, bla bla bla
const token = jwt.sign({ issuer: user.id }, keys.private, { algorithm: 'RS256' })
res.cookie('token', token, { httpOnly: true })
res.json(user)
})
Next.js front-end
const handleSubmit = async (e) => {
e.preventDefault()
try {
const res = await fetch('http://localhost:5000/api/v1/auth', {
method: 'post',
mode: 'cors',
credentials: 'include',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(formState),
})
const data = await res.json()
console.log(data)
} catch (err) {
console.error(err)
setError(err.message)
}
}
'Twas resolved.
I was looking in Session Storage as opposed to Cookies in my devtools.

How to fix 'TypeError: Failed to fetch'?

I'm getting a TypeError: Failed to fetch error when I attempt to send a post request using fetch on the front-end and an express route on the back-end.
I'm able to successfully create the new user in the db, but when attempting to obtain that new user data through the fetch promise, that's when the error is being thrown.
app.js
function createNewUser() {
let formUsername = document.getElementById('signup-username').value;
let formEmail = document.getElementById('signup-email').value;
let formPassword = document.getElementById('signup-password').value;
let url = "/users";
let newUserData = {
username: formUsername,
email: formEmail,
password: formPassword
}
fetch(url, {
method: 'POST',
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer',
body: JSON.stringify(newUserData),
}).then(res => res.json())
.then(response => console.log('Success: ', JSON.stringify(response)))
.catch(error => console.error('Error: ', error));
}
users.js
router.post('/users', function(req, res) {
User.create(req.body)
.then(function(user) {
res.json({
user: user
})
}
});
server.js
const express = require('express');
const app = express();
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const bcrypt = require('bcryptjs');
const auth = require('./auth');
const router = require('./routes/routes.js');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(router);
app.use('/', express.static(path.join(__dirname, 'public')));
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"OPTIONS, GET, POST, PUT, PATCH, DELETE" // what matters here is that OPTIONS is present
);
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization", "Access-Control-Allow-Origin");
next();
});
app.listen(3000, function(){
console.log("Listening on port 3000");
});
I need to get that user object back in order to access its data.
Edit:
So, I've figured out that the issue has to do with how the request is submitted on the front-end. If I create the following function and then call it when app.js is loaded, then everything works:
function createNewUserTest() {
let formUsername = 'dd';
let formEmail = 'd#d.com';
let formPassword = 'secrete';
let url = "/api/users";
let newUserData = {
username: formUsername,
email: formEmail,
password: formPassword
}
fetch(url, {
method: 'POST',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUserData),
})
.then(res => res.json())
.then(response => console.log('Success: ', response))
.catch(error => console.error('Error: ', error));
}
createNewUserTest();
But, if I try to call this function either through onsubmit in the form or onclick on the button in the html, or if I use an event listener (see below, which is in app.js), then I get the TypeError: Failed to fetch error:
let signupSubmitButton = document.getElementById('signup-submit');
signupSubmitButton.addEventListener('click', createNewUserTest);
This is even more baffling to me. I'm required to use Vanilla JS and I need to create the user through a form submission, but not sure what I need to adjust here.
Solution
Foiled by the event.preventDefault() again. This was all I needed.
let signupForm = document.getElementById('signup-form');
signupForm.addEventListener('submit', function(event) {
event.preventDefault();
let formUsername = document.getElementById('signup-username').value;
let formEmail = document.getElementById('signup-email').value;
let formPassword = document.getElementById('signup-password').value;
let url = "/api/users";
let newUserData = {
username: formUsername,
email: formEmail,
password: formPassword
}
fetch(url, {
method: 'POST',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUserData),
})
.then(res => res.json())
.then(response => console.log('Success: ', response))
.catch(error => console.error('Error: ', error));
});
The issue was that the page was reloading, which kept me from getting the data back in time. The solution was to simply add event.preventDefault() inside the listener.
app.js
let signupForm = document.getElementById('signup-form');
signupForm.addEventListener('submit', function(event) {
event.preventDefault();
let formUsername = document.getElementById('signup-username').value;
let formEmail = document.getElementById('signup-email').value;
let formPassword = document.getElementById('signup-password').value;
let url = "/api/users";
let newUserData = {
username: formUsername,
email: formEmail,
password: formPassword
}
fetch(url, {
method: 'POST',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUserData),
})
.then(res => res.json())
.then(response => console.log('Success: ', response))
.catch(error => console.error('Error: ', error));
});
The question is about "TypeError failed to fetch". The wording of the message sends one in the direction of network/server/CORS type issues as explored in other answers, but there is one cause I have discovered that is completely different.
I had this problem and took it at face value for some time, especially puzzled because it was provoked by my page POSTing in Chrome but not in Firefox.
It was only after I discovered chrome://net-internals/#events and saw that my request suffered from 'delegate_blocked_by = "Opening Files"' that I finally had a clue.
My request was POSTing a file uploaded from the user's computer via a file input element. This file happened to be a file open in Excel. Although it POSTed fine from Firefox, it was only when closed that it could be posted in Chrome.
Users of your web application need to be advised about this potential issue, and web developers should also be aware that "TypeError failed to fetch" can sometimes mean "TypeError didn't get as far as trying to fetch"
When it comes to CORS problems it's very often because the server doesn't know how to handle it properly. Basically every time you include a header like access-control-origin to your request it will instigate OPTIONS preflight request as well and if your server is not expecting that it will throw an error, because it was expecting a POST only requests.
In other words - try again without the "Access-Control-Origin": "*" part and see if it works or just try patching it on the server with something like this:
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"OPTIONS, GET, POST, PUT, PATCH, DELETE" // what matters here is that OPTIONS is present
);
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next();
});

Categories

Resources