404 Error with 'get' fetch request from locally hosted server - javascript

I am building a simple blog using the principles I learned here (CRUD app with Express and MongoDB). In order to edit previously published posts, I want to populate a text field with the contents of a blog post whose title is selected from a drop-down menu. That way I can easily make my changes and submit them.
I have written a basic 'put' fetch request that does absolutely nothing except report an error if something goes wrong:
fetch('articles', {method: 'put'})
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' + response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
Nothing goes wrong here, so console.log(data) is executed:
However, when I change 'put' to 'get' (and make no other changes), I get a 404 error:
How can it be that the 'get' fetch request causes a 404 error, but the 'put' request doesn't? I am hosting this app locally, so I'm running it from localhost:3000. The 'get', 'post', and 'put' operations in my server code look like this:
app.get('/', function (req, res) {
db.collection('articles').find().toArray(function (err, result) {
if (err) return console.log(err);
res.render('index.ejs', {articles: result});
});
});
app.post('/articles', function (req, res) {
db.collection('articles').save(req.body, function (err, result) {
if (err) return console.log(err);
console.log('saved to database');
res.redirect('/');
});
});
app.put('/articles', function (req, res) {
db.collection('articles')
.findOneAndUpdate({title: 'test title'}, {
$set: {
title: req.body.title,
body: req.body.body
}
}, {
sort: {_id: -1},
upsert: true
}, function (err, result) {
if (err) return res.send(err);
res.send(result);
});
});
I would like to eliminate this 404 error so I can proceed with the ultimate reason I'm using the 'get' request: to populate a text field with the contents of a previously published blog entry. After I've made my edits, I plan to use the 'put' request to finalize the changes in MongoDB.
Please let me know whether you need any additional information. Your advice is much appreciated!

You do not have a GET /articles route; you do have a GET / route, however.
If you want GET /articles to work, you should change the first argument to app.get to '/articles'.

Related

Storing API response in session

On POST of a page I'm trying to GET information from an API, return the output and store the results so it can be used across all pages. The method in which the API is called isn't the best but it's just for proof of concept rather than production quality. So here is the code at the moment:
router.post('/page-one/', function (req, res) {
var options = {
'method': 'GET',
'url': 'https://api.information.service.com/company/<value>',
'headers': {
'Authorization': 'Basic <key>'
}
}
request(options, function (error, response) {
if (error) throw new Error(error)
console.log(response.body)
})
res.redirect('/page-two/')
})
So this works fine, the console returns the right information. How would I then take this and use it across all pages? Say on page 2 my get is:
router.get('/page-two/', function (req, res) {
res.render('/page-two/', {
})
})
I'm using Express, Express Session, Express Writer and Nunjucks.

Node.JS and Express res.redirect() not enabling new webpage

I'm trying to save a variable to a text file, but if the variable isn't found when using spotifyApi.clientCredentialsGrant(), then I want my server to redirect to app.get('/error', function(req, res) {}); which displays a different webpage, but it's returning the error:
(node:11484) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
How can I get around this error to display the webpage error.html?
I don't have access to EJS or window.location because it conflicts with other files and it's a node.js program, respectively.
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
try {
spotifyApi.clientCredentialsGrant()
.then(function (data) {
// Save the access token so that it's used in future calls
client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
}, function (err) {
console.log('Something went wrong when retrieving an access token', err.message);
throw err;
});
fs.writeFile("./public/client_cred_token.txt", '', function (err) {
console.log('Clearing previous access token');
});
fs.writeFile("./public/client_cred_token.txt", client_cred_access_token, function (err) {
if (err) return console.log(err);
});
fs.readFile('./public/client_cred_token.txt', function (err, data) {
if (err) throw err;
console.log("Saved Client Credentials as: %s", data)
});
}
catch (err) {
res.redirect('/error');
}
});
Key takeaway from the accepted answer is to not send any HTML/files to the server until it's confirmed which one is needed.
You are calling res.sendFile() first and then if you later get an error, you are also calling res.redirect('/error') which means you'll be trying to send two responses to one http request which triggers the error you see. You can't do that.
The solution is to call res.sendFile() at the end of all your other operations so you can then call it when successful and call res.redirect() when there's an error and thus only call one or the other.
In a difference from the other answer here, I've shown you how to code this properly using asynchronous file I/O so the design could be used in a real server designed to serve the needs of more than one user.
const fsp = require('fs').promises;
app.get('/', async function (req, res) {
try {
let data = await spotifyApi.clientCredentialsGrant();
// Save the access token so that it's used in future calls
client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
await fsp.writeFile("./public/client_cred_token.txt", client_cred_access_token);
let writtenData = await fsp.readFile('./public/client_cred_token.txt');
console.log("Saved Client Credentials as: %s", writtenData);
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
} catch (err) {
console.log(err);
res.redirect('/error');
}
});
app.get('/', function (req, res) {
try {
spotifyApi.clientCredentialsGrant().then(function (data) {
// Save the access token so that it's used in future calls
let client_cred_access_token = data.body['access_token'];
console.log(client_cred_access_token);
console.log('Client Credentials Success!');
// truncate token file
fs.truncateSync("./public/client_cred_token.txt");
// write token to file
fs.writeFileSync("./public/client_cred_token.txt", client_cred_access_token);
// read token from file again
// NOTE: you could use `client_cred_access_token` here
let data = fs.readFileSync('./public/client_cred_token.txt');
console.log("Saved Client Credentials as: %s", data)
// send homepage to client when no error is thrown
res.sendFile(path.join(__dirname, '/public', 'homepage.html'));
}, function (err) {
console.log('Something went wrong when retrieving an access token', err.message);
throw err;
});
} catch (err) {
res.redirect('/error');
}
});
I swapped all asynchron file opreations with the syncron one.
They throw an error and you dont have to deal with callback chain/flow.
Also i moved the sendFile(...) at the botom in the try block, so when a error is thrown from any syncrhonus function call the sendFile is not reached, and your redirect can be sent to the client.
Otherwise you would send the homepage.html to the client, with all headers, and a redirect is not possible.

Node.JS returns a 404 on interrogation of an endpoint that actually exists

I have a web application, started by a previous company, written in Angular.JS. The application exposes a request towards the back-end (written in Node.JS+Express) to gather some data required to fill a table. Specifically, this is the request that the application sends everytime the user enters in the page that holds the table (The config variable holds the access token).
return $http.get(API + '/api/myPath/for/Having/Data', config).then(handleSuccess, handleError);
handleSuccess and handleError are so defined
handleSuccess: function (res) {
debugger;
var deferred = $q.defer();
res.data.success ? deferred.resolve(res.data) : deferred.reject(res.data.message);
return deferred.promise;
},
handleError: function (error) {
return {
success: false,
message: error
};
}
In my back-end I've put an a listener to whatever gets called with the "/api" prefix, like this
app.use('/api', authorization.validateToken);
And another listener, that should work only if there is no match (written at the very end of the file that handles all the general inquiries of the app)
app.all('*', (req, res) => {
console.log('Hi, Stack Overflow!');
res.send({
success: false,
status: 404,
message: 'Invalid Uri Resource'
});
});
And, lastly, this is the endpoint that should get called in the back-end from Angular.js
app.get('/api/myPath/for/Having/Data', something.somethingToCall);
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice, resulting in one failing procedure (404) and another one that goes smoothly (200).
The operation flow should be like this: Angular calls the back-end --> Node checks the validity of the token --> executes operation if everything goes okay.
The operation is called twice (seen thanks to the Visual Studio Code debugger and Chrome's Network Monitor) and, even though the token's validation process is correctly executed everytime, the first time the next() function will hold the app.all() listener.
Also,even before I start debugging the first request that is sent out, the JavaScript console on Google Chrome warns me that there has been an error such as like "Cannot read property 'data' of undefined", meaning that the request gets executed twice with the first time returning a 404.
exports.validateToken = (req, res, next) => {
console.log(`check user here`);
// next();
var token = //I take the token
console.log(token);
if (token) {
jwt.verify(token, require('../../secret'), (err, decoded) => {
if (err) {
res.send({
success: false,
status: 500,
tokenExpired: true,
message: "Effettua nuovamente l'accesso"
});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.send({
success: false,
status: 406, // Fprbidden
message: 'User not Authenticated'
});
}
};
Does anybody know how to help me somehow?
EDIT: this is an example of how Chrome's sees both requests. The screenshot, in particular, refers to the first one that gets called and produces the 404
The CORS is handled in the back-end like this
app.use(function (req, res, next) {
if (req.headers.origin && (req.headers.origin.match("http:\/\/somewebsite.com.*") || req.headers.origin.match("http:\/\/localhost:8010") )) {
res.header("Access-Control-Allow-Origin", req.headers.origin);
}
next();
});
Also, I'm adding the endpoint that needs to be called. This also exploits MongoDB + Mongoose for querying the DataBase and return stuff to the front-end. The parameters that I'm passing are pageSize (how many elements per page) and the current page number
exports.getAds = (req, res) => {
var criteria = req.body || {};
var pageSize = criteria['pageSize'] ? Number(criteria['pageSize']) : undefined;
var pageNumber = criteria['pageNumber'] ? Number(criteria['pageNumber']) : undefined;
var sort = criteria.sort || { createdAt: 'desc' };
if (criteria.customerName) criteria.customerName = { $regex: `.*${criteria.customerName}.*`, $options: 'i' };
if (criteria.spentEuros) criteria.spentEuros.$gte = criteria.spentEuros;
if (criteria.referralMail) criteria.referralMail = { $regex: `.*${criteria.referralMail}.*`, $options: 'i' };
console.log(criteria);
var columns = "customerName duration spentEuros";
if (pageSize && pageNumber) {
Adv.paginate(criteria, {
page: pageNumber,
limit: pageSize,
select: columns,
sort: sort
}, function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
} else {
Adv.find(criteria)
.select(columns)
.sort(sort)
.exec(function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
}
};
EDIT2: Solution to the question: adding an app.options listener in the back-end (as pointed out by #slebetman), alongside with the already existing app.get one, solved the issue
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice...
That sounds a lot like the browser sending a CORS preflight OPTIONS request, followed by a GET. Check the HTTP verb being used, and be sure you're handling OPTIONS (not just GET) if you need to support CORS on your endpoint. (If you're not expecting this to be a cross-origin request, check the origin of the page relative to the origin of the API call, something [protocol, port, domain] seems to be different — if it's an OPTIONS call.)

Node JS file transfer via Request Module

I am creating a node server to which files can be uploaded an then sent to a storage server which is also using node.
To do this i am using this method described on the Form-Data module page:
var formData = {
my_field: 'my_value',
my_file: fs.createReadStream(__dirname + '/unicycle.jpg'),
};
request.post({url:'http://service.com/upload', formData: formData}, function(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
My Problem is that when i try to write the file on the storage server it creates a text file with the content [object Object].
Here is my code:
main.js
var form = new formData();
form = {
'oldFileName': oldName,
'newFileName': newName,
'file': fs.createReadStream(FILEPATH),
};
request.post({url:'http://127.0.0.1:9001/upload', form: form}, function(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
});
storage.js
app.post('/upload', function(req,res){
//Filenames are displayed without problem
console.log(req.body.newFileName);
console.log(req.body.oldFileName);
fs.writeFile('./testing/' + req.body.newFileName, req.body.file, function(err) {
if(err) {
return console.log(err);
}
})
I'm sure I'm missing something really obvious, but I cant seem to get it to work.
You are passing formData in form option of request that changes the content to application/x-www-form-urlencodedinstead of multipart/form-data.
app.js
var form = {
'oldFileName': oldName,
'newFileName': newName,
'file': fs.createReadStream(FILEPATH),
};
request.post({url:'http://127.0.0.1:9001/upload', formData: form}, function(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
});
Also, to parse multipart/form-data, you have to use multer or similar library, body-parser doesn't work in that case. Please find following working storage.js code for saving file.
storage.js
var multer = require('multer')
var upload = multer({
storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './testing/');
},
filename: function (req, file, cb) {
cb(null, req.body.newFileName);
}
})
}).single('file');
app.post('/upload', function(req, res, next){
upload(req, res, function (err) {
if(err){
return res.send(err);
} else{
return res.send("Upload successfully");
}
});
});
Hope it helps you.
Alternatively, wrapping the [object Object] within a JSON.stringify() method should reveal the literal string content of the objects.
In my situation I was using the NodeJS Library for YouTube video uploads, following OAuth2.0 protocol.
Within this standard, you post your Client ID and Client Secret to authenticate your usage of the YouTube Data API.
In return, the server returns tokens, in the form of an Access Token and Refresh Token. These tokens are need to refresh the ability to use the API without expiry.
However, I was receiving (Object, object) in the terminal when requesting the 'tokens'....
Logger.log(Got the tokens:(token));
To rectify the problem and reveal the tokens in the terminal in a readable string format, I done the following...
Logger.log(Got the tokens: ${JSON.stringify(token)});
Now I can use the tokens accordingly.
//Note - ES6 backticks are used as string literals, but the backticks don't seem to be displaying in the parameters.

MYSQL + Node.JS Post Request Confusion

I am very new to networking and I have this code which, when I use a REST API like Postman, does exactly what I want it to do:
router.post('/', function(req,res,next){
var reqObj = req.body;
console.log(reqObj);
req.getConnection(function(err, conn){
if(err)
{
console.error('SQL Connection error: ', err);
return next(err);
}
else
{
var query = conn.query("INSERT INTO coordinates (id,lat,lon) VALUES(3,2,1);");
if(err)
{
console.error('SQL error: ', err);
return next(err);
}
res.json("Coordinates sent.");
}
})
} );
That is, it sends the query request to the MYSQL database. My question is, how do I do this without using Postman to send the POST request?
Thank you.
You can't unless you make a post request from within your application or something. If you don't intend on sending data, you can just make it a GET request by changing
router.post('/', function(req,res,next){
to
router.get('/', function(req,res,next){
Then you can just go to the relevant URL from your browser. If you're using chrome and you just wanna see the JSON data, I'd also recommend installing the JSONView chrome extension.
EDIT
Here's the example request using request-promise
var request = require('request-promise');
var objectData = {
name: 'Bruce',
alias: 'Batman'
};
var options = {
method: 'POST',
uri: 'http://your.api/endpoint/',
body: objectData,
json: true // Automatically stringifies the body to JSON
};
request(options).then(function(response){
// handle success response
}, function(error){
// handle error response
})

Categories