Image uploading platform using node-js, mongoose and multer - node.js

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/

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.

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

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.

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.

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