Upload PDF file to Express server - node.js

I've built a basic browser form allowing users to upload a PDF file. I then want to send that file to an Express backend. It seems like this should be a pretty basic action, but I'm unfamiliar with the end-to-end process so I'm not sure which piece is failing. I've searched through a number of SO questions/answers, but haven't found any that provide a complete solution, and I haven't been able to cobble together a solution either.
Update: It looks like the file is getting to the server, but the encoding is messed up. My guess is that FileReader.readAsText is the wrong method to use. FileReader.readAsBinaryString got me a little closer, but still not quite right (and it's deprecated). FileReader.readAsArrayBuffer seems like potentially the way to go, but I'm not sure how to correctly handle the buffer in Express.
Client/Browser
The form is built in React and just uses an onChange handler on the input itself. When a file has been added, the handler reads the file, adds it to the form data and sends the post request to the server.
// React form
<input
name="upload"
onChange={this._handleUpload}
type="file"
/>
_handleUpload = (e) => {
const { files, name } = e.target;
// Read the file
const reader = new FileReader();
reader.onload = (e) => {
const file = e.target.result;
// Now that we have the file's contents, append to the form data.
const formData = new FormData();
formData.append('file', file);
formData.append('type', name);
axios
.post('/upload', formData)
.then(res => {
// Handle the response...
})
.catch(err => console.log(err));
};
// Reading as text. Should this be something else?
reader.readAsText(files[0]);
}
Express App
The express app uses multer middleware to process the upload:
const app = express();
const upload = multer({});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.post('/upload', upload.any(), handleUpload);
Middleware
Finally, I have my own middleware that gets the file from multer. I'm testing this piece by just writing the file I've received to disk. It has contents, but it's not a readable PDF file.
const handleUpload = (req, res, next) => {
// The file shows up on req.body instead of req.file, per multer docs.
const { file } = req.body;
// File is written, but it's not a readable PDF.
const tmp = fs.writeFileSync(
path.join(__dirname, './test.pdf'),
file,
);
}
Is there some piece that I'm getting obviously wrong here? eg: Do PDFs need to be handled in a special way? Any tips for where to focus my debugging?

See if that solves your problem.
_handleUpload = (e) => {
const dataForm = new FormData();
dataForm.append('file', e.target.files[0]);
axios
.post('http://localhost:4000/test', dataForm)
.then(res => {
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<input
onChange={this._handleUpload}
type="file"
/>
</div>
)
}
server:
router.post('/test', upload.any(), (req, res) => {
console.log(req.files)
res.send({sucess: true})
})
No need to send the file type, the multer identifies the name and type for you.

Related

How to solve Multer Error: Unexpected end of form?

I found other similar questions regarding Multer, but with no answers. I'm trying to upload a file with next.js (front-end) and node.js (back-end). The data is being posted via the network tab when using dev tools.
Below is my setup:
app.js
const express = require('express');
const bodyParser = require('body-parser');
// Get routes
const routeUser = require('./routes/user');
// Create an express server
var app = express();
// Add necessary middleware
app.use(bodyParser.urlencoded({ extended: true })); // Support encoded bodies
app.use(bodyParser.json({
type: ["application/x-www-form-urlencoded", "application/json"], // Support json encoded bodies
}));
// Custom routes
routeUser(app);
// Start server on port 1234
app.listen(1234, () => {
console.log("API is running.");
});
route/user.js
const multer = require('multer');
module.exports = function(app) {
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./user_photos");
},
filename: (req, file, cb) => {
cb(null, file.originalname)
}
})
});
app.post('/user/update', upload.single('user_photo'), (req, res) => {
console.log(req.body, req.file);
});
}
Form
submit(event) {
event.preventDefault();
let form_values = new FormData(event.target);
fetch(this.props.env.api + "/user/update", {
method: 'post',
headers: {
'Content-Type': 'multipart/form-data; boundary=MyBoundary',
},
body: form_values
}).then((response) => {
return response.json();
}).then((response) => {
console.log(response);
});
}
------
<form onSubmit={this.submit}>
<input type="file" name="user_photo"/>
<button type="submit">Upload</button>
</form>
I've tried several tutorials, I'm setting it up according to the docs, yet I keep getting the following error:
Error: Unexpected end of form
at Multipart._final (...\node_modules\busboy\lib\types\multipart.js:588:17)
If I remove multipart/form-data as Content-Type, the form submits with no problem, but with no file. My guess it has something to do with the way the formData object is being received.
Hey #SReca, just faced this issue today and hope I can help you out here and anybody else reading this.
Resolving Unexpected end of form
Regarding the original issue about Unexpected end of form, you are correct in removing the Content-Type as a solution. This is because sending FormData() with regular Fetch or XMLHttpRequest will automatically have the header set by the Browser. The Browser will also attach the boundary needed in all multipart/form-data requests in order indicate when a form starts and ends. More details can be found on MDN's docs about Using FormData Objects... there's a warning about explicitly setting Content-Type.
Potential fix for missing file
Now, about the missing file, it's possible that it has an incorrect reference to the path you're expecting. I am not using diskStorage, but I am using regular dest option to save my file and had the same problem (wanted to save images to ./assets/images). What worked for me was to supply the complete directory path. Maybe changing your callback function in the destination property to
// Assuming the the path module is 'required' or 'imported' beforehand
cb(null, path.resolve(__dirname, '<path-to-desired-folder>'));
will solve the issue. Hope this helps!
What caused this too me was because I was using multiple middleware on the same route, I was using global middleware and then applied another middleware in sub route. so this caused conflits.
In my case the problem magically went away by downgrading Multer to 1.4.3.
See https://github.com/expressjs/multer/issues/1144

How to make nodejs server act like a proxy and get img from cloudinary then send it to browser

for storage space issues i cannot save images in server so i had to store it in cloudinary
and for seo purposes I had to serve it from my domain not cloudinary's
so i thought to get img files from cloudinary and send it directly to browser (to be served from my domain name )
what i am missing is converting the img file i got from cloudinary api into the right form so i can send it using response object in nodejs
here is the code
app.get('/uploads/img/:imgName', (req, res) => {
axios.get('https://res.cloudinary.com/dkhccaa25/image/upload/blog_img/${req.params.imgName}')
.then(response => {
console.log(response);
/* how to convert response into the right format so it can be sent */
//
//
//
})
.then (response => {
/*converted response */
res.sendFile(response)
})
.catch(error => {
console.log(error);
});
how I can be able to send the file from node server to browser so it can be displayed using
<img src="img url...">
You do not have to use res.sendFile, this will require saving it to the filesystem. Basically - accept the response and pass it directly with the correct content-type header send by the upstream response to the client.
Minimal example:
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/', (req, res) => {
axios.get('https://static.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg').then((axiosResp) => {
res.header('content-type', axiosResp.headers['content-type']).send(axiosResp.data);
});
});
app.listen(3000);
finally the problem solved by editing on #madflow answer (thanks for him )
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/', (req, res) => {
axios.get('https://static.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg', {responseType: 'stream'})
.then((axiosResp) => {
res.set({
'Content-Type': axiosResp.headers['content-type']
})
axiosResp.data.pipe(res)
});
});
app.listen(3000);

How to get uploaded image in react by url?

I want to realize uploading files for my users. I use CKEDITOR 5 in my react project. Back-end on nodeJS.
So, i can upload file, can get its Url, but, can't display one in VIEW page.
//my server code
const express = require('express');
//for uploading i use this module
const multiparty = require('connect-multiparty');
const multipartyMiddleware = multiparty({uploadDir: '/var/www/group0384.ru/public_html/server/uploads'}) //here is whole path to my upload folder on server
const app = express();
const port = 3555;
const path = require('path');
const moment = require('moment');
const fs = require('fs');
//so, here i have route /upload, which is indicated in configuration of ckeditor as route to send pictures
app.use(express.static("uploaded"));
app.post('/upload', multipartyMiddleware, (req, res) => {
var TempFile = req.files.upload;
var TempPathfile = TempFile.path;
const targetPathUrl = path.join(__dirname,"./uploaded/"+TempFile.name);
if(path.extname(TempFile.originalFilename).toLowerCase() === ".png" || ".jpg"){
fs.rename(TempPathfile, targetPathUrl, err =>{
res.status(200).json({
uploaded: true,
url: `${__dirname}/uploaded/${TempFile.originalFilename}`
}); // this path is the same as in 5th row (except folder, here it change, but it's no matter)
if(err) return console.log(err);
})
}
})
//------------CKEDITOR CODE---//
<CKEditor
editor={ClassicEditor}
data={this.state.data}
onChange={(event, editor) => {
this.setState({
data: editor.getData(),
});
}}
config={
{
ckfinder: {
uploadUrl: '/upload'
} // here, /upload - the route to send pictures
}
}
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
On my VIEW page, i getting this
screenshot
So, i've tried to change paths, but still couldn't get the picture.
please explain why I can't just get and output a file that is already uploaded on my own server
P.S. Sorry for my english
It seems from the screenshot that you are getting the absolute path to the image, if you want to show the image on the client-side and you are sure the image is saved on your server, you have to send it back as a public URL address of your image!
example: "http://example.com/images/image1.png"
Thank you all for answers, i resolved the problem.
In this part i change url for uploaded images
res.status(200).json({
uploaded: true,
url: `/files/${TempFile.originalFilename}`
});
Then, i created route with this url
app.get('/files/:url(*)', (req, res) => {
console.log(req.params.url)
res.sendFile(path.resolve(__dirname + '/uploaded/' + req.params.url))
})
And it works!

Is there any way to accept binary data type like pdf in serverless?

I am facing the issue like the following:
When uploading a pdf file in vue.js to serverless node.js application, file content is broken.
Because the serverless parses binary data type incorrectly, it happens the issue.
How can I accept binary data type like pdf correctly or other method to solve the issue?
// Vue.js
let formData = new FormData();
formData.append('file', fileObj);
axios.post(API_ENDPOINT + '/upload', formData).then(resp => {
console.log(resp);
})
// Serverless Express
const express = require('express');
const app = express();
const fileUpload = require('express-fileupload');
app.use(fileUpload());
app.post('/upload', (req, res) => {
console.log(req.files.file) // Uploaded tmp file - It has broken content
});
Currently I am using multiparty. I will provide the middleware in the following snippet of code
const { Form } = require('multiparty')
function formDataParser(req, res, next) {
if (!req.is('multipart/form-data')) {
next();
return;
}
const form = new Form();
form.parse(req, (err, fields, files) => {
if (err) {
res.status(400).json({
message: 'Could not parse multipart form.'
});
return;
}
const fieldsKeys = Object.keys(fields);
for (const fieldKey of fieldsKeys) {
fields[fieldKey] = fields[fieldKey][0];
}
req.form = {
fields,
files
}
next();
});
}
module.exports = formDataParser;
I suggest not to attach this middleware as a global one but instead use it only on specific routes that need it. With the provided solution you can access form fields in the following way:
req.form.fields.somefield

Downloading a file from a Node.JS API REST (express) with React.JS (Axios Get)

I have a React JS application that as a Backend has an API REST made with Node JS.
Currently, my objective is to be able to download files that are on the server.
The correct behavior should be that the user, after clicking on "Download file", should receive the file (Download with browser).
On the server-side, I have something like this (obviously, I'm gonna simplify it by removing JWT middleware, DB queries, etc..):
const express = require('express');
const router = express.Router();
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app.use(cors({ origin: "http://localhost:3000" }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
router.get('/download', (req, res, next) => {
res.download("\\\\folder\\subfolder\\myfile.txt");
});
app.use('/api', router);
const PORT = 3001;
app.listen(PORT, function() {
console.log("Server is running on port "+PORT);
});
Then, as I have said, I have a React JS application working as a Frontend:
apihelperdownload () {
return axios.get(API_URL + "download").then(function (response) {
return response;
})
}
.............
function downloadFile() {
apihelperdownload().then(
(res) => {
// Should I do something here with response?
},
(error) => {
}
)
}
<button className="download" onClick={() => downloadFile()}>
Download File
</button>
I have two files on my server, one TXT and one JPG.
Both have the correct path (I'm not getting any "file/path not found" error) and I am receiving a "200 status OK" in both cases... But I can't download the files.
Also:
In the JPG case, in Network Tab, on preview sub-Tab I can see the image (so the browser is receiving the image).
And the response looks like this:
(ignore the params and the different url, it's just that here is not simplified)
- In the TXT case, in Network Tab, on preview sub-Tab I can just see a white page.
And the response looks like this:
As you can see, in this second case (.txt file), the data is "empty" ( "" )
Data is the correct text.. I didn't save the txt file.. So it was empty..
I have checked several related questions like this Download a file from NodeJS Server using Express
But unfortunately, I haven't found how to solve my issue.
1) What am I doing wrong on the server-side?
2) What I have to do with the response on client-side?
Thanks
I have found how to solve it without third-party libraries and in quite an "easy way".
First of all, I have changed the request to POST (since I just made GET because I thought it was the only way).
After that, on the Axios request, we have to indicate the responseType as blob:
function apihelperdownload () {
return axios.post(API_URL + "download",{ key: 'value', headers: authHeader(), responseType: 'blob' }).then(function (response) {
return response;
})
}
Then, when we receive the response, we have to create an URL object as a Blob and a link element to download it.
function downloadFile(filename) {
apihelperdownload().then(
(res) => {
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
if (typeof window.navigator.msSaveBlob === 'function') {
window.navigator.msSaveBlob(
res.data,
filename
);
} else {
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
}
},
(error) => {
alert("Something went wrong");
}
)
}
With this, we can download almost any kind of file very easily.
You can use js-file-download module.
const FileDownload = require('js-file-download');
Axios.get(API_URL + "download")
.then((response) => {
FileDownload(response.data, 'file.txt');
});
Check this response for more: https://stackoverflow.com/a/41940307/6512445

Resources