I am a beginner in the JavaScript and NodeJS space. I was trying to create a simple file upload project with NodeJS. Furthermore, I created the routes in NodeJS and capturing the image from the webpage and sending it to the NodeJS routes using fetch.
This is the HTML file:
<div class="file-upload">
<div class="image-upload-wrap">
<input class="file-upload-input" type='file' onchange="readURL(this);" accept="image/*" />
<div class="drag-text">
<h3>Drag and drop a file or select add Image</h3>
</div>
</div>
<div class="file-upload-content">
<div class="file-upload-display">
<img id="file-upload-image" class="file-upload-image" src="#" alt="your image" />
<div class="image-title-wrap">
<button type="button" onclick="removeUpload()" class="remove-image"><i class="fas fa-trash-alt"></i> Remove <span class="image-title">Uploaded Image</span></button>
</div>
</div>
</div>
</div>
<br>
<div class="file-upload-server d-flex justify-content-center">
<button class="btn btn-expand-lg btn-primary" onclick="uploadFile()"><i class="fas fa-cloud-upload-alt"></i> Upload</button>
</div>
This is my JavaScript file:
async function uploadFile() {
let img = document.getElementById('file-upload-image').src;
console.log('Image String Length: ' + img.length);
const payload = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
file: img,
})
}
console.log(`Payload: ${JSON.stringify(payload)}`);
await fetch('http://localhost:3030/image/uploadimage', payload)
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
The image here is the base64 encoded string and that I passing to my nodejs route.
When I click my upload button I get the following error:
I have tested this code with an image of size 5.72kb and it works. But when I try to upload an image of size 81.7kb it fails with that error.
This is the nodejs route:
router.use(imageupload({
limits: { fileSize: 50 * 1024 * 1024 },
}));
router.use(express.urlencoded({limit: '50mb', extended: true}));
router.use(express.json());
const decodeBase64Image = (dataString) => {
let matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
response = {};
if (matches.length !== 3) {
return new Error('Invalid input string');
}
response.type = matches[1];
response.data = Buffer.from(matches[2], 'base64');
return response;
}
router.post('/uploadimage', cors(corsOptions), async (req, res) => {
let decodedImage = decodeBase64Image(req.body.file);
let imageBuffer = decodedImage.data;
let type = decodedImage.type;
let extension = mime.getExtension(type);
let NewImageName = Math.random().toString(36).substring(7);
let fileName = 'image-' + NewImageName + '.' + extension;
try {
fs.writeFile(`${path.join(__dirname, '../')}/public/uploads/${fileName}`,
imageBuffer, function(err) {
if(err) throw err;
console.log('The file was uploaded successfully');
return res.status(200).json({status: 'success', message: 'File uploaded successfully'});
});
} catch(err) {
console.log(err);
return res.status(500).send(err);
}
});
Any help or guidance around this would be great.
You need to set an upload limit for your express.json() middleware since you are using 'Content-Type': 'application/json' header in the post request.
Try replacing router.use(express.json()) with router.use(express.json({ limit: '50mb'})
Documentation
Edit
The error Unexpected token < in JSON at position 0' happens if you try to parse a string with response.json() which is not formatted as json. You can reproduce it in a one liner in nodejs as JSON.parse('I am no json...')
If you call response.json() on the client , it has to be properly formatted json or it result in an error. For doing so, use res.json() instead of res.send().
You should also make sure to always respond to the client, so change the line if(err) throw err; to
if(err) {
console.error(err)
// always send back a status, 500 means server error..
res.status(500).json('Internal server error')
return
}
I would suggest that you should use multer here's a link
Backend Code Sample:
var multer = require('multer')
var signature = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/')
},
filename: function (req, file, cb) {
if (file.mimetype == 'image/jpeg' || file.mimetype === 'image/jpg' || file.mimetype === 'image/png' || file.mimetype === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
cb(null, Date.now() + file.originalname)
} else {
cb({ error: 'Image type not supported' })
}
}
})
var uploadSignature = multer({ storage: signature })
router.put('/updateWorkOrderForSign',uploadSignature.array('signature', 1), function (req, res) {
try {
if (req.body.signature) {
var base64Data = req.body.signature.replace(/^data:image\/png;base64,/, "");
let rString = Math.random().toString(36).substring(7);
let date = Date.now();
let imageName = '/' + date + rString + '.png';
let imagePath = '/public' + imageName;
require("fs").writeFile('./' + imagePath, base64Data, 'base64', function (err) {
if (err) {
log.error('SAVE IMAGE FAILED WITH ERROR: ', err);
} else {
log.info('Signature image saved successfully')
}
});
} else {
//nothing to do
log.error("No signature image present")
}
} catch (err) {
log.error('ERROR WHILE CONVERTING BASE64 TO IMAGE: ', err);
}
})
Front-end code sample(I have implemented this in react native):
async function uploadFile() {
let imageObject = {
uri: image.path,
type: 'image/png',
name: 'signatureImage'
}
var form = new FormData();
form.append('signature', imageObject)
form.append(‘task’Id, taskId);
await fetch(urlStr, {
method: 'PUT',
headers: {
'Authorization': bearerToken,
Accept: 'application/json',
'Content-Type': 'multipart/form-data'},
body: params,
})
.then(response => response.json())
.then(responseData => {
var result = JSON.stringify(responseData);
return result;
})
.catch(error => {
console.log('ERROR WHILE UPLOADING IMAGE: ',error)
});
}
Related
I am getting blob of pdf file from other server which is on C# in node server when I am converting it to array buffer it's size is different from C# array buffer size and when I am opening it to browser it gives error "Failed to load PDF document."
This is my server side code
async post(param: any, body: any, endPoint: string, queryParams: string = '')
{
try
{
if(queryParams == '') queryParams = param.destinationSlug + '/' + param.mainBranchId;
const url = splendidAccountsEndPointsEnum.baseUrl + queryParams + endPoint;
const offset = new Date().getTimezoneOffset().toString();
const headerConfig = { headers: { 'X-Api-Key': param.apiKey, 'X-Api-Secret': param.apiSecret, 'X-App-Id': config.get<string>("splendidXAppId"), LocalDateTimeOffset: offset }}
const response = await axios.post(url, body, headerConfig)
.then(function (response: any)
{
return response?.data;
})
.catch(function (error: any)
{
console.log(error);
return error;
});
if (response?.status === 200)
{
console.log('success');
return response?.data;
}
return response;
}
catch (err)
{
console.error(err);
console.log(err);
return err;
}
}
And this is my client side
let response = this.orderService.generateInvoicePrinting(this.selectedRows)
.subscribe((res) => {
var file = new Blob([res], {type: 'application/pdf'});
var fileURL = URL.createObjectURL(file);
saveAs(file, "invoices.pdf");
window.open(fileURL);
this.pdfSrc = fileURL;
this.showPdf = true;
this.showSuccess('Invoices have been generated successfully.');
this.selectedRows = [];
},
error => {
this.showError('Invoices have not been generated successfully.');
});
generateInvoicePrinting(orders: any): Observable<any>
{
const url = `${environment.apiUrl}api/Order/printInvoices/pdf`;
return this.http.post(url, orders, { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), observe: 'response', responseType: 'blob' }).pipe(
map(res => res.body),
);
}
Can any one please help me out on how to download the pdf files when reactjs(using context api) is used as frontend(using axios),as on server side every thing is fine , if i click on download link - following response is shown in console Click here to see image
this is the client side code making req:
const pdfFileDownload = async (dlIdInfo) => {
const { id } = dlIdInfo;
console.log(`pdf file id`, id);
try {
const res = await axios({
method: "GET",
url: `/pub/pdfdload/${id}`,
responseType: 'blob',
});
console.log(res.data);
} catch (err) {
console.log(err.response);
}
}
this is server side code:
router.get("/pdfdload/:id", async (req, res, next) => {
const fileId = req.params.id;
try {
const fetchedFileToDl = await FILESDB.findById(fileId);
const fileArr = fetchedFileToDl.pdfFiles;
fileArr.map(filename => {
const editedFileName = filename.split("/")[1];
const filePath = path.join(__dirname, "../", "public", editedFileName);
const files = fs.createReadStream(filePath);
res.setHeader("Content-Type", "application/pdf");
res.setHeader('Content-Disposition', 'inline; filename="' + editedFileName + '"');
files.pipe(res);
});
} catch (err) {
console.log(err);
}});
Here is how I usually do:
const downloadFileAsPDF = (name, data) => {
const url = window.URL.createObjectURL(new Blob([res.data], {type: `application/pdf`}));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', name);
link.click();
window.URL.revokeObjectURL(url);
}
const pdfFileDownload = async (dlIdInfo) => {
const { id } = dlIdInfo;
console.log(`pdf file id`, id);
try {
const res = await axios({
method: "GET",
url: `/pub/pdfdload/${id}`,
responseType: 'blob',
});
downloadFileAsPDF('file.pdf', res.data);
} catch (err) {
console.log(err.response);
}
}
It's really simple but it get the job done.
So i am sending a file from one server to another using Axios, one is an app backend and the other is a blockchain server.
Where i am sending the file :
router.post("/acme/:id", auth, async (req, res) => {
var formData = new FormData();
console.log(req.files.file)
formData.append("image", req.files.file.data);
var Response;
try {
Response = await axios.post(BC_SERVER + "acmeDataFileUpload", {
id: req.params.id,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
} catch (error) {
console.log("Error BlockChain");
}
try {
res.status(201).send("ok");
} catch (e) {
res.status(500).send(e);
}
});
Where Axios is sending it :
app.post('/acmeDataFileUpload', async (req, res) => {
const id_owner = req.body.id;
console.log(req.body)
const file = req.body.data;
const fileName = id_owner;
const filePath = 'files/' + fileName;
console.log(fileName);
file.mv(filePath, async (err) => {
try {
const fileHash = await addFile(fileName, filePath);
fs.unlink(filePath, (err) => {
if (err) console.log(err);
});
const json = '{"dataType": "Object" , "local": "'+localServer+fileHash+'",' +'"ipfsServer": "'+ipfsServer+fileHash+'"}';
console.log(json);
const obj = JSON.parse(json);
res.status(201).send(obj);
} catch (err) {
console.log('Error : failed to download file');
console.log(err);
return res.status(500).send(err);
}
});
});
and this is what the log of req.body is like :
{
id: '5ec2b7d47ae93a49ecb773f6',
data: {
_overheadLength: 144,
_valueLength: 579564,
_valuesToMeasure: [],
writable: false,
readable: true,
dataSize: 0,
maxDataSize: 2097152,
pauseStreams: true,
_released: false,
_streams: [
'----------------------------383350625990492694059785\r\n' +
'Content-Disposition: form-data; name="image"\r\n' +
'Content-Type: application/octet-stream\r\n' +
'\r\n',
[Object],
null
],
_currentStream: null,
_insideLoop: false,
_pendingNext: false,
_boundary: '--------------------------383350625990492694059785'
},
headers: { 'Content-Type': 'multipart/form-data' }
}
Basically i am sending the buffer here, since FormData doesn't accept a file and tells me source.on is not a function i would rather my image is send to req.files instead of req.body, Axios is really confusing me.
headers: formData.getHeaders()
I previously had a single file upload set up and working properly. Now I need to make it handle multiple files.
Here is my code right now:
const multer = require('multer')
const { Storage } = require('#google-cloud/storage')
const storage = new Storage()
const m = multer({ storage: multer.memoryStorage() })
module.exports = app => {
app.use('/', router)
router.post(
'/reader-:shortId/file-upload',
passport.authenticate('jwt', { session: false }),
m.array('files'),
async function (req, res) {
const bucketName = req.params.shortId.toLowerCase()
await storage.createBucket(bucketName)
bucket = storage.bucket(bucketName)
let promises = []
req.files.forEach((file) => {
const blob = bucket.file(file.originalname)
const newPromise = new Promise((resolve, reject) => {
blob.createWriteStream({
metadata: { contentType: file.mimetype }
}).on('finish', async response => {
await blob.makePublic()
resolve(response)
}).on('error', err => {
reject('upload error: ', err)
}).end()
})
promises.push(newPromise)
})
Promise.all(promises).then((response) => {
// the response I get here is [undefined, undefined]
res.status(200).send(response)
}).catch((err) => {
res.status(400).send(err.message)
});
})
}
req.files does give me an array of files, with a buffer and a size that makes sense.
The promises all resolve.
But once I check the files in the google bucket, they have the right name but don't have any content (and size of 0)
As I said before, it was working when I was doing it with one file (using m.single('file')
I don't want to use the bucket as the destination with multer setup because I also have to change the file name before uploading to google bucket.
edit: this is the code example given by google cloud documentations for single file uploads (https://cloud.google.com/nodejs/getting-started/using-cloud-storage):
function sendUploadToGCS (req, res, next) {
if (!req.file) {
return next();
}
const gcsname = Date.now() + req.file.originalname;
const file = bucket.file(gcsname);
const stream = file.createWriteStream({
metadata: {
contentType: req.file.mimetype
},
resumable: false
});
stream.on('error', (err) => {
req.file.cloudStorageError = err;
next(err);
});
stream.on('finish', () => {
req.file.cloudStorageObject = gcsname;
file.makePublic().then(() => {
req.file.cloudStoragePublicUrl = getPublicUrl(gcsname);
next();
});
});
stream.end(req.file.buffer);
}
I originally had something like that working, but I just don't understand where it is getting the file buffer data from. That is probably where things are different with multiple files.
I know its too late, but someone might looking an answer for uploading multiple files on Google Cloud Storage.
Dependencies:
Express
Google Cloud Library
Multer
Body Parser
This is the controller code.
exports.post_image_upload = async (req, res) => {
/** Check if file exist */
if (!req.files) {
res.status(400).send('No file uploaded.');
return;
}
let PublicUrls = []
req.files.forEach((file) => {
const blob = bucket.file(file.fieldname + '-' + Date.now() + path.extname(file.originalname))
const blobStream = blob.createWriteStream({
metadata: { contentType: file.mimetype }
})
blobStream.on('finish', ()=> {
blob.makePublic()
})
blobStream.on('error', err => {
//Put your error message here
})
blobStream.end(file.buffer)
const Url = `https://storage.googleapis.com/${bucket.name}/${blob.name}`
PublicUrls.push(Url)
})
res.send(PublicUrls)
}
Good Luck
Ok, turns out I had to change
.end()
to
.end(file.buffer)
Marie Pelletier, I think your approach is 100% right. I modified a little your code trying to avoid the async response:
let promises = []
req.files.forEach((file) => {
const blob = bucket.file(file.originalname)
const newPromise = new Promise((resolve, reject) => {
blob.createWriteStream({
metadata: { contentType: file.mimetype },
resumable: false //Good for small files
}).on('finish', () => {
const Url = `https://storage.googleapis.com/${bucket.name}/${blob.name}`;
resolve({ name: file.originalname, url: Url });
}).on('error', err => {
reject('upload error: ', err);
}).end(file.buffer);
})
promises.push(newPromise);
})
Promise.all(promises).then((response) => {
res.status(200).send(response)
}).catch((err) => {
res.status(400).send(err.message)
});
This way, I didn't get 'undefined' anymore.
My Goal is to upload an image taken from a webcam to a Lambda function which then uploads it to AWS S3.
The lambda function seems to work when I test it, however I can't work out what exactly needs to be sent through from the React Camera.
Or if I am sending through the right format to upload it.
import Camera from 'react-camera';
..
This is the JSX
<Camera
ref={(cam) => {
this.camera = cam;
}}
>
<Button onClick={this.takePicture}>
<i className="fas fa-camera"></i> Take photo
</Button>
</Camera>
This is the react code that is called when they take the photo
takePicture = () => {
this.camera.capture()
.then(blob => {
console.log(blob);
this.props.dispatch(uploadImage(blob))
})
}
The uploadImage function in my action is:
export const uploadImage = (fileObj) => dispatch => {
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'image/jpeg'
},
body: fileObj
})
.then((response) => response.json())
.then(function (response) {
if (response.status === 'success') {
console.log(response);
// ... Show feedback
return response
} else {
// ... Show feedback
}
})
.catch((error) => {
console.error(error)
});
}
I figure I need to upload a base64 image..?
I don't understand how I get that from the blob
Here is the Lambda Function code for reference:
var params = {
Bucket: 'bucketName',
Key: Date.now() + '.jpg',
ContentType: 'image/jpeg',
Body: event.body,
ACL: "public-read"
};
return uploading = new Promise(function (resolve, reject) {
return s3.upload(params, function (err, data) {
if(err) {
state.uploadError = err
return reject({
error: err,
status: 'error',
message: 'something went wrong'
})
}
state.uploadData = data
state.fileLocation = data.Location
state.status = "success"
state.message = "File has been uploaded to the fileLocation"
return resolve(data)
});
})
Question:
How do I make the format of the blob correct so that when it's POSTed though as the body it will be the correct image format?
Very well organized question and code... thank you!
Updated:
Use the filesystem module to read the file and specify the encoding.
const fs = require('fs');
takePicture = () => {
this.camera.capture()
.then(blob => {
console.log(blob);
const image = fs.readFileSync(blob.path);
const b64Image = Buffer.from(image).toString('base64');
this.props.dispatch(uploadImage(b64image));
})
}