My JavaScript application allows users to download a file. The page shows a clickable link to the file. The file is stored in MongoDB. When I click on the link to download the file, I'm getting "Failed - File incomplete" in Chrome. The file download should show up in my downloads file, but it doesn't. I don't know what is wrong and I hope someone here can shed some light on the problem.
This app is built using node/express/ejs/MongoDB
Here's the router code:
router.get('/:id/download', async (req, res) => {
try {
const bug = await Bug.findById(req.params.id)
let buf = Buffer.from(bug.files)
res.writeHead(200, {
'Content-Disposition': `attachment; filename="${bug.fileName}"`,
'Content-Type': bug.fileType,
'Content-Length': buf.length,
}).end()
fs.writeFile(bug.fileName, buf, (err) => {
if (err) {
console.log(err);
}
else {
console.log("File written successfully\n");
}
})
} catch (err) {
console.log("error downloading file", err)
}
res.end()
})
Here's the HTML:
<div >
<div >
<div ></div>
<div >
<h2>Bug Details</h2>
<p>Title: <%= bug.title %></p>
<p>Description: <%= bug.description %></p>
<p>Category: <%= bug.category %></p>
<p>Status: <%= bug.status %></p>
<p>Priority: <%= bug.priority %></p>
<p>Supporting Documents: </p>
<a href="/<%= bug.id %>/download"><%= bug.fileName %></a>
<div>
<a href="/<%= bug.id %>/edit">Edit</a>
<form method="POST" action="/<%= bug.id %>?_method=DELETE">
<button type="submit">Delete</button>
</form>
</div>
</div>
<div ></div>
</div>
</div>
In MongoDB the file is stored as part of a document:
files: {
type: Buffer,
required: false
},
fileName: {
type: String,
required: false
},
fileType: {
type: String,
required: false
},
fileSize: {
type: Number,
required: false
}
When I test using a dummy .txt file, Chrome dev tools shows the response header as:
HTTP/1.1 200 OK X-Powered-By: Express Content-Disposition: attachment; filename="dummyfile.txt" Content-Type: text/plain Content-Length: 9 Date: Fri, 06 May 2022 20:19:01 GMT Connection: keep-alive Keep-Alive: timeout=5
One interesting side note: The file does download into my projects folder and everything is fine, but it doesn't download to my /downloads folder as it should.
CodePudding user response:
You should use res.download
method which express provides, no need to set any headers.
Also, I'm using fs promises module for async await syntax
const fs = require("fs").promises;
router.get("/:id/download", async (req, res) => {
try {
const bug = await Bug.findById(req.params.id);
let buf = Buffer.from(bug.files);
await fs.writeFile(bug.fileName,buf);
res.download(bug.fileName, err => {
if (err) {
throw err;
} else {
// If download is complete
if (res.headersSent) {
// if you want to delete the file which was created locally after download is complete
fs.rm(bug.fileName);
}
}
});
} catch (err) {
console.log("error downloading file", err);
}
});