I have just begun to learn node.js. Over the last two days, I've been working on a project that accepts userinput and publishes a ICS file. I have all of that working. Now consider when I have to show this data. I get a router.get to see if I am at the /cal page and..
router.get('/cal', function(req, res, next)
{
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
//console.log(ical.toString());
});
});
res.send(ical.toString());
// res.render('index', {
// title: 'Cal View'
// })
})
So when /cal is requested, it loops through my db and creates an ICS calendar ical. If I do console.log(ical.toString) within the loop, it gives me a properly formatted calendar following the protocol.
However, I'd like to END the response with this. At the end I do a res.send just to see what gets published on the page. This is what gets published
BEGIN:VCALENDAR VERSION:2.0
PRODID:calendar//EN
END:VCALENDAR
Now the reason is pretty obvious. Its the nature of node.js. The response gets sent to the browser before the callback function finishes adding each individual VEVENT to the calendar object.
I have two related questions:
1) Whats the proper way to "wait" till the callback is done.
2) How
do I use res to send out a .ics dynamic link with
ical.toString() as the content. Do I need to create a new view for
this ?
edit: I guess for number 2 I'd have to set the HTTP headers like so
//set correct content-type-header
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename=calendar.ics');
but how do I do this when using views.
Simply send the response, once you got the neccessary data! You are not required to end or send directly in your route but can do it in a nested callback as well:
router.get('/cal', function(req, res, next) {
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
});
res.type('ics');
res.send(ical.toString());
});
});
I also included sending the proper Content-Type by using res.type.
Also: Don't forget to add proper error handling. You can for example use res.sendStatus(500) if an error occured while retrieving the documents.
Related
I am working on building a blog API for a practice project, but am using the data from an external API. (There is no authorization required, I am using the JSON data at permission of the developer)
The idea is that the user can enter multiple topic parameters into my API. Then, I make individual requests to the external API for the requested info.
For each topic query, I would like to:
Get the appropriate data from the external API based on the params entered (using a GET request to the URL)
Add the response data to my own array that will be displayed at the end.
Check if each object already exists in the array (to avoid duplicates).
res.send the array.
My main problem I think has to do with understanding the scope and also promises in Axios. I have tried to read up on the concept of promise based requests but I can't seem to understand how to apply this to my code.
I know my code is an overall mess, but if anybody could explain how I can extract the data from the Axios function, I think it could help me get the ball rolling again.
Sorry if this is a super low-level or obvious question - I am self-taught and am still very much a newbie!~ (my code is a pretty big mess right now haha)
Here is a screenshot of the bit of code I need to fix:
router.get('/:tagQuery', function(req, res){
const tagString = req.params.tagQuery;
const tagArray = tagString.split(',');
router.get('/:tag', function(req, res){
const tagString = req.params.tag;
const tagArray = queryString.split(',');
const displayPosts = tagArray.map(function(topic){
const baseUrl = "https://info.io/api/blog/posts";
return axios
.get(baseUrl, {
params: {
tag: tag
}
})
.then(function(response) {
const responseData = response.data.posts;
if (tag === (tagArray[0])){
const responseData = response.data.posts;
displayPosts.push(responseData);
} else {
responseData.forEach(function(post){
// I will write function to check if post already exists in responseData array. Else, add to array
}); // End if/then
})
.catch(function(err) {
console.log(err.message);
}); // End Axios
}); // End Map Function
res.send(displayPosts);
});
Node.js is a single thread non-blocking, and according to your code you will respond with the result before you fetching the data.
you are using .map which will fetch n queries.
use Promise.all to fetch all the requests || Promise.allsettled.
after that inside the .then of Promise.all || promise.allsettled, map your result.
after that respond with the mapped data to the user
router.get('/:tag', function (req, res) {
const tagString = req.params.tag;
const tagArray = queryString.split(',');
const baseUrl = "https://info.io/api/blog/posts";
const topicsPromises=tagArray.map((tobic)=>{
return axios
.get(baseUrl, {
params: {
tag: tag
}
})
});
Promise.all(topicsPromises).then(topicsArr=>{
//all the data have been fetched successfully
// loop through the array and handle your business logic for each topic
//send the required data to the user using res.send()
}).catch(err=>{
// error while fetching the data
});
});
your code will be something like this.
note: read first in promise.all and how it is working.
My code is
main.post('/userlist', function(req, res, next) {
// where did you get this?
// var db = req.db;
var query = connection.query("SELECT name,zaman,giriscikis FROM giriscikis where date_format(zaman,'%Y-%m-%d') between ? and ?", [req.body.bas, req.body.bitis], function(err, result) {
if (err) throw err;
console.log(result);
res.send(httpResponse(result));
return res.redirect('/zamansorgu');
//console.log(req.body.bas)
//console.log(req.body.bitis)
});
});
I want to fecth data from database and redirect to same page in the code(zamansorgu.html)
But I get an error
Cannot set headers after they are sent to the client
How can I solve this problem
thank you for your helps
You are attempting to send back JSON data and redirect to a different page. That's not possible. Each endpoint request can have one response, not more. You can either send back the data, or redirect. That's because redirecting really does send back data too (the html of the new target page).
Think about it from the caller's point of view. If it did allow this how would it work? If someone uses this link from a browser should the browser show the JSON data you returned, or should it take the user to the new page?
The error is saying "hey, I already sent back data. I can't redirect now because we are already down the path of returning some JSON".
If you want to use the data to format the output that can be done, or if you want to redirect to a new location and pass the data in the url, that's also possible. Consider code like this:
main.post('/userlist', function(req, res, next) {
// var db = req.db;
var query = connection.query("SELECT name,zaman,giriscikis FROM giriscikis where date_format(zaman,'%Y-%m-%d') between ? and ?", [req.body.bas, req.body.bitis], function(err, result) {
if (err) return next(err);
if (result.urlparam) {
// this builds a new url using the query value
const nextUrl = `/zamansorgu?param=${result.urlparam}`;
return res.redirect(nextUrl);
}
else {
// this builds html here
const html = `<html><body><h1>${result.title}</h1></body></html>`;
return res.send(html);
}
});
});
I also ran into this, in my case it was quite a deceptive little bug. A node-inspector session helped me pinpoint the problem quickly however. The problem in my case was pretty bone-headed, the res.end call in the sample below is the offending line.
res.writeHead(200, {"Content-Type": "application/json"});
res.end(JSON.stringify(someObject));
someObject did not exist after a refactor and that was causing a ReferenceError to get thrown. There is a try-catch in Router.prototype._dispatch that is catching the ReferenceError and passing it into next
res.status(301).redirect(`/zamansorgu?name=${"John"}&email=${"john#email.com"}`)
So, this is something I explored but it will be dependent on the structure of your application. You could always pull the data out using query params and hydrate your application.
I'm still a bit new to Node in general, so I'm sorry if this is noob question. My setup is Sailsjs + MongoDB.
I have a RESTful API controller set up to handle the "lab" collection in my DB.
Here is what I use in my controller to pull up all the objects in this collection when /lab/ is used:
index: function (req, res, next) {
Lab.find(function foundLabs(err, labs) {
if (err) return next(err);
res.view({
labs: labs
});
});
},
In this collection there are fields for "site" and "lab" and I'd like to be able to filter what shows up with params like:
/lab/:site
/lab/:site/:lab
So if "/lab/aq" was pulled up it would get all objects in the AQ site and if "/lab/aq/123" was pulled up it would get the objects for the 123 lab in the AQ site.
I know this would likely be done with the Lab.find function, but I haven't been able to find any documentation which gives me an answer I'm looking for.
Any help would be appreciated.
In your config/routes.js file you need to add a route with optional parameters:
'/findLabs/:site?/:lab?': 'LabController.findLabs'
// use a different route than 'lab' to avoid interfering with the blueprint routes
Then, in your LabController.js, if the requested url had site and/or lab, you will find them in req.params:
// when a request is sent to '/findLabs/aq/123':
findLabs: function(req, res, next) {
sails.log(req.params) // {site: 'aq', lab: '123'}
// you can use them to filter:
var query = Lab.find();
if (req.params.site) query.where({site: req.params.site});
if (req.params.lab) query.where({lab: req.params.lab});
query.exec(function(err, labs) {
if (err) return res.serverError();
res.json(labs);
});
}
I have the following (simplified for example) angular directive which creates a dropzone
directives.directive('dropzone', ['dropZoneFactory', function(dropZoneFactory){
'use strict';
return {
restrict: 'C',
link : function(scope, element, attrs){
new Dropzone('#'+attrs.id, {url: attrs.url});
var myDropZone = Dropzone.forElement('#'+attrs.id);
myDropZone.on('sending', function(file, xhr, formData){
//this gets triggered
console.log('sending');
formData.userName='bob';
});
}
}
}]);
As you can see the the sending event handler I'm trying to send the username ("bob") along with the uploaded file. However, I can't seem to retrieve it in my route middleware as req.params comes back as an empty array (I've also tried req.body).
My node route
{
path: '/uploads',
httpMethod: 'POST',
middleware: [express.bodyParser({ keepExtensions: true, uploadDir: 'uploads'}),function(request,response){
// comes back as []
console.log(request.params);
//this sees the files fine
console.log(request.files);
response.end("upload complete");
}]
}
Here is what the docs say on the sending event
Called just before each file is sent. Gets the xhr object and the formData objects as second and third parameters, so you can modify them (for example to add a CSRF token) or add additional data.
EDIT
I dropped the programmatic approach for now. I have two forms submitting to the same endpoint, a regular one with just post and a dropzone one. Both work, so I don't think it's an issue with the endpoint rather with how I handle the 'sending' event.
//Receives the POST var just fine
form(action="http://127.0.0.1:3000/uploads", method="post", id="mydz")
input(type="hidden", name="additionaldata", value="1")
input(type="submit")
//With this one I can get the POST var
form(action="http://127.0.0.1:3000/uploads", method="post", id="mydz2", class="dropzone")
input(type="hidden", name="additionaldata", value="1")
OK, I've actually figured it out, thanks to Using Dropzone.js to upload after new user creation, send headers
The sending event:
myDropZone.on('sending', function(file, xhr, formData){
formData.append('userName', 'bob');
});
As opposed to formData.userName = 'bob' which doesn't work for some reason.
I would like to add to NicolasMoise's answer.
As a beginner in webdev I got stuck on how to obtain an instance of Dropzone. I wanted to retrieve an instance of Dropzone that had been generated by the autodiscovery feature. But it turns out that the easiest way to do this is to manually add a Dropzone instance after first telling Dropzone not to auto-discover.
<input id="pathInput"/>
<div id="uploadForm" class="dropzone"/>
<script>
$(document).ready(function(){
Dropzone.autoDiscover = false;
var dZone = new Dropzone("div#uploadForm", {url: "/api/uploads"});
dZone.on("sending", function(file, xhr, data){
data.append("uploadFolder", $("#pathInput")[0].value);
});
});
</script>
Serverside the data will be in request.body.uploadFolder
Nicolas answer is one possible solution to the problem. It is especially useful if you need to alter the file object prior to sending.
An alternative is to use the params option:
var myDropzone = new Dropzone("div#myId",
{ url: "/file/post", params: { 'param_1': 1 }});
cf. the documention
For those that are using thatisuday/ng-dropzone the callback methods are done as such:
<ng-dropzone class="dropzone" options="dzOptions" callbacks="dzCallbacks" methods="dzMethods"></ng-dropzone>
In a controller:
$scope.dzCallbacks = {
sending: function(file, xhr, form) {
console.log('custom sending', arguments);
form.append('a', 'b');
}
};
I've been building my first node.js app using express.js.
It has been fun :)
I must be having some kind of misconception going, so here goes.
I have some route defined as such:
app.all('/account/summary', apiPOST('AccountSummary', {FromDate: 'default', ToDate: 'default'}), function(req, res){
var data=req.apiJSON;
console.log(data);
res.render('accountsummary', {locals: data, layout: 'layouts/layout'});
});
apiPOST() is defined as such:
apiPOST = function (operation, postParams) {
return function (req, res, next){
console.log('RIGHT AT START');
console.log(postParams);
console.log('END');
var currentDate = new Date();
var day = ('0'+currentDate.getDate()).slice(-2);
var month = ('0'+(currentDate.getMonth() + 1)).slice(-2);
var year = ('0'+currentDate.getFullYear()).slice(-4);
console.log('BEFORE DATES');
console.log(postParams);
if (typeof(postParams.FromDate)!='undefined' && (postParams.FromDate=='default' || postParams.FromDate=='')){
postParams.FromDate=year+'-'+month+'-'+day;
}
if (typeof(postParams.ToDate)!='undefined' && (postParams.ToDate=='default' || postParams.ToDate=='')){
postParams.ToDate=year+'-'+month+'-'+day;
}
//automatically add all posted data to postParams
if (typeof(req.body)!='undefined'){
for (var key in req.body){
if (req.body.hasOwnProperty(key)){
postParams[key]=req.body[key];
}
}
}
// here is do some talking to an XML web service and convert it to JSON;
// we use our postParams variable to POST
next();
}
}
First off this works fine. When reaching the page on a GET request, it defaults both FromDate and ToDate to today. This page has a form that you may post to specify a new FromData and ToDate. the posted data automatically get added to the postParams and that also works fine.
The problem that I am experiencing is that the next time a user visits the page using GET, the previously POSTed data is still around and so it default to that and not to today.
I cannot figure out why that data is still available. By the looks of it, it is not being posted, but rather remembered in postParams. Is postParams now global?
Thanks!
What's happening is that you're calling apiPOST only once, during the app.all call to configure that route and that one call creates the one postParams parameter object that all future invocations of apiPOST's returned function will share.