9

I am new to the mean stack. I want to know how to upload an image file to the database(mongoose) through angularjs. If possible, please provide me with some code. I have searched the internet but I haven't found any suitable code.

Fergus
  • 2,821
  • 2
  • 27
  • 41
user3775998
  • 1,393
  • 3
  • 13
  • 22

2 Answers2

16

You have plenty ways and tools to achieve what you want. I put one of them here:

For this one I use angular-file-upload as client side. So you need this one in your controller:

        $scope.onFileSelect = function(image) {
            if (angular.isArray(image)) {
                image = image[0];
            }

            // This is how I handle file types in client side
            if (image.type !== 'image/png' && image.type !== 'image/jpeg') {
                alert('Only PNG and JPEG are accepted.');
                return;
            }

            $scope.uploadInProgress = true;
            $scope.uploadProgress = 0;

            $scope.upload = $upload.upload({
                url: '/upload/image',
                method: 'POST',
                file: image
            }).progress(function(event) {
                $scope.uploadProgress = Math.floor(event.loaded / event.total);
                $scope.$apply();
            }).success(function(data, status, headers, config) {
                $scope.uploadInProgress = false;
                // If you need uploaded file immediately 
                $scope.uploadedImage = JSON.parse(data);      
            }).error(function(err) {
                $scope.uploadInProgress = false;
                console.log('Error uploading file: ' + err.message || err);
            });
        };

And following code in your view (I also added file type handler for modern browsers):

Upload image <input type="file" data-ng-file-select="onFileSelect($files)" accept="image/png, image/jpeg">
<span data-ng-if="uploadInProgress">Upload progress: {{ uploadProgress }}</span>
<img data-ng-src="uploadedImage" data-ng-if="uploadedImage">

For server side, I used node-multiparty.

And this is what you need in your server side route:

app.route('/upload/image')
    .post(upload.postImage);

And in server side controller:

var uuid = require('node-uuid'),
    multiparty = require('multiparty'),
    fs = require('fs');

exports.postImage = 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 tmpPath = file.path;
        var extIndex = tmpPath.lastIndexOf('.');
        var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
        // uuid is for generating unique filenames. 
        var fileName = uuid.v4() + extension;
        var destPath = 'path/to/where/you/want/to/store/your/files/' + fileName;

        // Server side file type checker.
        if (contentType !== 'image/png' && contentType !== 'image/jpeg') {
            fs.unlink(tmpPath);
            return res.status(400).send('Unsupported file type.');
        }

        fs.rename(tmpPath, destPath, function(err) {
            if (err) {
                return res.status(400).send('Image is not saved:');
            }
            return res.json(destPath);
        });
    });
};

As you can see, I store uploaded files in file system, so I just used node-uuid to give them unique name. If you want to store your files directly in database, you don't need uuid, and in that case, just use Buffer data type. Also please take care of things like adding angularFileUpload to your angular module dependencies.

Kevinleary.net
  • 8,851
  • 3
  • 54
  • 46
Foad Nosrati Habibi
  • 1,134
  • 1
  • 8
  • 13
  • 2
    I believe the require should actually be `var uuid = require('node-uuid')` – Kevinleary.net Nov 06 '14 at 20:54
  • @Foad - thanks for this, really great. For me, this creates a file at `os.tmpDir()` which is then outside of the server root, so the call to fs.rename() fails because it's outside of read/write permissions. Any ideas? – Brian Dec 19 '14 at 20:01
  • 3
    @Brian You can always use __dirname to retrieve the executing directory and choose a relative path in your app's file structure. – Dave Dec 24 '14 at 18:07
  • 1
    @Foad Nosrati Habibi thank you for this solution, I am just geting used to the Mean Stack and don't know where I would be without these examples. I am looking to enhance this solution to include uploads of multiple images, if you have any information on how to do this I would really appreciate it. – Charlie Tupman May 08 '15 at 16:55
  • In your angular, these $scope.upload = $upload.upload({ ::::::::::::::::::: to ::::::::::::::::::::: $scope.upload = Upload.upload({ The $upload doesn't work for me but Upload as document on https://github.com/danialfarid/ng-file-upload#node – olyjosh Jul 16 '16 at 00:19
4

I got ENOENT and EXDEV errors. After solving these, below code worked for me.

var uuid = require('node-uuid'),
multiparty = require('multiparty'),
fs = require('fs');

var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
    var file = files.file[0];
    var contentType = file.headers['content-type'];
    var tmpPath = file.path;
    var extIndex = tmpPath.lastIndexOf('.');
    var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
    // uuid is for generating unique filenames.
    var fileName = uuid.v4() + extension;
    var destPath = appRoot +'/../public/images/profile_images/' + fileName;

    // Server side file type checker.
    if (contentType !== 'image/png' && contentType !== 'image/jpeg') {
        fs.unlink(tmpPath);
        return res.status(400).send('Unsupported file type.');
    }

    var is = fs.createReadStream(tmpPath);
    var os = fs.createWriteStream(destPath);

    if(is.pipe(os)) {
        fs.unlink(tmpPath, function (err) { //To unlink the file from temp path after copy
            if (err) {
                console.log(err);
            }
        });
        return res.json(destPath);
    }else
        return res.json('File not uploaded');
});

for variable 'appRoot' do below in express.js

path = require('path');
global.appRoot = path.resolve(__dirname);
Arvind Kushwaha
  • 759
  • 2
  • 10
  • 18
  • 1
    This works like a charm! :) (express@4.0.0, multiparty@4.1.2, node-uuid@1.4.3) – nabinca Oct 06 '15 at 07:02
  • I think its neccessary you include a way of creating such directory if not existing. I first get this ENOENT error before making modifications as follow var destPath = appRoot + '/public/images/profile_images/'; if (!fs.existsSync(destPath)) { fs.mkdirSync(destPath); } var destPath = destPath+fileName; – olyjosh Jul 16 '16 at 00:11