NodeJS - Send converted file to client side to download - javascript

GOAL: Allow the user to download a PDF
Background: The below code generates a car.pdf file and stores it into the main project's directory when localhost:3000/ is loaded. This is great because I want to find a Car by id in the database, generate a handlebars template, pass the data from Car into it, and generate a PDF from the compiled HTML
Issue: Instead of saving the PDF to the main project's directory, I want it to download to the user's computer.
How can I do this?
Here is my code. I am using the NPM package: html-pdf
helpers/export-helper.js
const fs = require('fs');
const pdf = require('html-pdf');
const Handlebars = require('handlebars');
const { Car } = require('./../models/car');
var writePDF = (res) => {
Car.findById({_id: '58857e256b639400110897af'})
.then((car) => {
var source = fs.readFileSync(__dirname + '/templates/car.handlebars', 'utf8');
var template = Handlebars.compile(source);
var file = template(car);
pdf.create(file, { format: 'Letter' })
.toFile('./car.pdf', (err, res) => {
if (err) return console.log(err);
console.log(res); // { filename: '/app/businesscard.pdf' }
});
})
.catch((errors) => {
console.log(errors);
});
};
module.exports = { writePDF };
routes/home.js
const express = require('express');
const router = express.Router();
const { writePDF } = require('./../helpers/export-helpers');
router.get('/', (req, res) => {
writePDF();
});
module.exports = router;

You should use res.download for this. Like so
router.get('/', (req, res) => {
res.download('car.pdf');
});
https://expressjs.com/en/api.html#res.download

You have to pipe the created pdf with response to client side.

Related

Unexpected end of form error when using Multer

I'm trying to upload an image (jpg/jpeg/png) from the browser to NodeJS. I have read through several tutorials and many posts on forums but very few seem to have this specific issue.
I've made sure to match the name provided to multer (upload.single('upload')) with the formData key (formData.append('upload', selectedFile, selectedFile.name))
I tried using headers originally, but later read that I should exclude them.
I tried submitting through a <form action="/upload" method="post" enctype="multipart/form-data"> but still got the same error.
I have found this similar question with only one answer which isn't clear
Multer gives unexpetcted end of form error and this question Unexpected end of form at Multipart._final which has no answers.
Every other question seems to be about an 'Unexpected field' or 'Unexpected end of multipart data' error which - judging from the solutions - is irrelevant here.
Below is my code...
Browser:
<body>
<input type="file" id="file_uploader" name="upload" />
<button onclick="uploadImage()" class="btn-default">SUBMIT</button>
<!-- OTHER STUFF -->
</body>
<script>
let selectedFile;
let uploadData = new FormData();
const fileInput = document.getElementById('file_uploader');
fileInput.onchange = () => {
selectedFile = fileInput.files[0];
uploadData.append('upload', selectedFile, selectedFile.name);
}
function uploadImage(){
fetch('/upload', {
method: 'POST',
body: uploadData
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error('Error: ', error);
});
}
</script>
NodeJS
let express = require('express');
const multer = require('multer');
//multer options
const upload = multer({
dest: './upload/',
limits: {
fileSize: 1000000,
}
})
const app = express();
app.post('/upload', upload.single('upload'), (req, res) => {
res.send();
}, (error, req, res, next) => {
console.log(error.message);
})
exports.app = functions.https.onRequest(app);
...And here is the error log, if it helps:
Error: Unexpected end of form
> at Multipart._final (C:\Users\p\Downloads\MyInvestmentHub\functions\node_modules\busboy\lib\types\multipart.js:588:17)
> at callFinal (node:internal/streams/writable:694:27)
> at prefinish (node:internal/streams/writable:723:7)
> at finishMaybe (node:internal/streams/writable:733:5)
> at Multipart.Writable.end (node:internal/streams/writable:631:5)
> at onend (node:internal/streams/readable:693:10)
> at processTicksAndRejections (node:internal/process/task_queues:78:11)
I haven't posted many questions as of yet, so I apologise if I'm missing something or the format is off. Let me know and I will make appropriate edits.
Thanks.
I also got the exact same error.
Before using multer I had installed express-fileupload. When I unistalled it using the command npm uninstall express-fileupload I could get rid of the error.
And if it is the same case with you don't forget to delete the commands you already added for express-fileupload module. (like requiring fileupload)
Hi there I ran into the same issue for me was the lack of a bodyParser middleware that allows our requests files to parsed into Buffers.
I was able to resolve the problem like so in express:
var bodyParser = require('body-parser')
bodyParser.json([options])
I had this problem using multer with next js api. What worked for me is, I exported an a config that sets bodyParser to false like so;
export const config = {
api: {
bodyParser: false
}
}
In my case, the cause was other middleware. Check for other middleware running before multer. For me, the issue was express-openapi-validator middleware. Once I removed that middleware, it worked as expected.
Using body-parser package worked for me:
const bodyParser = require('body-parser')
// ...
app.use(bodyParser()) // support encoded bodies
My upload single file route:
const multer = require('multer')
const express = require('express')
const router = express()
const path = require('path') // node built-in path package
// needs "app.use(bodyParser())" middleware to work
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, process.cwd() + '/public/')
},
filename: function (req, file, cb) {
// generate the public name, removing problematic characters
const originalName = encodeURIComponent(path.parse(file.originalname).name).replace(/[^a-zA-Z0-9]/g, '')
const timestamp = Date.now()
const extension = path.extname(file.originalname).toLowerCase()
cb(null, originalName + '_' + timestamp + extension)
}
})
const upload = multer({
storage: storage,
limits: { fileSize: 1 * 1024 * 1024 }, // 1 Mb
fileFilter: (req, file, callback) => {
const acceptableExtensions = ['png', 'jpg', 'jpeg', 'jpg']
if (!(acceptableExtensions.some(extension =>
path.extname(file.originalname).toLowerCase() === `.${extension}`)
)) {
return callback(new Error(`Extension not allowed, accepted extensions are ${acceptableExtensions.join(',')}`))
}
callback(null, true)
}
})
router.post('/api/upload/single', upload.single('file'), (req, res) => {
res.status(200).json(req.file)
})
module.exports = {
uploadRouter: router
}
I think this is may causes by the responsed end,so in your continuous Middleware,you can do upload file at last.
i do this resolve problems.
const upload = multer({
dest: "./uploads",
});
app.use(upload.any());
app.post(
"/upload",
(req, res, next) => {
res.end("文件上传成功");
},
upload.single("fileKey")
);
try using these it work
const express = require('express')
const app = express()
const path = require('path')
const multer = require('multer')
var filestorageEngine = multer.diskStorage({
destination: (req, file, cb) => {
cb(null,'./uploads')
},
filename:(req,file, cb) => {
cb(null,"[maues]-" + file.originalname)
}
})
var upload = multer({
storage:filestorageEngine
})
app.post('/file', upload.array('file', 3),(req, res) => {
console.log(req.file)
res.send("file uploaded successfully")
})
app.listen(5000, ()=> {
console.log("server running")
})
in my frontend or client-side removing the headers in my request. And make sure your inputs are as a formData.
For example:
let formData = new FormData();
formData.append("fileName", file);
const res = await fetch("/api/create-card", {
method: "POST",
body: formData,
})
This worked for me.
I think, the problem is in the express and body-parser module, I just eliminated it
app.use(bodyParser.text({ type: '/' }));
and it works!
Try downgrading Multer to 1.4.3. It worked for me.
See https://github.com/expressjs/multer/issues/1144

How to trigger node fs.writefile from another javascript file

Okay so basicly i have html from which im collecting input text and updating object with it
index.html
<script type="text/javascript" src="script.js"></script>
<span class="fa fa-link fa-fw"></span>
once triggered it is overriding textInput object
script.js - script file linked to index.html
textInput = {
text: '',
};
const exportFile = () => {
var x = document.getElementById("sampleeditor").innerHTML;
textInput = x;
};
now with my updated textInput object i want to pass it to app.js file which will handle write/reading json file. But not sure how i can trigger write/update function from app.js using script.js, if try export functions/modules rest functions used in script.js turning - "declared but value never read"
app.js - my node app file.
const express = require('express')
const port = 3000;
const path = require('path')
const textInput = require('./public/shit')
const app = express()
app.use(express.static('public'));
app.get('/', function(request, response){
response.sendFile(path.join(__dirname + '/index.html'));
})
//write to json
const fs = require('fs');
let data = JSON.stringify(textInput, null, 2);
fs.writeFile('text.json', data, (err) => {
if (err) throw err;
console.log('Data written to file');
});
console.log(textInput);
//read json
fs.readFile('text.json', (err, data) => {
if (err) throw err;
let textInput = JSON.parse(data);
console.log(textInput);
});
console.log('This is after the read call');
////write to json
app.listen(port)

Nodejs : Get zip file from aws s3 url and manipulate files inside it after extracting

I am trying to fetch a zip file uploaded to aws s3. After that file is fetched, I have to extract it and display the names of files inside the folder. How can I achieve this? I am new to file streaming and this is what I have done till now.
import * as aws from "aws-sdk";
import express from "express";
import fs from "fs";
import request from "request";
import * as unzipper from "unzipper";
const config = {
// credentials
};
const s3Client = new aws.S3(config);
const app = express();
app.use(express.json({
limit: "1mb"
}));
app.use(express.urlencoded({
extended: true
}));
app.post("/seturl", async(req, res) => {
try {
const url = req.body.url;
request(url).pipe(fs.createWriteStream('ez.zip'));
console.log("here");
const zip = fs.createReadStream('ez.zip').pipe(unzipper.Parse({
forceStream: true
}));
for await (const entry of zip) {
const fileName = entry.path;
console.log("///////////", fileName);
const type = entry.type; // 'Directory' or 'File'
const size = entry.vars.uncompressedSize; // There is also compressedSize;
if (fileName === "this IS the file I'm looking for") {
entry.pipe(fs.createWriteStream('output/path'));
} else {
entry.autodrain();
}
}
} catch (error) {
return Promise.reject(`Error in reading ${error}`);
}
});
app.listen(5600, (err) => {
if (err) {
console.error(err);
} else {
console.log("running");
}
});
I am using the unzipper library here. If there is something better, I am open to use it. As of now, I am getting FILE ENDED error.

file-type npm module returns null despite being called with buffer, what am I doing wrong?

I'm developing a website where users can upload video files to be stored in MongoDB. Before the files get uploaded and stored, I would like to check and validate the mimetype of the file. I would like to do that with help of a npm module, I have tried without success with file-type.
Link to file-type npm module: https://www.npmjs.com/package/file-type
I call the module with buffer of uploaded files (tested with mp4-files) but it returns null. Here is my code for the upload route:
'use strict';
const router = require('express').Router();
const VideoInfo = require('../../models/VideoInfo');
const VideoAmount = require('../../models/VideoAmount');
const path = require('path');
const Lib = require('../../lib/Lib');
const multer = require('multer');
const GridFsStorage = require('multer-gridfs-storage');
const fileType = require('file-type');
// Defines storage of files with validation
const storage = new GridFsStorage({
url: process.env.dbURL,
file: (req, file) => {
const data = [];
req.on('data', chunk => {
data.push(chunk);
});
req.on('end', () => {
const buffer = Buffer.concat(data);
const fType = fileType(buffer);
return new Promise((resolve, reject) => {
if (fType === null) {
return reject(new Error('Unsupported file format'));
}
if (fType.mime !== 'video/mp4' ||
fType.mime !== 'video/webm' ||
fType.mime !== 'video/ogg') {
return reject(new Error('Unsupported file format'));
}
if (!req.session.username) {
return reject(new Error('Unauthorized file upload attempt'));
}
// changes the file name before storing
const fileName =
Lib.make.randomString() + path.extname(file.originalname);
const fileInfo = {
filename: fileName,
bucketName: 'uploads'
};
resolve(fileInfo);
});
});
}
});
const upload = multer({ storage });
router.route('/upload')
.get((req, res) => {
// renders upload form, not really relevant
})
.post(upload.single('video'), async (req, res) => {
// file gets saved to DB with upload.single-function
});
module.exports = router;
What am I doing wrong?
The problem was that I wasn't getting the video file buffer.
What solved it was including busboy-body-parser in my app:
const busboyBodyParser = require('busboy-body-parser')
app.use(busboyBodyParser({
limit: '120mb'
}))
Then I could get the buffer from the request:
const fileContent = req.files.video
const buffer = fileContent.data
Then I could get the file type of the file by calling file-type with the buffer.

Write file to directory then zip directory

I am trying to write a file to a directory templates then stream a zip with the content that was written. However, the when the zip file is returned it says Failed - Network Error which is due to the fs.writeFile in the controller. If i remove the WriteFile stream then the zipping works fine. My question is how do i first write the file then run the zip. There seems to be something synchronous happening with the archiving and file writing of typeArrayString.
Controller:
exports.download_one_feed = function(req, res, next) {
Feed.findOne({'name': req.params.id})
.exec(function(err, dbfeeds){
if(err){
res.send('get error has occured in routes/feeds.js');
} else {
const feedArray = dbfeeds.feed.data;
// write file
// get from db collection & write file to download
const typeArrayString = JSON.stringify(feedArray);
let type = 'b'; // test only
fs.writeFile(path.join(appDir, 'templates/' + type + '/template.json'), typeArrayString, (err) => {
if (err) throw err;
console.log('Saved!');
})
archiverService.FileArchiver(feedArray, res);
}
})
};
Archive Service
const archiver = require('archiver')
const zip = archiver('zip')
const path = require('path')
const fs = require('fs')
const appDir = path.dirname(require.main.filename)
exports.FileArchiver = function (feedArray, res) {
// const app = this.app;
const uploadsDir = path.join(appDir, '/uploads/');
const templatesDir = path.join(appDir, '/templates/');
const extensions = [".jpg", ".png", ".svg"];
let imageArray = [];
const feedArrayObject = JSON.parse(feedArrayString);
feedArrayObject.forEach(function(x){iterate(x)}); // grab image names from object
imageArray = uniq_fast(imageArray); // remove duplicates
// zip images
for (let i = 0; i < imageArray.length; i++) {
console.log(imageArray[i])
const filePath = path.join(uploadsDir, imageArray[i]);
zip.append(fs.createReadStream(filePath), { name: 'images/'+imageArray[i] });
}
res.attachment('download.zip');
zip.pipe(res);
// zip template directory
console.log(templatesDir)
zip.directory(templatesDir, false);
zip.on('error', (err) => { throw err; });
zip.finalize();
return this;
}
Instead of writing the file then zipping the directory, i used zip.append to override the old file in the directory.

Categories