File Upload with AngularJS and NodeJS

You are going good with your development work and then you have to do a file upload, oops a hurdle. File upload is not as difficult as some people presume it to be. Well not at-least after this tutorial. Let’s flick the hurdle out of our way.
There are two parts of file upload, the client end where we should enable the user to choose a file and send it to the server. At the server, we receive the file and save it into our desired path.
In this AngularJS tutorial we will learn to do file upload with angular and node. These can be seen as two separate parts, so for example, if you are working on AngularJS with some other back-end i.e. not Node.js, you can still take help from this article for the angular part of it and vice versa.
If you are looking for a tutorial with Angular2+ visit this link.

Prerequisite

This article assumes you have already worked with AngularJS and Node expressjs and have a basic knowledge of them.
 

Lets begin

We will be dividing this into two sections, server side with node and the client end with AngularJS.

Backend with NodeJS

We will use multer to handle file upload in our express app. Multer is a popular NodeJS middleware package for handling file uploads. Lets have a look at our complete Server file, I’ll explain parts of it later.
 
APP.JS
    var express = require('express'); 
    var app = express(); 
    var bodyParser = require('body-parser');
    var multer = require('multer');
    app.use(function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "http://localhost");
        res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        next();
    });
    /** Serving from the same express Server
    No cors required */

    app.use(express.static('../client'));
    app.use(bodyParser.json());
    var storage = multer.diskStorage({ //multers disk storage settings
        destination: function (req, file, cb) {
            cb(null, './uploads/')
        },
        filename: function (req, file, cb) {
            var datetimestamp = Date.now();
            cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1])
        }
    });
    var upload = multer({ //multer settings
                    storage: storage
                }).single('file');
    /** API path that will upload the files */
    app.post('/upload', function(req, res) {
        upload(req,res,function(err){
            if(err){
                 res.json({error_code:1,err_desc:err});
                 return;
            }
             res.json({error_code:0,err_desc:null});
        })<br />
    });
    app.listen('3000', function(){
        console.log('running on 3000...');
    });

PACKAGE.JSON
    {
      "name": "expapp",
      "version": "1.0.0",
      "description": "",
      "main": "app.js",
      "scripts": {
        "test": "echo "Error: no test specified" &amp;&amp; exit 1"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "gulp": "3.9.0",
        "gulp-develop-server": "0.5.0",
        "gulp-jshint": "1.12.0"
      },
      "dependencies": {
        "body-parser": "1.14.1",
        "express": "4.13.3",
        "fs": "0.0.2",
        "multer": "1.1.0"
      }
    }
 
To install dependencies just run npm install. Alternatively you can install each module independently and save it to your package.json.
 

Explanation

This section consist explanation of each block of code in our app.js file.
At the top we are requiring our node modules.
 
APP.JS
    app.use(function(req, res, next) { //allow cross origin requests
        res.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS, DELETE, GET");
        res.header("Access-Control-Allow-Origin", "http://localhost");
        res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        next();
    });
    /** Serving from the same express Server
    No cors required */

    app.use(express.static('../client'));
    app.use(bodyParser.json());
Here we are doing two things, we are allowing our express server to accept cross-origin request from another server. (In this case localhost:80)
Alternatively we are asking express to expose client folder as a static path, in this way we can run our AngularJS client code on the same express server (cross-origin wont be required if we follow this).
 
APP.JS
    var storage = multer.diskStorage({ //multers disk storage settings
        destination: function (req, file, cb) {
            cb(null, './uploads/')
        },
        filename: function (req, file, cb) {
            var datetimestamp = Date.now();
            cb(null, file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length -1])
        }
    });
Here we are defining Multer storage settings. Multer supports two type of storage, viz. memory and disk. We are using diskStorage for this tutorial, as memory storage might give problems if the file is too large or multiple files are uploaded very fast.
In the storage setting we give the destination path to save our files. We also rename our file. I’m appending datetime to the name in order to avoid any duplicate naming conflict. Also we need to append the file extension as by default Multer would save the file without any extension.
 
APP.JS
    var upload = multer({ //multer settings
                    storage: storage
                }).single('file');
Now we create a Multer instance by calling multer and passing our options into it. At the same time we specify the type of upload, that is, if it ismultiple files or single. In our case its single, and the parameter ('file') should normally be the name of the input field in our html form but in our case since we are using ngFileUpload in AngularJS it should match the key which holds the file object in the post request.
 
APP.JS
    /** API path that will upload the files */
    app.post('/upload', function(req, res) {
        upload(req,res,function(err){
            if(err){
                 res.json({error_code:1,err_desc:err});
                 return;
            }
             res.json({error_code:0,err_desc:null});
        })
    });
    app.listen('3000', function(){
        console.log('running on 3000...');
    });
Next we create an express route as '/upload' to upload our file. Multer also provides a callback in which we can check if there was an error while performing upload.
Finally we are creating our express server.
 

File API

Each uploaded file object contains the following information.
file object
file object

Run Express server

To start the express server, go to your working directory in cmd and run node app.js. I’ll also bundle up the gulpfile.js with the downloaded code, so if you use gulp for automation, you can just start the server by running gulp.
 
This wraps up the work on backend, now lets move to the front-end stuff.
 

Front-end in AngularJS

We will be using ng-file-upload module in AngularJS for facilitating file uploads.
 

Setting Up

Along with angular.js we will need to include ng-file-upload related files into our project.
Install ng-file-upload using a package manager or download the required files form here.
Include the files into your project.
INDEX.HTML
<script src="angular.min.js"></script>
<script src="ng-file-upload-shim.min.js"></script> <!-- for no html5 browsers support -->
<script src="ng-file-upload.min.js"></script>
Lets have a look at our complete code then I’ll explain each code block in detail. I’ve two files index.html for the markup and main.js for our angular module and controller.
 
INDEX.HTML
<html>
    <head>
        <title>Home</title>
    </head>
    <body ng-app="fileUpload">
        <h1>Angular Node File Upload</h1>
        <form  ng-controller="MyCtrl as up" name="up.upload_form">
                Single Image with validations
            <input 
                type="file" 
                ngf-select 
                ng-model="up.file" 
                name="file" 
                ngf-pattern="'image/*'"
                accept="image/*" 
                ngf-max-size="20MB" 
                />
            Image thumbnail: <img style="width:100px;" ngf-thumbnail="up.file || '/thumb.jpg'"/>
            <i ng-show="up.upload_form.file.$error.required">*required</i><br>
            <i ng-show="up.upload_form.file.$error.maxSize">File too large 
            {{up.file.size / 1000000|number:1}}MB: max 20M</i>
            <button type="submit" ng-click="up.submit()">submit</button>
            <p>{{up.progress}}
        </form>
    </body>
    <script type="text/javascript" src="node_modules/angular/angular.min.js"></script>
    <script type="text/javascript" src="node_modules/ng-file-upload/dist/ng-file-upload.min.js"></script>
    <script type="text/javascript" src="node_modules/ng-file-upload/dist/ng-file-upload-shim.min.js"></script>
    <script type="text/javascript" src="main.js"></script>
</html>
Here we have declared our angular app as fileUpload. We are using controller-as syntax ng-controller="MyCtrl as up". Named our form as name="up.upload_form".
Next, we have added an input type as a file, which will allow us to select files. On top of it, we have added a few directives provided by ng-file-upload, explained as followed.
  • ngf-select
  • : Shows the type of selection is select. Drag and drop is also available.
  • ngf-pattern
  • : Partern to match, type of file.(eg:”‘image/*'” for images)
  • ngf-max-size
  • : Maximum file size allowed to be uploaded.
We have also added ng-model as up.file.
Next, we are displaying the thumbnail of the file selected using the ngf-thumbnaildirective.
Below which we have added error-validation messages to display. Finally, we have the submit button and we are also showing the upload progress.
At the bottom the libraries are loaded and our angular app file main.js.
 
MAIN.JS
    angular.module('fileUpload', ['ngFileUpload'])
    .controller('MyCtrl',['Upload','$window',function(Upload,$window){
        var vm = this;
        vm.submit = function(){ //function to call on form submit
            if (vm.upload_form.file.$valid &amp;&amp; vm.file) { //check if from is valid
                vm.upload(vm.file); //call upload function
            }
        }
        vm.upload = function (file) {
            Upload.upload({
                url: 'http://localhost:3000/upload', //webAPI exposed to upload the file
                data:{file:file} //pass file as data, should be user ng-model
            }).then(function (resp) { //upload function returns a promise
                if(resp.data.error_code === 0){ //validate success
                    $window.alert('Success ' + resp.config.data.file.name + 'uploaded. Response: ');
                } else {
                    $window.alert('an error occured');
                }
            }, function (resp) { //catch error
                console.log('Error status: ' + resp.status);
                $window.alert('Error status: ' + resp.status);
            }, function (evt) { 
                console.log(evt);
                var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
                console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name);
                vm.progress = 'progress: ' + progressPercentage + '% '; // capture upload progress
            });
        };
    }]);
Here we have created our app module and defined our controller.
Inject ngFileUpload as a dependency into our app. ngFileUploadprovides an Upload service which we need to inject into our controller. We store the controller instance in vm.
vm.submit() is the function called on submit of the form. This function validates the form and in turn calls vm.upload().
The Upload service exposes and upload method, which accepts the web API url and other data. data object should contain the file model to be uploaded. Note the key in the data object that holds the file must match the parameter in your multer’s single file instance.
In our example its file at both places.
We are also calculating and storing the file upload progress in vm.progressvariable.
This was it for our client-end implementation, you can run the client app on any localserver or the same express server, see below.

Running our App

Note. These steps may vary if you are not following the same directory structure, ours is as follows, also this structure is only for demonstration purpose, we don’t recommend following it.
DIRECTORY STRUCTURE
./
    client/
        node_modules/
        index.html
        main.js
    server/
        node_modules/
        app.js
        package.json
Steps to run the demo app.
  • Download the code from here OR clone our repository by running git clone https://github.com/rahil471/file-upload-with-angularjs-and-nodejs.git
  • Navigate to the server directory in our app using command-line.
  • Run npm install
  • If you use gulp run gulp OR simply run node app.js
  • Open http://localhost:3000 in your browser

Conclusion

Thus we have seen how to upload files using NodeJS and AngularJSFile Upload is not as difficult as some people assume it to be. This was just a basic example of File upload to point you towards the right direction. Also to note, we have learnt file uploads on both ends(back-end and front-end) and the tutorial of each can be used independently of one another. Eg. You can use Multer file upload with some other front-end framework of your choice similarly you can use ngFileUpload with some other back-end technology of your choice. There is a lot more to file uploads like multiple files, drag and drop, etc. You can know more about it by following our reference links below.

Comments

Popular posts from this blog

AngularJS unit testing tutorial with karma-jasmine

Search Sort and Pagination in ng-repeat – AngularJS