Downloading dynamically generated files from SPA

I make single page applications with Angular that use Meteor as a pure backend, no routing in Meteor, just using methods over DDP.

I need to add the possibility to download content files (pdf, csv, etc.) that are dynamically generated on the server. I also need to be able to check who the user is and if he is authenticated.

I found this mhagmajer:server-router which seems to do what I need. But it seems that is is not very widely used, although I think that what I need would be fairly common use case.

Is there a more common way to to this?

for clarity
What I want to do would be done in express.js like this:

router.post(’/pdf’, (req, res, next) => {
res.setHeader(‘Content-disposition’, ‘inline; filename="’ + req.filename + ‘"’);
res.setHeader(‘Content-type’, ‘application/pdf’);
pdf.create(req.pdfHtml, req.pdfOptions).toStream(function (err, stream) {
if (err) {
next(err);
} else {
stream.pipe(res);
}
});
});

I usually generate the content in a method, then finish up the file encoding client side, and invoke a save dialog.

For example this is in a method:


    const workbook = new Workbook()
    workbook.addSheet('Summary', summary)
    workbook.addSheet('Content', worksheet)

    return workbook

And then client side:

  handleExportClick = async () => {
    import XLSX from 'xlsx'

    try {
      const workbook = await Methods.exportGroupData(this.props.group._id)
      XLSX.writeFile(workbook, 'group-data.xlsx')
    } catch (e) {
      console.error(e)
    }
  }

That’s using the xlsx package from npm, but it’s just using the filesaver API to create a file client side.

thank you captainn,

I was actually thinking about doing something like that, but I was still curious if i could do it through the server.

Seems that it is acually very simple: Meteor exposes Connect.js (which is the basis for Express.js) through WebApp.connectHandlers.

Simply doing

import { WebApp } from 'meteor/webapp';
const pdf = require('html-pdf');

WebApp.connectHandlers.use('/hello', (req, res, next) => {
    req.filename = 'test.pdf';
    const html = `<strong>this should look bold</strong>`;
    res.setHeader('Content-disposition', 'inline; filename="' + req.filename + '"');
    res.setHeader('Content-type', 'application/pdf');
    pdf.create(html).toStream(function (err, stream) {
        if (err) {
            next(err);
        } else {
            stream.pipe(res);
        }
    });
});

works!

I can do the authentication thing like this:

  • user hits button;
  • a ‘generate pdf-data’ method generates the data and stores it as a (temporary) document in a mongo collection;
  • method returns the _id of the document;
  • and finally the pdf-route gets called with that _id and returns the pdf.
  • immediatly after generation of the pdf the temporary document is deleted

Is it possible to do this as a fetch call from the client, which accesses the Connect endpoint directly? That way you wouldn’t need to save the file to Mongo and file transport over HTTP is faster than DDP as far as I know. How would you do authentication in this case? Perhaps your method could return some kind of link id (which could be a mongo collection), which can be verified in the endpoint – so basically you’d be generating a one-time download link.

By the way, there’s nothing preventing you from writing your endpoint in Express, then passing it to WebApp.connectHandlers, as Express itself is a compatible middleware.

Another aside, I’ve had success with pdfmake for pdf file generation.

Isn’t that exactly what I described in the section under ‘I can do the authentication thing like this:’?

Yes that’s the same as you described. I guess I was thinking about how to avoid saving the pdf to mongo temporarily, and instead returning a link first for authentication (since Meteor’s accounts need to use methods for the user info, whereas connectHandlers don’t have access to user info AFAIK). Then in connectHandlers generate the pdf and immediately send it as a download. Exact same thing really, and perhaps you have good reasons for (pre-)generating the pdf and saving it to mongo temporarily.