Search code examples
node.jsasynchronousexpressnedb

how to publish a page using node.js


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.


Solution

  • 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.