req.file is undefined - multer and nodejs - node.js

All the data is being sent to the backend except for the image file. I keep getting req.file is undefined, which prevents data from being stored in the database.
On the server side I have a routes folder with the that handles a new food entry from the user.
const multer = require('multer')
var fs = require('fs')
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'uploads')
},
filename: function(req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({storage: storage})
router.route('/add', upload.single('foodImage')).post((req, res) => {
var img = fs.readFileSync(req.body.file.path);
var encode_image = img.toString('base64')
var finalImg = {
contentType: req.file.mimetype,
image: new Buffer(encode_image, 'base64')
}
console.log(finalImg);
// remaining code left out
On the front end I have a form that collects input from user that includes a photo and a text description. This is in a component file.
fileSelectedHandler(e) {
this.setState({
selectedFile: e.target.files[0]
})
}
onSubmit(e) {
const nutrition = {
'calories': calories,
'fat': fat,
'sugar': sugar,
'carbs': carbs,
'cholesterol': cholesterol,
'protein': protein,
'photo': result.foods[0].photo.thumb,
'date': this.state.date,
'description': this.state.description,
'food_list': food_list.toString(),
'foodImage': this.state.selectedFile
}
console.log(nutrition);
axios.post('http://localhost:5000/nutrition/add', nutrition).then(res => console.log(res));
window.location = '/nutrition';
//remaining code hidden
render() {
return (
<div>
<h3>Create New Nutrition Log</h3>
<form onSubmit={this.onSubmit} encType='multipart/form-data'>
<div className="form-group">
<label>Upload food image!</label><br></br>
<input type="file" onChange={this.fileSelectedHandler} name="foodImage"/>
</div>
<div className="form-group">
<label>Description: </label>
<input type="text"
required
className="form-control"
value={this.state.description}
onChange={this.onChangeDescription}
/>
</div>
//remaining code hidden

. Client Side
To send images from client to the server your request data should have a multipart/form-data structure. You can accomplished that with:
const data = new FormData();
data.append('calories', calories)
data.append('fat', fat)
data.append('sugar', sugar)
data.append('carbs', carbs)
data.append('cholesterol', cholesterol)
data.append('protein', protein)
data.append('photo', result.foods[0].photo.thumb)
data.append('date', this.state.date)
data.append('description', this.state.description)
data.append('food_list', food_list.toString())
data.append('foodImage', this.state.selectedFile)
axios.post('http://localhost:5000/nutrition/add', data)
.then(res => console.log(res));
Also, this part of the code where you set encType has no use because you are doing a custom post with axios, not directly from form. (You can remove encType).
<form onSubmit={this.onSubmit} encType='multipart/form-data'>
. Server Side
And to get your file from server side:
// BEFORE
// router.route('/add', upload.single('foodImage')).post((req, res) => {
// AFTER
router.route("/add").post(upload.single("foodImage"), (req, res) => {
// You can get image details (path, fieldname, size, etc) from request.file.
console.log(req.file);
// And JSON data, you can get it normally from request.body
console.log(req.body);
Also take a look at your post path, on server you are setting it as '/add' and on client you are sending the request to 'nutrition/add'
Finally, there is this link that could be useful to you.
rest-api-file-ie-images-processing-best-practices

Related

Multer's req.body works but req.files gives undefined

I'm trying to submit a form , that consists some text and an image file to the server. Regarding multer, my understanding is that, multer creates a storage folder for our images 'my-uploads/' and we pass on the key from formData.append('imageFile', imageFile) to upload.single('imageFile'). I tried giving paths like: my-uploads/, /my-uploads, ./my-uploads, so far none of it is working.
Next, using Fetch, I have been able to send the text body to the server and it reaches in [Object: null prototype].....(Not sure if it the right way of sending). The image files is not showing up the way expected too. Multer throws undefined when called req.files. Where is it going wrong with the code?
html:
<form class="blogForm" method="post" encType="multipart/form-data">
<input type="file" class="imageInput" name="file" multiple = "true"/>
<div class="blogEntryDiv" contenteditable="true"></div>
<input class= "blogSubmitButton" type="submit" value="Submit" >
</form>
js
document.querySelector('.blogForm').addEventListener('submit', (e) => {
let formData = new FormData();
let textContent = document.querySelector('.blogEntryDiv').innerText
let imageFile = document.querySelector('.imageInput').files
formData.append('textcontent', textContent);
formData.append('imageFile', imageFile);
fetch(`/someimage`, {
method: 'POST',
body: formData
}).then(function (res){
console.log(res);
}).then(json => console.log(json))
.catch(err => console.log(err));
})
app.js:
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'my-uploads/')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({ storage: storage })
app.post('/someimage', upload.single('imageFile'), (req, resp) => {
console.log(req.body)
console.log(req.files)//gives undefined
})
req.body gives:
[Object: null prototype] {
textcontent: '\n\nlorem lorem',
imageFile: '[object FileList]' //gives a string
}
formData.append('imageFile', imageFile) does not work, because imageFile is a file list, but you can only append single files. Use formData.append('imageFile', imageFile[0]).
Also, multer will write the single file into req.file, not into req.files.

validate request body before uploading a file using multer in nodejs

I want to upload user profiles according to their school ids
// import multer
var multer = require('multer')
// import school and student models
const { School } = require('./models/school/school.model.js')
const { Student } = require('./models/student/student.model.js')
// configure multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = `./uploads/schools/${req.body.schoolId}/students`
fs.exists(dir, exist => {
if (!exist) {
return fs.mkdir(dir, { recursive: true }, error => cb(error, dir))
}
return cb(null, dir)
})
},
filename: (req, file, cb) => {
cb(null, `student-${normaliseDate(new Date().toISOString())}.${file.originalname.split('.')[file.originalname.split('.').length - 1]}`)
}
})
var upload = multer({storage: storage}).single('profile')
app.post('/student', function (req, res) {
// check if submitted school id is valid
let school = await School.findOne({ _id: req.body.schoolId })
if (!school)
return res.send(`School of id ${req.body.schoolId} doesn't exist`)
// upload the profile pic
upload(req, res, function (err) {
if (err)
return res.send(err)
})
// save the student
let newStudent = new Student({
name: req.body.name,
email: req.body.email,
school: req.body.schoolId,
profile: req.file.filename
})
const saveDocument = await newStudent.save()
if (saveDocument)
return res.send(saveDocument).status(201)
return res.send('New College not Registered').status(500)
})
but when I try to access req.body before uploading the profile I get an empty object.
I would upload the profiles in a tempolary folder and move them later but what if the submitted school id is invalid ?? this will require me to delete the uploaded file. so I want to make sure the submitted details are valid and then upload the profile later save the student.
Check your codes, you are using await but async is nowhere to be found. Though it may not be the cause of getting an empty req.body object.
let school = await School.findOne({ _id: req.body.schoolId })
const saveDocument = await newStudent.save()
I am not sure of how you are sending data to the backend but try these option it may work
remember that when javascript is sending POST data from client to server it sends them according to how they are arranged so if you have your client form like this
<form action="">
<input type="file" name="pic">
<input type="text" name="username">
<input type="number" name="age">
</form>
it will send file named pic first, then text named username secondly and finally send number named age, so if you are getting empty req.body while the file has not been completely uploaded you might check this if it is the problem because javascript process them on an order according to how they were sent
i hope this gonna help

Image uploading platform using node-js, mongoose and multer

I'm desperately trying to build an image gallery in node-js where I can:
1. use a form to upload an image from the harddrive
2. save the uploaded file in mongo-DB (mongoose)
3. Retrieve the uploaded images and show it in my website
I'm quite new to node and also mongodb, so I have a really hard time doing this (doing the same for simply news / text-only db-schemas was so easy). I have searched all similar topics before, but none of them helped me, so I'm really desperate now since I had to copy some code-parts that I don't understand on the way to solving it. Please help me with some beginners explanations!
My html form
<form id="uploadForm" action="/images" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="imageSelector">Select Image</label>
<input type="file" class="form-control" name="image" placeholder="upload image" id="imageSelector" accept="image/png, image/jpeg">
</div>
<div class="form-group imagePreview">
<p>No files currently selected for upload</p>
</div>
<div class="form-group">
<label for="imageDescription">Image Description</label>
<input type="text" class="form-control" name="description" placeholder="description" id="imageDescription">
</div>
<button class="btn btn-success">submit</button>
</form>
My DB-Schema:
let imageSchema = new mongoose.Schema({
img: { data: Buffer, contentType: String },
description: String});
module.exports = mongoose.model("Image", imageSchema); //Exporting my Schema
My route file:
let express = require("express");
let mongoose = require("mongoose");
let bodyParser = require("body-parser");
let router = express.Router({mergeParams: true});
let multer = require("multer");
let Image = require("../models/image"); //Using DB-Schema that I exported
const GridFsStorage = require('multer-gridfs-storage');
const storage = new GridFsStorage({
url: "mongodb://localhost/My_DB",
file: (req, file) => {
return {
filename: 'file_' + Date.now()
};
}
});
const upload = multer({ storage });
const sUpload = upload.single('image');
router.post('/', sUpload, (req, res, next) => {
console.log("FILENAME: " + req.file.filename);
console.log("CHUNKSIZE: " + req.file.chunkSize);
console.log("CONTENTTYPE: " + req.file.contentType);
Image.create(req.file, function(err, addedImage) {
if(err) {
console.log(err);
} else {
console.log("SUCCESSFULLY ADDED NEW IMAGE! " + addedImage);
res.redirect("/images");
}
})
});
module.exports = router;
Now, what do I have to do to get the actual file to retrieve it? When I load my html-form and upload an image, after clicking submit, I get the following console.logs:
FILENAME: file_1522841638818
CHUNKSIZE: 261120
CONTENTTYPE: image/png
PATH: undefined
ADDED NEW IMAGE SUCCESSFULLY! { _id: 5ac4b82674d2091a8db36f2f, __v: 0 }
When I go to my DB "My_DB" and take a look at db.images.find() the image is not uploaded. So I need to know
1. How to upload it into my DB?
2. How to retrieve it and really use the file itself to show it within html ?
Thanks very much!
Btw.: I implemented this whole multer-gridfs-storage thing because I red that you can't upload images > 16 MB without it. I think I'm not really using multer yet so maybe you can give me some hints of how I can get to the image file. I know, you might talk about some docu of specific packages that I implemented. Believe me, this is only one attempt of about 100 different attempts to approach this and I'm really confused now of what is important for me :-/
Use
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({ storage: storage })
Url : is file system path not mongo path
Also Your Model need data ,contenttype,description fields
img: {
data: Buffer,
contentType: String },
description: String});
But req.file does not contains it on same key so please create new json with key mapping
More Info : https://github.com/expressjs/multer
For storing image to mongoDB, you need to write a service something like this in the link https://devdactic.com/ionic-image-upload-nodejs-server/

Using Node, Express and Multer for Upload a file to MongoDB? What should I use, Multer or GRIDfs?

Hello I'm running a final project on heroku using MongoLab and Multer to upload photos seems to work fine but after a few hours or less the photos dissapear from the page but leaves the img placeholder and another field like name or date area where it was.
Is this Heroku or Mongolab issue or is it just the free account is this because of the free acounts. also it doesnt matter if there is one photo uploaded or a dozen they will still dissapear after a while.
Could you help me? Is there a hack, tricky line of code or something that I made wrong with Multer or my database? I have made a deep research in the web and Stack-Overflow I found something call GRIDfs but I don't know if that will work. Could you tell me if I'm in rigth way?
This is my code for Multer:
var express = require("express"),
body_parser = require("body-parser"),
multer = require("multer"),
mongoose = require("mongoose");
var app = express();
//connect my local database to MongoLab database
var url = process.env.DATABASE || "mongodb://localhost/black_hat";
mongoose.connect(url);
Multer configuration
var storage = multer.diskStorage({
destination: function(req, file, callback){
callback(null, "./public/uploads");
},
filename: function(req, file, callback){
callback(null, Date.now() + file.originalname);
}
});
var upload = multer({storage : storage}).single("image");
My POST route
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
if(err){
return res.end("Error uploading file");
}
// get data from form
var newName = req.body.name,
newCover = "/uploads/" + req.file.filename;
// Packing into an object
var newCollage = {
name : newName,
cover: newCover
};
//create new album
Collage.create(newCollage, function(err, collage) {
if(err){
console.log(err);
} else {
// console.log(collage);
res.redirect("/fotos_videos");
}
});
});
});
The form where I upload the picture
<div style="width=30%; margin: 30px auto" >
<form action="/fotos_videos" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label>Album Name</label>
<input type="text" name="name" placeholder="Album Name" class="form-control">
</div>
<div class="form-group">
<label>Choose Album Cover</label>
<input type="file" name="image" placeholder="image url" class="form-control" required>
</div>
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block">Upload</button>
</div>
</form>
Back
</div>
Finally my Model for Collage
var mongoose = require("mongoose");
// Schema
var collageSchema = new mongoose.Schema({
name : String,
cover : String,
photos: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Photo"
}]
});
var Collage = mongoose.model("Collage", collageSchema);
module.exports = Collage;
Any kind of help will be appreciated.
if you want to save the image to the db u should not have "dest" property. also implementation of multer is wrong maybe because it is an old question and previous api was like that. here what u should u do.
configure your multer:
const multer = require("multer");
const upload = multer({
limits: { fileSize: 5000000 },
fileFilter(req, file, cb) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //allowed file extensions
return cb(new Error("please upload png,jpeg or jpg"));
}
cb(undefined, true);
}
});
then create an endpoint. u already have this
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
this iplementation wrong. upload is a middleware and if you wanna add authentication, only authenticated user should be access to this endpoint. so we need to tweak this endpoint.
Also u need to define a schema for uploads in order to save them to the db. let's say u created a model named uploads and this to it. we do not need to validate it by mongoose because multer already takes care of it. make sure u delete the dest property from multer configuration. because if u dont delete, multer will save the file to the folder so our async (req,res){} function will not be able to access to the file so we cannot save it to the db.
uploads:{type:Buffer} //include this in your uploads schema
then in this file call Uploads model.
router.post(
"/fotos_videos/me/uploads", isLoggedIn,
upload.single("uploads"), //uploads is just a random name when u upload from postman, form form-data,put "uploads" in key field
async (req, res) => {
req.upload.uploads = req.file.buffer;//i explain down below
await req.upload.save();
res.send();
},
(err, req, res) => {
res.status(400).send("error happned");
}
);
We do not store our pictures in the filesystem. The reason is almost all the deployment platforms, require us to take our code and push it up to the repository on their server. So the file system gets wiped every time we deploy which means we will lose data when we deploy. So instead of storing the images on the file system, we are going to add a field to the user model to store the image of binary data in the database.
now it is time to fetch our image from the db. for this u need to have a new route
router.get("/fotos_videos/:id/uploads", async (req, res) => {
try {
const upload = await Uploads.findById(req.params.id);
if (!upload || !upload.uploads) {
throw new Error("no image found");
}
res.set("Content-Type", "image/png");
res.send(upload.uploads);
} catch (e) {
res.status(400).send(e.message);
}
});
I don't know if this will be helpful. but from my understanding multer is the stuff used to get the data from the user (like uploaded images). It doesn't do persistent storage. If you want to store the data (large files like images) you should use gridfs-stream .
You should a create a read stream to GridFs once the data comes in from the user.

MEAN Stack File uploads

I've recently started programming with the MEAN Stack, and I'm currently implementing some sort of social network. Been using the MEAN.io framework to do so.
My main problem right now is getting the file upload to work, because what I want to do is receive the file from the form into the AngularJS Controller and pass it along with more info's to ExpressJS so I can finally send everything to MongoDB. (I'm building a register new user form).
I dont want to store the file itself on the database but I want to store a link to it.
I've searched dozens of pages on google with different search queries but I couldn't find anything that I could understand or worked. Been searching for hours to no result. That's why I've came here.
Can anyone help me with this?
Thanks :)
EDIT: Maybe a bit of the code would help understand.
The default MEAN.io Users Angular controller which I'm using as foundation has this:
$scope.register = function(){
$scope.usernameError = null;
$scope.registerError = null;
$http.post('/register', {
email: $scope.user.email,
password: $scope.user.password,
confirmPassword: $scope.user.confirmPassword,
username: $scope.user.username,
name: $scope.user.fullname
})//... has a bit more code but I cut it because the post is the main thing here.
};
What I want to do is:
Receive a file from a form, onto this controller and pass it along with email, password, name, etc, etc and be able to use the json on expressjs, which sits on the server side.
The '/register' is a nodejs route so a server controller which creates the user (with the user schema) and sends it to the MongoDB.
I recently did something just like this. I used angular-file-upload. You'll also want node-multiparty for your endpoint to parse the form data. Then you could use s3 for uploading the file to s3.
Here's some of my [edited] code.
Angular Template
<button>
Upload <input type="file" ng-file-select="onFileSelect($files)">
</button>
Angular Controller
$scope.onFileSelect = function(image) {
$scope.uploadInProgress = true;
$scope.uploadProgress = 0;
if (angular.isArray(image)) {
image = image[0];
}
$scope.upload = $upload.upload({
url: '/api/v1/upload/image',
method: 'POST',
data: {
type: 'profile'
},
file: image
}).progress(function(event) {
$scope.uploadProgress = Math.floor(event.loaded / event.total);
$scope.$apply();
}).success(function(data, status, headers, config) {
AlertService.success('Photo uploaded!');
}).error(function(err) {
$scope.uploadInProgress = false;
AlertService.error('Error uploading file: ' + err.message || err);
});
};
Route
var uuid = require('uuid'); // https://github.com/defunctzombie/node-uuid
var multiparty = require('multiparty'); // https://github.com/andrewrk/node-multiparty
var s3 = require('s3'); // https://github.com/andrewrk/node-s3-client
var s3Client = s3.createClient({
key: '<your_key>',
secret: '<your_secret>',
bucket: '<your_bucket>'
});
module.exports = function(app) {
app.post('/api/v1/upload/image', function(req, res) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
var file = files.file[0];
var contentType = file.headers['content-type'];
var extension = file.path.substring(file.path.lastIndexOf('.'));
var destPath = '/' + user.id + '/profile' + '/' + uuid.v4() + extension;
var headers = {
'x-amz-acl': 'public-read',
'Content-Length': file.size,
'Content-Type': contentType
};
var uploader = s3Client.upload(file.path, destPath, headers);
uploader.on('error', function(err) {
//TODO handle this
});
uploader.on('end', function(url) {
//TODO do something with the url
console.log('file opened:', url);
});
});
});
}
I changed this from my code, so it may not work out of the box, but hopefully it's helpful!
Recently a new package was added to the list of packages on mean.io. It's a beauty!
Simply run:
$ mean install mean-upload
This installs the package into the node folder but you have access to the directives in your packages.
http://mean.io/#!/packages/53ccd40e56eac633a3eee335
On your form view, add something like this:
<div class="form-group">
<label class="control-label">Images</label>
<mean-upload file-dest="'/packages/photos/'" upload-file-callback="uploadFileArticleCallback(file)"></mean-upload>
<br>
<ul class="list-group">
<li ng-repeat="image in article.images" class="list-group-item">
{{image.name}}
<span class="glyphicon glyphicon-remove-circle pull-right" ng-click="deletePhoto(image)"></span>
</li>
</ul>
</div>
And in your controller:
$scope.uploadFileArticleCallback = function(file) {
if (file.type.indexOf('image') !== -1){
$scope.article.images.push({
'size': file.size,
'type': file.type,
'name': file.name,
'src': file.src
});
}
else{
$scope.article.files.push({
'size': file.size,
'type': file.type,
'name': file.name,
'src': file.src
});
}
};
$scope.deletePhoto = function(photo) {
var index = $scope.article.images.indexOf(photo);
$scope.article.images.splice(index, 1);
}
Enjoy!
Mean-upload has been obsoleted and is now called "upload". It is managed in - https://git.mean.io/orit/upload
I know this post is old. I came across it and #kentcdodds had an answer that i really liked, but the libraries he used are now out of date and I could not get them to work. So after some research i have a newer similar solution I want to share.
HTML using ng-upload
<form >
<div style="margin-bottom: 15px;">
<button type="file" name="file" id="file" ngf-select="uploadFiles($file, $invalidFiles)" accept="image/*" ngf-max-height="1000" ngf-max-size="1MB">Select File</button>
</div>
</form>
INCLUDE ng-upload module
download it, references the files and include the module
var app = angular.module('app', ['ngFileUpload']);
this will give you access to the Upload service.
Controller code
$scope.uploadFiles = function(file, errFiles) {
$scope.f = file;
$scope.errFile = errFiles && errFiles[0];
if (file) {
file.upload = Upload.upload({
url: 'you-api-endpoint',
data: {file: file}
});
//put promise and event watchers here if wanted
}
};
NODE api code
All the code below is in a separate route file which is required in my main server.js file.
require('./app/app-routes.js')(app, _);
var fs = require('fs');
var uuid = require('uuid');
var s3 = require('s3fs'); // https://github.com/RiptideElements/s3fs
var s3Impl = new s3('bucketname', {
accessKeyId: '<your access key id>',
secretAccessKey: '< your secret access key >'
});
var multiparty = require('connect-multiparty');
var multipartyMiddleware = multiparty();
module.exports = function(app, _) {
app.use(multipartyMiddleware);
app.post('/your-api-endpoint',function(req, res){
var file = req.files.file; // multiparty is what allows the file to to be accessed in the req
var stream = fs.createReadStream(file.path);
var extension = file.path.substring(file.path.lastIndexOf('.'));
var destPath = '/' + req.user._id + '/avatar/' + uuid.v4() + extension;
var base = 'https://you-bucket-url';
return s3Impl.writeFile(destPath, stream, {ContentType: file.type}).then(function(one){
fs.unlink(file.path);
res.send(base + destPath);
});
});
All i was trying to do was upload a unique avatar for a user. Hope this helps!!

Resources