Multer's req.body works but req.files gives undefined - node.js

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.

Related

req.file is undefined - multer and nodejs

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

Angular6 Rename the File to be Uploaded

I am using Angular6 and NodeJS for my API. MY application is a School Software where I should save Students Images with their Registration Numbers. I am using ng2-file-upload in Angular for File Uploads and Multer in NodeJS, Upload is working perfectly, but I can't understand how to rename the file with the registration number of Student. My url in Angular consists of Student Registration number, I just need to know how to send that Reg. Number to NodeJS and rename the file with Reg. Number
My html File
<input type="file" name="photo" ng2FileSelect [uploader]="uploader" />
<button type="button" class="btn btn-success btn-s"
(click)="uploader.uploadAll()"
[disabled]="!uploader.getNotUploadedItems().length" >
Upload an Image
</button>
My .ts file
public uploader: FileUploader = new FileUploader({url: URL, itemAlias: 'file'});
ngOnInit() {
this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; };
this.uploader.onCompleteItem = (item: any, response: any, status: any, headers: any) => {
console.log('ImageUpload:uploaded:', item, status, response);
alert('File uploaded successfully');
};
}
My NodeJS Multer Upload:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'E:/school')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname);
}
});
var upload = multer({ storage: storage });
router.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
console.log("No file received");
return res.send({
success: false
});
} else {
console.log('file received');
return res.send({
success: true
})
}
});
Thanks in Advance. Any help will be Appreciated.
You can edit the name before posting the formData to the backend. Give the file name (or get from a form) and get the file extension from the file to upload. Then append to formData before posting to the backend.
Reference: https://stackoverflow.com/a/55178342 and https://youtu.be/v67NunIp5w8
let fileToUpload = <File>files[0];
let fileName:string = 'test-name' //get name from form for example
let fileExtension:string = fileToUpload.name.split('?')[0].split('.').pop();
const formData = new FormData();
formData.append('file', fileToUpload, fileName+'.'+fileExtension);
this.http.post(environment.backendApiUrl+'/api/upload', formData, {reportProgress: true, observe: 'events'})
.subscribe(event => {
if(event.type === HttpEventType.UploadProgress) {
this.progress = Math.round(100 * event.loaded / event.total);
} else if (event.type === HttpEventType.Response) {
this.message = 'Upload success.';
this.onUploadFinished.emit(event.body);
}
});
Be careful: This solution should work however this code is ugly and probably not the best way to do what you want. But it's just to show you how you can rename your file in the front-end side.
Important: I will assume you are uploading a single file because we will use the first item of the uploader.queue for this example.
First, you can add an input type text in your HTML file just below your input file :
<input *ngIf="uploader.queue.length" type="text" name="file" [value]="uploader.queue[0].file.name"
(change)="renameFile($event.target.value)"/>
This input will be showed when you will upload a file. The (change) will trigger the renameFile func in your component with the current input value.
Then update your TS component by adding the renameFile method :
renameFile(name: string): void {
// fetching the item uploaded
const oldFileItem: FileItem = this.uploader.queue[0];
// re-instanciating a new file based on the old file by changing the name property
const newFile: File = new File([this.uploader.queue[0]._file], name, {type: oldFileItem.file.type});
// uploader.queue is taking FileItem objects, instanciating a new FileItem
const newFileItem = new FileItem(this.uploader, newFile, this.opts);
// replacing the old one by the new file updated
this.uploader.queue[0] = newFileItem;
}
Finally, you can have a look to the file property in your console.log inside the onCompleteItem function, the file has been updated.
You should be able to watch the onFileSelected event and get file[0] (if you are single file upload).
And set the file[0].name before uploading.

Multer: upload different file types in different folders

I have a simple form with two file input fields. One is about an image, and the other is about a mp3 file.
I have an express server which utilizes multer as file upload system. I'd like to save the image inside the img, and the mp3 file inside the music folder.
This is what I tried so far:
var musicUpload = multer({dest: 'music'});
var imgUpload = multer({dest: 'img'});
app.post('music',
musicUpload.single('music'),
imgUpload.single('img'),
function (req, res) {
...
});
While this is the form:
<form action="post" enctype="multipart/form-data" action="music">
<input type="file" id="img" name="img">
<input type="file" id="music" name="music">
<input type="submit" value="Send">
</form>
I need to handle the 2 different files in a different way, this is the reason why I used "single" twice. But, unluckly, I receive a "Unexpected field" error message.
How can achieve the result?
Ps. There are many question on SO about multiple files uploading, but none of them solved my specific problem. Don't be too much fast at flagging this question :)
You could try using something like DiskStorage. As per their docs: https://github.com/expressjs/multer
var storage = multer.diskStorage({
destination: function (req, file, cb) {
if (file.mimetype === 'audio/mp3') {
cb(null, 'songs')
} else if (file.mimetype === 'image/jpeg') {
cb(null, 'img')
} else {
console.log(file.mimetype)
cb({ error: 'Mime type not supported' })
}
}
})
var upload = multer({ storage: storage })
and then on the endpoints themselves do:
var upload = multer({storage});
router.post('/song', upload.any(), function(req, res) {
...
});
this is probably cleaner then your approach and i think gives the functionality you're looking for as it gives you more granular control over saving those files. (with edits by #cristian)
you can add if else statement in multer storage destination and use file.fieldname to determine your input field name and store it in different folder.
var storage = multer.diskStorage({
destination: function (req, file, cd) {
if (file.fieldname === 'img') {
cd(null, '/img');
}
else if (file.fieldname === 'music') {
cd(null, '/music');
}
},
filename: function (req, file, cd) {
cd(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
var upload = multer({storage: storage});
and in your post method this will be the solid partner. reference
var cpUpload = upload.fields([{ name: 'img', maxCount: 1 }, { name: 'music', maxCount: 1 }]);
app.post('/music', cpUpload, function (req, res, next) {
console.log(req.file, req.body);
res.redirect('/');
});
Consider looking at the solutions shown here in this similar question, it shows how to use the upload.fields[...] provided by multer for multi-file uploads that gives you more control withe the chance to define if the field is .single or .array using the maxCount:.
OR
Look into my repo (a nuxt-express-mongodb app) under server where i use multer to upload a song/music and image from 1 post request using upload.fields[...] to see a full working implementation.

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/

Nodejs Amazon S3 File Upload

I am trying to create a form that uploads my local files to my S3 bucket, but I'm a little confused as to where parts of the uploading logic should exist. I wanted to keep my POST route clean and reference the AWS logic that is housed in a separate file, but I'm a little confused as to what value should be used for the Body property for the params level and how I should reference this module when setting the completed upload URL to my database property at the fileAttachment line. Can anyone point me in the right direction?
Here is my aws-s3.js file:
module.exports = function() {
var path = require('path');
var async = require('async');
var fs = require('fs');
var AWS = require('aws-sdk');
var config = require(path.resolve(__dirname, '..', '..','./config/config.js'));
AWS.config.region = config.region;
var s3 = new AWS.S3({params: {Bucket: config.awsBucket}});
var params = {Key: config.awsAccessKeyId, Body: req.body.fileAttachment};
s3.upload(params, function(err, data){
if (err) {
console.log("Error uploading data: ", err);
} else {
console.log("Successfully uploaded data to " + config.awsBucket + " /myKey");
}
});
return s3;
};
Here is my route:
appRoutes.route('/create/file')
.get(function(req, res){
models.DiscoverySource.findAll({
where: {
organizationId: req.user.organizationId
}, attributes: ['discoverySource']
}).then(function(discoverySource){
res.render('pages/app/file-create.hbs',{
discoverySource: discoverySource
});
});
})
.post(function(req, res){
models.File.create({
discovery: req.body.discovery,
discoverySource: req.body.discoverySource,
fileAttachment:
userId: req.user.user_id
}).then(function(){
res.redirect('/app');
});
});
Form:
<form action="/app/create/file" method="post">
<div class="form-header">
<label for="data-discovery-source">Discovery Source:</label>
<select name="discoverySource">
{{#each discoverySource}}
<option value="{{this.discoverySource}}">{{this.discoverySource}}</option>
{{/each}}
</select>
<label for="data-discovery">Discovery:</label>
<textarea id="discovery-text-field" name="discovery"></textarea>
<label for="report-link">File Attachment:</label>
<input type="file" name="fileAttachment">
</div>
<button type="submit" id="create-button">Create File</button>
</form>
You can try using the multer-s3 module.
It allows you to upload your file with storaage configured to AWS.
This code uses the aws-sdk module and more about it's configuration can be found here.
Here is my code example:
It uses the recommended amazon AWS SDK for JavaScript in Node.js with
And it also uses multer express middleware for uploading files.
var aws = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')
var app = express()
var s3 = new aws.S3({ {accessKeyId: 'akid', secretAccessKey: 'secret'})
//this can also be configured in config file and through code
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'some-bucket',
key: function (req, file, cb) {
cb(null, Date.now().toString())
}
})
})
//the upload.array -means that you can load multiple files(max 3) named photos maximum 3, the resulting AWS full path urls will be returned in req.files
app.post('/upload', upload.array('photos', 3), function(req, res, next) {
//reference results that can be stored in database for example in req.files
res.send('Successfully uploaded ' + req.files.length + ' files!')
})
this codealso uses the multer npm module. More about its configuration possibilities like: single, any upload.array, fields can be found here.
You can alternatively use Minio-js here is an example of presigned-postpolicy.js. I hope it helps.
var Minio = require('minio')
var s3Client = new Minio({
endPoint: 's3.amazonaws.com',
accessKey: 'YOUR-ACCESSKEYID',
secretKey: 'YOUR-SECRETACCESSKEY',
insecure: false // Default is false.
})
// Construct a new postPolicy.
var policy = s3Client.newPostPolicy()
// Set the object name my-objectname.
policy.setKey("my-objectname")
// Set the bucket to my-bucketname.
policy.setBucket("my-bucketname")
var expires = new Date
expires.setSeconds(24 * 60 * 60 * 10) //10 days
policy.setExpires(expires)
policy.setContentLengthRange(1024, 1024*1024) // Min upload length is 1KB Max upload size is 1MB
s3Client.presignedPostPolicy(policy, function(e, formData) {
if (e) return console.log(e)
var curl = []
curl.push('curl https://s3.amazonaws.com/my-bucketname')
for (var key in formData) {
if (formData.hasOwnProperty(key)) {
var value = formData[key]
curl.push(`-F ${key}=${value}`)
}
}
// Print curl command to upload files.
curl.push('-F file=#<FILE>')
console.log(curl.join(' '))
})
Disclaimer: I work for Minio, Open Source object storage compatible with AWS S3.

Resources