7

I'm trying to trigger a download from a POST request handler in Koa with koa-router. Essentially, I'm trying to do something like this:

app.js

const Koa = require('koa')
const router = require('./router')

const app = new Koa()

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(3000)

router.js

const fs = require('fs')
const Router = require('koa-router')

const router = new Router()

router.post('/generate', function * () {
  const path = `${__dirname}/test.txt`
  this.body = fs.createReadStream(path)
  this.set('Content-disposition', 'attachment; filename= test.txt')
})

module.exports = router

client.js

const { fetch } = window;

const request = {
  method: 'POST',
  body: JSON.stringify({ fake: 'data' })
}

// Make the POST request
fetch('/generate', request)

However, when the POST request is sent, nothing happens. I don't get any error in the server console or the browser console either. Any help would be appreciated!

Nickolay
  • 31,095
  • 13
  • 107
  • 185
Saad
  • 49,729
  • 21
  • 73
  • 112
  • I think your problem is not on server-side code but client-side. Could you show what are you doing with the fetched data? fetch will not work as a form submission, YOU must handle the response and if you want to download it do what Vedran did on his client-side code. – Diego Fernandez Oct 07 '16 at 16:44

3 Answers3

6

You should set file stream in the body and send Content-disposition to attachment with that file name. Use below code

const Router = require('koa-router');
const router = new Router();

router.post('/generate', function * () {
  const path = `${__dirname}/file.txt`;
  this.body = fs.createReadStream(path);
  this.set('Content-disposition', 'attachment; filename= file.txt');
});

module.exports = router;

UPDATE: Complete working code:

var app = require('koa')();
var router = require('koa-router')();
const fs = require('fs');
router.post('/generate', function () {
  const path = `${__dirname}/file.txt`;
  this.body = fs.createReadStream(path);
  this.set('Content-disposition', 'attachment; filename= file.txt');
});

app
  .use(router.routes())
  .use(router.allowedMethods());

  app.listen(3000);

Client:

<button id="btnDownload">Download</button>

<script type="text/javascript">
    const request = {
        method: 'POST',
        body: JSON.stringify({
            fake: 'data'
        })
    }

    document.getElementById('download').onclick = () => {
        fetch('/generate', request)
            .then(res => {
                return res.text()
            })
            .then(content => {});
    }
</script>
Sachin
  • 2,912
  • 16
  • 25
  • This is basically what I've been trying to do, but it doesn't seem to be working. I've updated my post to include my full code, please let me know if you know what the issue is. – Saad Oct 07 '16 at 01:44
  • Basically, when the POST request is sent, nothing happens in my browser. I thought the file would download? – Saad Oct 07 '16 at 01:47
  • @meh_programmer its because the spelling you are using of attachment .this.set('Content-disposition', 'attchment; filename= test.txt'). change "attchment" to "attachment"; :) – Sachin Oct 07 '16 at 03:02
  • Sorry, I made a typo when I was copying my code here. I have it spelled correctly, but it still doesn't seem to do anything. I think the issue might have to do with the fact that I'm getting this file from a POST request and might have to do something else to trigger a download. – Saad Oct 07 '16 at 05:49
  • Actually POST might not be the problem because my code(UPDATE: complete code) is working and downloading file with the post type. – Sachin Oct 07 '16 at 06:11
  • Yup, I'm using your exact code it doesn't seem to trigger a download. Can you share the client side code you're using? – Saad Oct 07 '16 at 06:43
  • It looks like even after disabling my popup blocker this solution didn't work, although Vedran's solution did work. Do you know what else could be the issue? – Saad Oct 10 '16 at 09:16
5

You could try using https://github.com/koajs/send

router.post('/generate', function * (next) {
    yield send(this, 'file.txt');
});

And in client side, you'll need to create and trigger download upon receiving file content via post request. Put this code in request callback

fetch('/generate', request)
  .then(res => { return res.text() })
  .then(content => {

    uriContent = "data:application/octet-stream," + encodeURIComponent(content);

    newWindow = window.open(uriContent, 'somefile');

  });
Vedran Jukic
  • 841
  • 8
  • 14
  • This code doesn't seem to work for me. Do you have a working `gist` by any chance of a simple example? – Saad Oct 07 '16 at 02:03
  • I've created public github repo https://github.com/vedranjukic/downloadkoademo Just clone and npm install. Run the app with node app.js and open chrome localhost:3000 and click on download button to test. I've tried to make it as simple as possible. Let me know if it works for you. – Vedran Jukic Oct 07 '16 at 06:56
  • Wow, I just figured out what the issue was. I had `uBlock Origin` installed as my popup blocker, and it looks like it was preventing the downloads. Sorry about the silly issue, and thanks for the response! – Saad Oct 10 '16 at 09:06
0

same functionality can be achieved using a tag download.I prefer this.It works without JS but not in safari.

<!DOCTYPE html>
<html>
<body>

<p>Click on the w3schools logo to download the image:<p>

<a href="http://www.w3schools.com/images/myw3schoolsimage.jpg" download>
  <img border="0" src="http://www.w3schools.com/images/myw3schoolsimage.jpg" alt="W3Schools" width="104" height="142">
</a>

<p><b>Note:</b> The download attribute is not supported in Edge version 12, IE, Safari or Opera version 12 (and earlier).</p>

</body>
</html>

refernce: http://www.w3schools.com/tags/att_a_download.asp

Sanka
  • 1,294
  • 1
  • 11
  • 20