file upload with angularjs and nodejs

angular and node are really hot technologies recently.

The issue i am tackling today is uploading file from angular frontend to nodejs backend. The file i am working with is avatar(image), but it tech should be able to applied to any file.

There are a lot of options on both sides.

Frontend angularJs

angular-file-upload

https://github.com/danialfarid/angular-file-upload#php

This directive is easy to use and not as fancy as others. These are the reason i choose it.

ng-flow

flow js is another option that many ppl are using.

https://github.com/flowjs/ng-flow

I just found it has too many features and a little bit over-complicated. Thing like chunking file transfer is not something i really need here. What’s more the documentation and examples are not quite useful that I have to dig into the source code to understand how some directive attribute work. If you need some of its features like chunking / image preview, use it.

Backend Nodejs

Most of them are based on Busyboy

Formidable

https://github.com/felixge/node-formidable

Formidable has been there for a long time. I choose it because of its easy to use and good documentation. It does not have that much features as the other options but far enough for me since I just need to be able to parse the multi-part request, get the fields and files.

multiparty

https://github.com/andrewrk/node-multiparty/

Multiparty is another middleware to parse http requests wit multipart/form-data. The functionality and API are very similar to Formidable. But with some new features like you can chain multiple callbacks for Parse. It also has support for the AWS s3, which might be very useful to those who uses cloud.

Multer

https://github.com/expressjs/multer

multer is quite new and is actively maintained. I might switch to it later if I have more time. It is also recommended by the Express team as the middleware for replacing the old and ugly express-multipart, which is the integrated in Express 3 but no long bundled in Express 4.

Implementation

html snippet

Use the angular-file-upload directive. Each time a file is added, it triggers the upload(files) method in controller.

<div style="margin-left: 400px; margin-top: 100px;" >
                <label for="uploadWidget"> Upload Image</label>
                <div id="uploadWidget" class="btn btn-default" ng-file-select ng-file-change="upload($files)">Select File</div>
            </div>

Angular controller
The $upload is something we get from the directive and could be injected here. The upload function will upload the file with some metadata to the server’s ‘/api/uploadAvatar’ endpoint. progress() is optional. success() will accept the callback with the response stored in ‘data’.

contactControllers.controller('MemberEditController', ['$scope', '$routeParams', 'MemberResource', '$location', '$window', '$upload',
    function ($scope, $routeParams, MemberResource, $location, $window, $upload) {
        $scope.upload = function (files) {
            if (files && files.length) {
                $upload.upload({url: '/api/uploadAvatar', fields: {username: $scope.member.username}, file: files[0]}).progress(function (event) {
                    var progressPercentage = parseInt(100.0 * event.loaded / event.total);
                    console.log('progress: ' + progressPercentage + '% ' + event.config.file.name);
                }).success(function (data, status, headers, config) {
                    console.log('file ' + config.file.name + 'uploaded. Response: ' + JSON.stringify(data));
                    $scope.member.photo = data.path;
                });
            }
        };

NodeJs
in the server side, we first config the route for the ‘/api/uploadAvatar’ that we have above so that we could handle the request. restImpl is a file that i have for holding my rest implementations. Below is the code in the nodejs bootstrap app.js/server.js

var restImpl = require('./routes/restImpl');

app.post('/api/uploadAvatar', restImpl.uploadAvatar);

in the restImpl js, we do the real work.
The logger below is an instance of winston. Look at my another POST for configuring logging for nodejs.

In the uploadAvatar function, we get a formidable instance which is used to parse the incoming request so that we could get all the files and fields out of it. Then we could do all the processing in the callback. we 1st get the file, then the current path for the temp file and move the file from the temp location to the target path. Finally we return the new file’s path to the client so that we could handle it on the angularjs(the ‘data’ above in the success callback).

var formidable = require('formidable');
var path = require('path');
var fs = require('fs');
var logger = require('winston');
//handle avatar upload
exports.uploadAvatar = function (req, res) {
    var form = new formidable.IncomingForm();
    form.parse(req, function (err, fields, files) {
        var file = files.file;
        var username = fields.username;
        var tempPath = file.path;
        var targetPath = path.resolve('./public/photos/' + username + '/' + file.name);
        fs.rename(tempPath, targetPath, function (err) {
            if (err) {
                throw err
            }
            logger.debug(file.name + " upload complete for user: " + username);
            return res.json({path: 'photos/' + username + '/' + file.name})
        })
    });
};
Advertisements

9 comments

  1. Pingback: deploy nodejs angularjs mongodb expressjs application to openshift | Life in USA
  2. simon · October 30, 2015

    Hello, I have a small problem , the file can not

    HTML

    Upload Image
    Select File

    UPLOAD.JS
    var app = angular.module(‘fileUpload’, [‘ngFileUpload’]);
    var version = ‘9.0.3’;

    app.controller(‘MemberEditController’, [‘$scope’, ‘$routeParams’, ‘MemberResource’, ‘$location’, ‘$window’, ‘$upload’,
    function ($scope, $routeParams, MemberResource, $location, $window, $upload) {
    $scope.upload = function (files) {
    if (files && files.length) {
    $upload.upload({url: ‘/api/uploadAvatar’, fields: {username: $scope.member.username}, file: files[0]}).progress(function (event) {
    var progressPercentage = parseInt(100.0 * event.loaded / event.total);
    console.log(‘progress: ‘ + progressPercentage + ‘% ‘ + event.config.file.name);
    }).success(function (data, status, headers, config) {
    console.log(‘file ‘ + config.file.name + ‘uploaded. Response: ‘ + JSON.stringify(data));
    $scope.member.photo = data.path;
    });
    }
    }; ….

    SERVER.JS
    var formidable = require(‘formidable’),
    http = require(‘http’),
    util = require(‘util’);

    http.createServer(function(req, res) {
    if (req.url == ‘/api/uploadAvatar’ && req.method.toLowerCase() == ‘post’) {
    // parse a file upload
    var form = new formidable.IncomingForm();

    form.parse(req, function(err, fields, files) {
    res.writeHead(200, {‘content-type’: ‘text/plain’});
    res.write(‘received upload:\n\n’);
    res.end(util.inspect({fields: fields, files: files}));
    });

    return;
    }

    // show a file upload form
    res.writeHead(200, {‘content-type’: ‘text/html’});
    res.end(
    ”+
    ”+
    ”+
    ”+

    );
    }).listen(8080);
    console.log(“Server running on port 8080”);

    I create folders /api/uploadAvatar

    No tranfer,

    Thanks

    • LEON · October 30, 2015

      I am not sure what you meant`~~ would u elaborate it ?

  3. simon · October 30, 2015

    Hello, I have a small problem , the file can not

    HTML

    Upload Image
    Select File

    UPLOAD.JS
    var app = angular.module(‘fileUpload’, [‘ngFileUpload’]);
    var version = ‘9.0.3’;

    app.controller(‘MemberEditController’, [‘$scope’, ‘$routeParams’, ‘MemberResource’, ‘$location’, ‘$window’, ‘$upload’,
    function ($scope, $routeParams, MemberResource, $location, $window, $upload) {
    $scope.upload = function (files) {
    if (files && files.length) {
    $upload.upload({url: ‘/api/uploadAvatar’, fields: {username: $scope.member.username}, file: files[0]}).progress(function (event) {
    var progressPercentage = parseInt(100.0 * event.loaded / event.total);
    console.log(‘progress: ‘ + progressPercentage + ‘% ‘ + event.config.file.name);
    }).success(function (data, status, headers, config) {
    console.log(‘file ‘ + config.file.name + ‘uploaded. Response: ‘ + JSON.stringify(data));
    $scope.member.photo = data.path;
    });
    }
    }; ….

    SERVER.JS
    var formidable = require(‘formidable’),
    http = require(‘http’),
    util = require(‘util’);

    http.createServer(function(req, res) {
    if (req.url == ‘/api/uploadAvatar’ && req.method.toLowerCase() == ‘post’) {
    // parse a file upload
    var form = new formidable.IncomingForm();

    form.parse(req, function(err, fields, files) {
    res.writeHead(200, {‘content-type’: ‘text/plain’});
    res.write(‘received upload:\n\n’);
    res.end(util.inspect({fields: fields, files: files}));
    });

    return;
    }

    // show a file upload form
    res.writeHead(200, {‘content-type’: ‘text/html’});
    res.end(
    ”+
    ”+
    ”+
    ”+

    );
    }).listen(8080);
    console.log(“Server running on port 8080”);

    I create folders /api/uploadAvatar

    Thanks

  4. Mouhamad Ounayssi · December 9, 2015

    I have got the error below, using AngularJS for the Front End interface and NodeJS for the back end:

    Adding some extra console for debugging).

    POST http://localhost:3000/updateuserprofilepicture
    (index):1 XMLHttpRequest cannot load http://localhost:3000/updateuserprofilepicture. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost’ is therefore not allowed access. The response had HTTP status code 400.
    app.js:6161 error
    app.js:6162 null

    • LEON · December 9, 2015

      What browser are you using? Chrome?

      If you want everyone to be able to access the Node app, then try using

      res.header(‘Access-Control-Allow-Origin’, “*”)

      That will allow requests from any origin. The CORS enable site has a lot of information on the different Access-Control-Allow headers and how to use them.

      I you are using Chrome, please look at this bug(https://code.google.com/p/chromium/issues/detail?id=67743) regarding localhost and Access-Control-Allow-Origin. There is another StackOverflow question(http://stackoverflow.com/questions/10883211/deadly-cors-when-http-localhost-is-the-origin) that details the issue.

      • Mouhamad Ounayssi · December 10, 2015

        Kindly;

        The “res.header(‘Access-Control-Allow-Origin’, “*”)” is already added to NodeJS.
        The problem that’s angulsrJS sends the response as a JSON Format, but we need the request header to be sending as a form-data. The others requests are working properly, the problem is coming when we are trying to send a files(images,videos,audio,documents) within the request.

      • LEON · December 10, 2015

        It’s kind of messy here. Do you mean you want to send header in the body as for form-data? Is that allowed in the http protocol… I never tried that…

      • Mouhamad Ounayssi · December 11, 2015

        Yes, exactly.

        If you know about PostMan extension from Chrome Browser, i tested the procedure call using this extension and it’s working properly, i had got a response and the files had been uploaded.

        The problem is when i’m trying using AngularJS, it’s prompting that error.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s