POSTing from Angular to Node. Getting empty body on node - node.js

Im posting two fields in Angular to a NodeJs endpoint.
I usually post on the body and everything is perfect at Node, but this time, I have to post a form to upload a file.
So this is my code for posting the form data (Angular side):
var APIURL = sessionStorage.getItem('endPoint') + "profile/updateeocoverage";
let formData = new FormData();
formData.append("data", JSON.stringify(this.payLoad));
if (this.eofile) {
formData.append("myfile", this.eofile, this.eofile.name);
}
this.httpClient.post(APIURL, formData).subscribe(
result => {
....
My problem is that I always retrieved the body at node as follows:
router.post('/updateeocoverage', async (req, res, next) => {
console.log(req.body)
return;
....
But with the method Im using now in Angular, req.body is retrieving {}
Is the POST wrong, or the router wrong at Node side?
Thanks.
UPDATE ON PABLO'S ANSWER BELOW (SOLUTION PROVIDED) for whoever runs into this issue:
Using Multer solved the problem, but as he said some workaround is needed as to set the file name, but most important it is required to authenticate the user, so:
const multer = require('multer')
const path = require('path')
To authenticate the user, I send the authentication parameters on the header. Sending it as formdata.append didn't work for me. This sets true or false to upload the file, otherwise anyone can upload anything to the route:
async function authenticateUser(req, file, cb) {
let tempcred = JSON.parse(req.headers.data)
let credentials = tempcred.credentials;
let userData = await utils.isValidUser((credentials), false);
if (userData.isValid == false) {
cb(null, false)
return;
}
else {
cb(null, true)
}
}
Then, since Multer uploads the file with a random name, and I need to save it with the user ID name and the file extension, I do the following:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/eofiles/')
},
filename: async function (req, file, cb) {
let tempcred = JSON.parse(req.headers.data)
let credentials = tempcred.credentials;
let userid = await utils.decrypt(credentials.userid, process.env.SECRET);
cb(null, userid + path.extname(file.originalname))
}
});
Finally, I declare the upload variable for using it with Multer:
var upload = multer({ storage: storage, fileFilter: authenticateUser })
And set the router:
router.post('/updateeofile', upload.single("myfile"), async (req, res, next) => {
let filename = req.file.filename //gets the file name
...
...
do my stuff, save on database, etc
...
...
});
For the record, "myfile" is the input file id.
And this is how I upload the file from Angular:
var APIURL = sessionStorage.getItem('endPoint') + "eoset/updateeofile";
const httpOptions = {
headers: new HttpHeaders({
'data': `${JSON.stringify(this.payLoad)}`
})
};
let formData = new FormData();
if (this.eofile) {
formData.append("myfile", this.eofile, this.eofile.name);
}
this.httpClient.post(APIURL, formData, httpOptions).subscribe(
result => {
...
...
...
},
error => {
});
I spent 6 hours on this today. I hope this helps you and saves you some time.

Try using Multer
npm i multer
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
router.post('/updateeocoverage', upload.single('myfile'), function (req, res, next) {
res.json(JSON.parse(req.body.data))
})
You will need to indicate the destination folder (line 2) and work out the file name, extension, etc.

First thing to do is ensure that the body isn't empty before making the request, so do a console.log before the request:
console.log(formData);
this.httpClient.post(APIURL, formData).subscribe(
result => {
....
After that, if the body is filled correctly, try to put the body like this on the request method:
this.httpClient.post(APIURL, {formData})
or
this.httpClient.post(APIURL, {
"field1": formData.field1,
...
})
and if these two things don't correct your issue, problaby you have something wrong on back-end side

Related

Folder /tmp is not working in Vercel Production. Giving error of EORFs

Folder Structure image# Multer.js File
const multer = require("multer");
const path = require("path");
const fs = require("fs");
const httpStatus = require("http-status");
const ApiError = require("../utils/ApiError")
const logger = require("../utils/logger");
const multerUpload = async (req, res, next) => {
let fileName = "";
let storage = multer.diskStorage({
destination: function (req, file, callback) {
fs.mkdir(path.join(path.resolve(), "/tmp"), (err) => {
if (err) {
logger.error("mkdir tmp %o", err);
}
callback(null, path.join(path.resolve(), "/tmp"));
});
},
filename: function (req, file, callback) {
fileName = file.fieldname + "-" + req.query.eventId + Date.now() + path.extname(file.originalname);
logger.info("filename of uploadSheet===> %s", fileName);
callback(null, fileName);
},
});
// below code is to read the added data to DB from file
var upload = multer({
storage: storage,
fileFilter: function (req, file, callback) {
var ext = path.extname(file.originalname);
if (ext !== '.xlsx') {
return callback(new Error('Only Excel sheets are allowed'))
}
callback(null, true)
},
}).single("sheet");
upload(req, res, async function (err) {
if (err) {
next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, err.message));
} else {
req.fileName = fileName;
next();
}
})
}
module.exports = multerUpload;
It gives error of EORFS read only file in vercel production but the code works fine in local.
I'm trying to upload the excel sheet file from the Api and then read the data from it and add it into the Mongodb.
I once encountered this same problem working with Heroku a long time ago, I haven't worked with vercel but with quick research, I will say this is the cause, vercel does not provide storage for you to upload files to in production, you need a separate service for that like Amazon S3, but there also exists Azure File Storage and Google Cloud Storage.
alternatively, if you don't want to add more services to your project, you can just convert the image to base64 string and save it as text(but need to make the field/column read-only so it does not get corrupted) NOT the best alternative but it was something I once did
To use /tmp in server functions, you should just use /tmp/your-file. Remove path.resolve().
Only if you need to store something temporarily, you may try to use /tmp directory.
Limit 512 MB + no guaranty - https://github.com/vercel/vercel/discussions/5320

cannot access before initialization

I'm new in Node.js and OOP and need some help with my upload images system based on GridFS. In my class I have "initializeStorage" method and in the last line of that method "this.upload" works as middleware with I need to use in my routes file. In routes file I tried use "new" keyword to my class but then I was getting undiefiend error, I tried few others ways to solve that problem but I'm run of ideas. If someone have solution or hint lemme know and write below. Thanks.
GridFS class file
class MyGridFS {
constructor() {
let gfs = this.gfs;
let gridfsBucket = this.gridfsBucket;
}
initializeGridFS(conn) {
this.conn = conn;
conn.once("open", () => {
this.gridfsBucket = new mongoose.mongo.GridFSBucket(conn.db, {
bucketName: "uploads",
});
this.gfs = Grid(conn.db, mongoose.mongo);
this.gfs.collection("uploads");
});
}
initializeStorage(mongo_uri) {
this.mongo_uri = mongo_uri;
const storage = new GridFsStorage({
url: this.mongo_uri,
file: (req, file) => {
const filename = "file_" + Date.now() + path.extname(file.originalname);
return {
filename,
bucketName: "uploads",
};
},
});
this.upload = multer({ storage });
}
Routes file
upload.post("/upload", uploadMiddleware.single("file"), (req, res) =>
uploadController.upload(req, res),
);
In "uploadMiddleware" place should be upload middleware from class before

How do I get "res.fields" to stop coming back as "undefined" in my application?

I am working on a project using multer to upload multiple files. To do this, I am using upload.fields in a controller file, the code looks like this:
tourController.js
const multerStorage = multer.memoryStorage();
const multerFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image')) {
cb(null, true);
} else {
cb(new AppError('Not an image! Please upload images only.', 400), false);
}
};
const upload = multer({
storage: multerStorage,
fileFilter: multerFilter
});
exports.uploadTourImages = upload.fields([
{ name: 'imageCover', maxCount: 1 },
{ name: 'images', maxCount: 3 }
]);
In the next function, where I resize the images, I console.log res.files, but it comes back as undefined in my terminal output. Here is the code where I console.log res.files. It follow the code that I posted directly above:
exports.resizeTourImages = (req, res, next) => {
console.log(res.files);
next();
};
I tried using upload.array, but couldn't get that to work as expected either. I'm not sure if the problem could be with a different part of the application. Here's where the function appears in the middleware stack in the route file:
tourRoutes.js
router
.route('/:id')
.get(tourController.getTour)
.patch(
authController.protect,
authController.restrictTo('admin', 'lead-guide', 'guide'),
tourController.uploadTourImages,
tourController.resizeTourImages,
tourController.updateTour
)
I'm at a loss as to why I can't get this work. If helpful, I have all of the code uploaded to GitHub here:
https://github.com/christopherbclark/mynatours
As mentioned in the documentation, res.files is undefined because multer populates the files under the req-object. You need to change it to:
exports.resizeTourImages = (req, res, next) => {
console.log(req.files);
next();
};

Convert a file using FFMPEG and upload to AWS S3 Nodejs

Hey everyone so quick question I want to allow a user to upload a WebM file and convert it using FFmpeg to mp4. I am using Nodejs for the backend and already have a route that uploads files to Amazon S3 file storage. But let's say I wanted to send that file and not store it anywhere but convert it to mp4 from the request itself is that possible? If not is it possible to take an s3 file URL and convert it to mp4? Can anybody point me in the right direction as to what is possible and the best way to do this?
basically all I want to do is
const objectUrl = createObjectURL(Blob);
ffmpeg -i objectURL S3OutputLocation
or
ffmpeg -i myS3InputLocation myS3OutputLocation
Okay so there is a couple of things you have to do in order to make this work.
1. you need to set up a local instance of multer as you need to upload the file locally before going to s3. I tried to do it with s3 directly but it seemed to use a lot of costly and time-consuming read operations that took much longer than writing the file to the server first. I found this to be the best solution.
You do this like so:
const localStorage = multer.diskStorage({
destination: function(req, file, cb) {
const destination = __dirname + "\\canvas-uploads";
console.log("destination", destination);
cb(null, destination);
},
filename: function(req, file, cb) {
const filename = req.body.id + "." + file.mimetype.toString().slice(file.mimetype.toString().lastIndexOf("/") + 1);
console.log("filename", filename);
cb(null, filename);
}
});
const uploadLocal = multer({
storage: localStorage
});
You need to set up ffmpeg-fluent and wrap it in a promise so you can be sure it's finished with all your processing (uploading to s3 and etc in the same route.) for convenience.
you do this like so:
router.post('/upload-temp', uploadLocal.array("upload"), async(req, res, next) =>{
res.json({id: req.body.id});
});
router.post('/ffmpeg', async(req, res, next) => {
try {
const reqPath = path.join(__dirname, '../upload/canvas-uploads/');
const {id, type} = req.body;
const localFileInput = `${reqPath}${id}.webm`;
const localFileOutput = `${reqPath}${id}.${type}`;
console.log("localInput", localFileInput);
console.log("localOutput", localFileOutput);
const key = `canvas/${id}.${type}`;
await new Promise((resolve, reject) => {
ffmpeg().input(localFileInput)
.withOutputFormat(type)
.output(localFileOutput)
.on('end',async ()=> {
const fileContent = await fs.readFileSync(localFileOutput);
await fs.unlinkSync(localFileInput);
await fs.unlinkSync(localFileOutput);
const params = {
Bucket: process.env.BUCKET_NAME,
Key: key,
Body: fileContent
}
await s3.putObject(params).promise();
resolve();
}).run();
})
res.send("success")
} catch(error){
console.log(error);
res.send(error);
}
});

Given the local relative url to an image, how can I get actual image to send

I am writing an express app where one endpoint takes in an image and I would then send that image to a third-party Api. I used multer to save the image to disk, and I have the relative file path to the image, but the API I am trying to call needs the actual image or an image url. The problem is that i'm passing just a string value that contains the filepath to the image. Here is what I have currently:
var multer = require('multer')
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/');
},
filename: function(req, file, cb) {
cb(null, Date.now() + file.originalname);
}
});
var upload = multer({ storage: storage });
And I need to call the image in this api call here:
router.post('/images/tags/info/image', upload.single('fileName'), function(req, res, next) {
console.log(req.file.path);
var imageUrl = req.file.path;
app.models.predict('APP_KEY', imageUrl).then(
function (response) {
var responseJson = JSON.stringify(response.data.outputs[0].data.concepts);
var data = collectTags(responseJson);
data.then(function(value) {
res.json(value);
});
},
function (err) {
console.log(err);
});
});
When I console.log(req.file.filePath), I get a valid filepath, but I need the actual image to pass into the app.models.predict(API_KEY, image)
You can just read the image from the file system and send it to the api.
e.g.
const bitmap = await q.nfcall(fs.readFile,req.file.path);
//remove the temp image
fs.unlink(req.file.path);
const buffer = new Buffer(bitmap).toString('base64');
app.models.predict('APP_KEY', buffer);
Example sending the image as base64 (using async/await).

Resources