Update Frontend after using Delete Route in Express.js - node.js

I have created a delete route in express.js that looks like the following:
router.delete("/notes/:id", (req, res) => {
console.log("delete route called")
const noteToRemove = findById(req.params.id, notes);
const result = notes.filter(note => note !== noteToRemove)
console.log(result);
fs.writeFileSync(
path.join(__dirname, '../../db/db.json'),
JSON.stringify({ notes: result }, null, 2)
);
)}
findById is a function I have declared in another file to locate an item in my database db/db.json that locates an item based on an ID I created earlier.
The issue here is that this code will remove an item from the database, but I want the frontend to show the updated list from the database. How does one reload the frontend to see the updated changes in the database?

Two things:
You will need to include a response in your server side method to let the client know that the resource was deleted. You should also add responses with error codes in cases something goes wrong, but for simplicity here we only address the nominal case.
router.delete("/notes/:id", (req, res) => {
console.log("delete route called")
const noteToRemove = findById(req.params.id, notes);
const result = notes.filter(note => note !== noteToRemove)
console.log(result);
fs.writeFileSync(
path.join(__dirname, '../../db/db.json'),
JSON.stringify({ notes: result }, null, 2)
);
res.end(); // respond to the client to let them know we are finished
)}
On the front-end you will need to react to this response and reload the resources. This highly depends on how your front end is structured and how it gets data from the server. As a hack, for now, you can just call location.reload(), which will do the trick in the vast majority of cases -- but isn't very elegant of course nowadays where you could refetch and render just parts of the data and page.

Related

Send blob-data along with a string to backend

I´ve got a weird problem.
Using Node, React, Express, MongoDB -> MERN Stack.
So my page generates a PDF file which then gets send to the backend (as blob data) and is being stored on there.
The problem I have, now I need to send a payment ID along with that blob data to save the order in the data base. I need both in one post request, to make it as smooth as possible:
await axios
.post(process.env.REACT_APP_SERVER_API + '/payment/cash', {
blobData,
paymentId
})
.then(async (res) => ...
like so.
Before, when I just sent the blob data, I could simply access the data in the backend by writing:
exports.createCashOrder = async (req, res) => {
const { filename } = req.file; // THIS RIGHT HERE
const fileHash = await genFileHash(filename);
try {
await saveOrder(filename, fileHash, "cash", paymentId);
//await sendOrderCreatedEmail(req.body, fileHash);
//await sendOrderReceivedConfirmEmail(req.body, fileHash);
res.send({ filename: filename });
}
But that doesn't work anymore. I dont have access to that file object anymore when sending that request object.
Neither by trying
req.body.blobData
req.body.blobData.file
req.file
Any idea how to achieve that, except from making two seperate post requests?
Glad for any help, cheers!
Send the data as a form
await axios
.postForm(process.env.REACT_APP_SERVER_API + '/payment/cash', {
blobData,
paymentId
})
.then(async (res) => ...
And then use multer middleware to handle the form in express.

Why can't I make express route synchronous

I know what is wrong with my code and I have looked into the best way of solving it, however with my lack of experience, I am having a hard time finding a good answer.
I need my first route(/data) to be fully completed before the second(/logo) express route sends the data. In short, I just need the variable symbolUrl to be completed before it goes into the second fetch call. Here is the code down below to explain
app.use(express.static('public'));
const url =
'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest';
const qString =
'?CMC_PRO_API_KEY=' + process.env.apiKey + '&start=1&limit=10&convert=USD';
let symbol = [];
app.get('/data', async (req, res) => {
const fetch_res = await fetch(url + qString);
const coinData = await fetch_res.json();
for (let i = 0; i < 9; i++) {
symbol.push(coinData.data[i]['symbol']);
};
res.json(coinData);
});
app.get('/logo', async (req, res) => {
const symbolUrl = symbol.join(',');
const url2 = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/info';
const qString2 = `?CMC_PRO_API_KEY=${apiKey}%symbol=${symbolUrl}`;
const fetch_res2 = await fetch(url2 + qString2);
const coinLogo = await fetch_res2.json();
res.json(coinLogo);
});
The issue I am trying to solve with this project is that I want to send the data(/data) to be sent to the front end first because this API call will load the majority of the page. Then my second call will load images and other larger files afterward. HOWEVER, the API I am working with to get the logos(images) of the specific crypto coins I want, I need a different endpoint as well as use %symbol=${symbolUrl} in the API call to get the correct tokens I want to call.
client code:
fetch('http://localhost:2000/data')
.then(async (response) => {
return response.json();
})
.then(async (data) => {
const parsedData = data['data'];
// console.log(data['data'][0]['name'])
await parsedData.forEach((element) => {
// this just has about 20 lines of code generating the the look of the page. It works as intended
});
fetch('http://localhost:2000/logo')
.then(async (response) => {
return response.json();
})
.then(async (logo) => {
console.log(logo)});
***I have tried putting this in an async function and awaiting the first fetch call
All I need to be done is for app.get(/data) to be fully complete before doing my second app.get. I have done testing and I know that is the issue. I apologize if it is something easy, but I couldn't find anything on making an app.get synchronous and I have tried putting both in a async function, however that did not work.
You cannot send responses in fragments like you're trying to do, it would throw an error saying Can't set headers after they are sent to client
The proper method to implement what you are trying to do is to define the first layer as middleware, and then allow the second layer to return the response. Here layer basically means a function handler.
In order to control when the execution passes to the next layer / next function handler, express has a third parameter (request, response, next). You're only using request and response, researching about next will solve your concern.
Express next function, what is it really for?
First handler
app.get('something_unique', async (req, res, next) => {
// do whatever you want to do first
// save data into res.locals
res.locals.foo = {...}
next()
})
Second Handler
app.get('something_unique', (req, res) => {
const data = res.locals.foo;
// whatever you want
return res.json({ anything })
})
More:
Express next function, what is it really for?
Error: Can't set headers after they are sent to the client
Passing variables to the next middleware using next() in Express.js
I'm not sure what client code you're really running as it sounds like you've bee trying several things, but this should work to sequence the /data request and the /logo request so that the /logo request is not run until the response from the /data request has been received.:
async function run() {
const r1 = await fetch('http://localhost:2000/data');
const data = await r1.json();
const parsedData = data.data;
parsedData.forEach((element) => {
// this just has about 20 lines of code generating
// the the look of the page. It works as intended
});
const r2 = await fetch('http://localhost:2000/logo');
const logo = await r2.json();
return logo;
}
run().then(logo => {
console.log(logo);
}).catch(err => {
// handle errors here
console.log(err);
});
If there is any asynchronous code inside the .forEach(), then we will have to see that also to properly sequence that.
As I've said in my comments, stuffing the data from the first request into a server-side variable is probably the wrong design on the server because two separate clients both issuing /data requests will conflict with one another, creating race conditions. But, you haven't explained what this data is really for or why you're stuffing it into a variable on the server for us to suggest an alternate design.

How to use JSON data from api in frontend

I followed a tutorial for making an api with express and postgres. I can get all my data in json form no problem. But I have no idea how to use the data in the frontend of a site.
These are what I have in 2 different files that are linked.
index.js:
const db = require('../queries')
router.get('/classes/:id', db.getClassById)
router.get('/classes/:id/edit', db.getClassById, (req, res) => {
res.render('dashboard/editClass')
})
queries.js:
const getClassById = (req, res) => {
const id = parseInt(req.params.id)
pool.query('SELECT * FROM classes WHERE state = 1 AND classId = $1', [id], (err, results) => {
if(err){
throw err
}
res.status(200).json(results.rows)
})
}
module.exports = {
getClassById
}
The getClassById query is called by the express middleware and automatically sends the json data to the page, which will not allow the res.render('dashboard/editClass') to work.
So how would I call this query so that I can fill in a form with the data from the query so a user can see the existing data and make any changes they want?
Thanks to Mark and Marc who commented, I realized I needed to fetch the data from my own api when rendering the front end pages. I am now using axios to get this done in node and so far it is doing exactly what I was looking for.

Expressjs Firebase React Redux Cannot set headers after they are sent to the client

I've been researching this issue for several hours now and found something odd. Using ExpressJS, Firebase, and React for a small app, and need to call the Firebase Database via the Express Backend, and I also need to make post requests to store data in the database via the Express Backend.
Functionality: I make a post request to the backend to add data to the database. Since Firebase is real time db, the data will immediately reflect on the page.
Problem: The issue is, when I make a post call to the backend and that completes, the page refreshes but the data doesn't show because of this
ERROR: [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
/**
* Add new note to Firebase
* Real-Time Database
*/
app.post('/addNote', (req, res)=> {
var title = req.body.note.title;
var body = req.body.note.body;
var userId= req.body.note.uid;
db.notes.push({
title: title,
body: body,
uid: userId
})
res.send("Success")
})
app.get('/all', (req, res, next)=> {
db.notes.on('value', snapshot => {
return res.send(snapshot.val());
})
})
Possible Solution: I've found that using the code below, I can make a post request, manually refresh the page, and the data will reflect with no header error. I'm trying to code the proper functionality but can't seem to figure out where the code is sending multiple responses with the db.notes.on because I'm only sending res.send one time. The clear difference is (.on listens and updates immediately, while .once requires manual refresh)
/**
* Add new note to Firebase
* Real-Time Database
*/
app.post('/addNote', (req, res)=> {
var title = req.body.note.title;
var body = req.body.note.body;
var userId= req.body.note.uid;
db.notes.push({
title: title,
body: body,
uid: userId
})
res.send("Success")
})
app.get('/all', (req, res, next)=> {
db.notes.once('value', snapshot => {
return res.send(snapshot.val());
})
})
An on("value" listener to Firebase will fire:
straight away with the current value of the data,
and will then later also fire when the data changes.
Since you're sending the data in the response to the client in #1, the response will be closed/finished by the time #2 happens.
By using a once("value" listener this problem doesn't happen, since once() removes the listener after #1.

Can app.render() render an array of views?

It does not seem to be documented, I was wondering if it is possible to render multiple views or an array of views in Expressjs like so:
const data = 'some data passed by a DB';
const app = express();
const arrayViews = ['layout','email', 'web'];
app.render(arrayViews, data, (err, html) => {
if (err) throw err;
})
or do I have to do it in multiple instances
app.render('email', data, (err, html) => {
if (err) throw err;
})
app.render('web', data, (err, html) => {
if (err) throw err;
})
No. You cannot pass an array of views to app.render(). The point of app.render() is to create ONE piece of rendered HTML that can be sent as a response to a particular http request. You can't send multiple responses to one http request. So, you only want to call res.render() once for any given request.
If you want to have two different types of responses for different situations, then you should either make a separate route for each response and call res.render() once in each response with the appropriate template for that request.
Or, you can pass in a query parameter in the URL and use an if statement in the route handler to decide which template to render. But, the point is that you send exactly one response for each request.
For example, here's looking at a query parameter to decide which type of render to do:
app.get('/mypage', function(req, res) {
if (req.query.page === "email") {
res.render('email', data);
} else {
res.render('web', data);
}
});
Otherwise, you'd have two separate routes:
app.get('/mypage/email', function(req, res) {
res.render('email', data);
});
app.get('/mypage/web', function(req, res) {
res.render('web', data);
});
I may have originally been confused about the point of your question (since you don't show the overall context of your render or what you're using it for).
If you're using app.render() to collect the HTML from rendering operations, you can call that multiple times and wait for all to be done and then you have multiple rendered templates which you can do whatever you want with. But, a single app.render() doesn't accept multiple templates. You'd have to call it multiple times and wait for all the requests to be done.
Promise.all([appRender('email'), appRender('web')]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});
Or, you could use this to make a version of app.render() that would take an array:
const util = require('util');
const appRender = util.promisify(app.render.bind(app));
app.renderAll = function(arrayOfTemplates) {
return Promise.all(arrayOfTemplates.map(template => {
return appRender(template);
}));
});
app.renderAll(['email', ['web']]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});

Resources