Build a MEAN Stack File Uploader App with Filestack

Introduction

Filepicker is a web service which helps us developers smoothly handle file uploading. An amazing feature it provides is to let users to choose the source from where to upload a picture, so you can easily upload a picture from your computer, Dropbox, Google Drive, Facebook and even from the web.

As of December 17th their name actually changed to Filestack, you can find more news about it by visiting their blog post here.

Scotch already offers a good JQuery tutorial to get familiar with filepicker but this time we are going to build a MEAN app to see easily our uploader can integrate with Angular.

Throughout this tutorial, we are gonna build a single-page MEAN app to add and retrieve information about superheroes. Why superheroes?! Well, because filepicker (Filestack from now on) is actually our superhero upload files handler.

Here you may find the source code for the app we are gonna develop.

Ok, no more fluff, let's start the tutorial by surfing the official website and getting our API key.

Register and get the API key

The free plan is now slightly different than it was before and now allows you the following monthly:

  • 3GB of bandwidth.
  • 250 files to upload.
  • 300 Transformations.

This is definitely more than enough for our purposes.

Here is the homepage of Filestack:

I warmly suggest new users to spend some time checking out the website as Filestack really comes with tons of features:

  • Upload from different sources such as Facebook, Instagram, Dropbox, Google-Drive etc.
  • Easy integration of your S3 bucket by S3 Import tool.
  • Filestack's transformation engine provides several choices to apply filters, resize and crop your images, add watermarks and even facial detection.
  • Audio and video transformations (like transcoding) are also available.
  • Filestack viewer allows users to look at files directly in your web page

And much more.

...Ready?

On the top right of the page click on "Try it free" and fill the form to register as free user. You should see something like this:

Awesome! You should now be in the developer portal where you may add new applications, add details to them, receive new API keys, and so on.

Note the pop-up, the developers of Filestack are so nice to give you the code to easily integrate the uploader in your application: You just need to copy and paste the library source link and the widget to see Filestack in action. The widget already comes with the API key included in the attribute data-fp-apikey.

Whenever you need the API key, just go to the developer portal and there you can see your list of applications with related keys.

Ok... No more talk. Let's code.

Prerequisites

App directory

Let's create the folder directory this way:


-- app  
---- models 
------ superhero.js
---- routes
------ superhero.js

-- public 
---- index.html 
---- app.js
---- controllers
---- partials

-- server.js
-- package.json
-- bower.json
-- .bowercc

Install the packages

Then, open the package.json and insert the following code:

 {
    "name": "superheroApp",
    "main": "server.js",
    "dependencies": {
     "body-parser": "^1.15.0",
     "express": "^4.13.4",
     "mongoose": "^4.4.8",
     "morgan": "^1.7.0"
   }
}

That's pretty much all we need for the backend part, so in the command line point to the app folder and run npm install.

For the frontend libraries, we run bower. First of all, edit the file .bowercc to create the folder /bower_component under /public. Copy and paste this code into the file and save:

{
    "directory" : "public/bower_components"
}

Finally, open the bower.json file and paste the following code:

{
    "name": "superheroApp",
    "main": "server.js",
    "dependencies": {
     "angular-filepicker": "^1.1.4",
     "bootstrap": "^3.3.6",
     "angular": "^1.5.2",
     "angular-route": "^1.5.2"
    }
}

If you take a look at the dependencies, you may notice there is one called angular-filepicker. We are going to use angular-filepicker to integrate the filestack functionalities in our app as an Angular service, but I will talk about it in detail later.

So, let's run bower install and we can start to write the server.

The server

Superhero model

Now that we have the module installed, let's start by defining our superhero model. In the /models folder create a file called superhero.js and paste the following code:

// Dependencies
var mongoose    = require('mongoose');
var Schema      = mongoose.Schema;

// Defines the superhero schema
var SuperheroSchema = new Schema({
    name: {type: String, required: true},
    gender: {type: String, required: true},
    superPowers: {type: String, required: true},
    picture: {type: Schema.Types.Mixed, required: true},
    morePictures: Schema.Types.Mixed, // this is not required
    createdAt: {type: Date, default: Date.now},    
});

// Sets the createdAt parameter equal to the current time
SuperheroSchema.pre('save', function(next){
    now = new Date();
    if (!this.createdAt) {
        this.createdAt = now;
    }
    next();
});

// Exports the SuperheroSchema for use elsewhere.
module.exports = mongoose.model('superhero', SuperheroSchema);

As we work with MongoDB, we take advatange of Mongoose which makes the connection with MongoDB very easy to handle.

The superhero schema is quite simple, it consists of the superhero name, gender, super powers (a simple description is enough), the main picture, optional pictures stored in morePictures and the creation date.

Let me highlight one thing: The fields picture and morePictures are defined as mixed. Filestack, once the picture is uploaded, returns an object with different properties. For the purpose of this tutorial, we simply save it directly in the database without extracting URL of the picture from it.

Server.js

The server file is very easy too. Think about it: Our app allows the user to create a new superhero, display them in a gallery, and show the details of a single superhero. For this, we only need three routes.

Copy and paste this code into your server.js file:

var express = require('express');
var mongoose = require('mongoose');
var port = process.env.PORT || 3000;
var morgan = require('morgan');
var bodyParser = require('body-parser');
var app = express();
var path = require('path');

var superhero = require('./app/routes/superhero')();

// Just some options for the db connection
var options = { server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, 
                replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } } }; 

mongoose.connect('YOUR_DB_LOCATION', options);
var db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));

// Log with Morgan
app.use(morgan('dev'));

// parse application/json and look for raw text                                   
app.use(bodyParser.json());                                     
app.use(bodyParser.urlencoded({extended: true}));               
app.use(bodyParser.text());                                    
app.use(bodyParser.json({ type: 'application/json'}));  

// Static files
app.use(express.static(__dirname + '/public')); 

app.route('/superhero')
    .post(superhero.post)
    .get(superhero.getAll);
app.route('/superhero/:id')
    .get(superhero.getOne);

app.listen(port);
console.log('listening on port ' + port);

After the dependencies declaration we connect to mongoDB, so substitute 'YOUR_DB_LOCATION' with the location of your database. In my case I used mLab Database-as-a-Service as they provide a free plan which suits the needs of our app.

We use bodyParse to parse the client requests as JSON format to store the superhero.

As you can see, we defined a route /superhero with a GET and POST as well as /superhero/:id with only a GET.

NB in the superhero routes section I am gonna explain the behavior of each of the 3 callbacks you can see in the routes.

  • A GET request to /superhero returns all the superheroes stored in the database.
  • A POST request to /superhero creates a new superhero into the database.
  • A GET request to /superhero/:id returns a single superhero with id = :id.

Time to write some routes...

Superhero routes

In the /routes folder create a new file called superhero.js and paste the following code:

// Dependencies
var mongoose  = require('mongoose');
var Superhero = require('../models/superhero');
// App routes
module.exports = function() {
    return {
        /*
         * Get route to retrieve all the superheroes.
         */
        getAll : function(req, res){
            //Query the DB and if no errors, send all the superheroes
            var query = Superhero.find({});
            query.exec(function(err, superheroes){
                if(err) res.send(err);
                //If no errors, send them back to the client
                res.json(superheroes);
            });
        },
        /*
         * Post route to save a new superhero into the DB.
         */
        post: function(req, res){
            //Creates a new superhero
            var newSuperhero = new Superhero(req.body);
            //Save it into the DB.
            newSuperhero.save(function(err){
                if(err) res.send(err);
                //If no errors, send it back to the client
                res.json(req.body);
            });
        },
        /*
         * Get a single superhero based on id.
         */
        getOne: function(req, res){
            Superhero.findById(req.params.id, function(err, superhero){
                if(err) res.send(err);
                //If no errors, send it back to the client
                res.json(superhero);
            });     
        }
    }
};  

Let's talk about the 3 functions used as callbacks:

  • getAll, as the name says, queries the database and returns all the superheroes stored in it.
  • post on the other hand is in charge to store the newly created superhero into database.
  • The last function, getOne, first retrieves the id sent by the client and returns the superhero associated to that id.

Great! We are now ready to test the backend with POSTMAN.

The server in action

We first run the server, then test it by adding a superhero and then retrieve it.

In the following section we are gonna use POSTMAN as we can test the server by creating and sending any HTTP request without implementing any client-side of the app.

You can download it for free as a browser app so check your browser web store in case.

Run the server

In the command prompt go to the app folder and run node server.js. If all went well, you should see the server saying listening on port 3000.

Add a superhero

Let's now open POSTMAN from the browser Apps and add a test superhero.

  1. Set a POST request to http://localhost:3000/superhero.
  2. In the headers tab, add Content-type as application/json.

In the body tab we send the superhero information. As said before, the picture field is an object but right now we do not know what kind of properties the filestack-returned object owns, do we? However, for the purpose of the test we can just imagine that the object contains the url and the filename of the uploaded picture. Copy this in the body a send it to the server.

You should see the answer from the server. Remember? Once saved into the DB the server returns the superhero back to the client.

So in POSTMAN you should see the superhero back to you! ...with status 200 of course.

Some of you guys may wonder why I did not add the morePictures field. Well, because it is not required, but you can add it and test again. Filestack, for multiple files, will simply send you back and array of objects. So feel free to play around in POSTMAN.

Get superheroes

Ok let's retrieve the superhero.

We don't have the id created by mongoDB, so for now we only make a GET request to /superhero. Since we only added a single superhero, more or less, it's like retrieving it by a given id: Set again the header Content-type equal to application/json and if everything goes well, you should get the superhero back.

Good news, the server this time returns the _id too. Why don't you try to get the superhero by GET request to /supehero/:id?

About time to create the client frontend.

The client

Remember the developer portal of Filestack? They kindly provided us an example code for including their widget in our app. Let's try it.

Filestack Widget

Open the index.html and paste the following code:

<script type="text/javascript" src="//api.filestackapi.com/filestack.js"></script>

<input type="filepicker" data-fp-apikey="YOUR_API_KEY"
onchange="console.log(event.fpfile)">

Do not forget to substitute YOUR_API_KEY with the real key you are provided by Filestack. In addition, instead of alerting the url from the onchange listener, we show the complete result in the console log.

Now run the server and from the browser go to http://localhost:3000. You should have the following result:

The widget comes as a button to upload files. Try to click it and upload whatever file you want from the your favorite location (have you noticed how many sources?!).

Once done, open the development tools of your browser and check the console:

It returned an object with a lot of information related to the picture we have just uploaded, exactly as expected!

A better frontend UI

For the purpose of our application, we are not using the widget, but instead we are gonna create our own upload button and use angular-filepicker to run the communication with Filestack webservice.

Here is the link to the Github project for angular-filepicker, it comes with an interesting demo app which shows the usage, so I again suggest a new user to spend some time reading the documentation and get familiar with it.

The UI of our app consists of a page where the user can add a superhero, a gallery to list all of the added superheroes, and a detail page for the chosen superhero.

First let's create the "wrapper" for our routes into index.html. Copy and paste this code into your index.html:

<!DOCTYPE HTML>
<html ng-app="superheroApp">
<head>
    <meta charset="utf-8">
    <title>Superheroes App!</title>
    <meta name="description" content="Superhero database">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSS -->
    <link rel="stylesheet" href="./bower_components/bootstrap/dist/css/bootstrap.min.css"/>

    <!-- JS libraries -->

    <script src="./bower_components/angular/angular.js"></script>   
    <script src="./bower_components/angular-route/angular-route.js"></script>
    <script src="./bower_components/filepicker-js/filepicker.js"></script>
    <script src="./bower_components/angular-filepicker/dist/angular_filepicker.js"></script>

    <!-- Angular files -->
    <script src="./app.js"></script>    

    <script src="./controllers/addSuperheroController.js"></script>

</head>
<body>
<div class="container">
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">Superheroes MEAN app</a>
            </div>

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav navbar-right">
                <li role="presentation"><a href="/#/addSuperhero">Add Superhero</a></li>
                <li role="presentation"><a href="/#/gallery">Gallery</a></li>
              </ul>
            </div>
        </div>
    </nav>
    <!-- Here is where the partials will be displayed -->
    <div ng-view></div>
</div>
</body>
</html>

Let me explain the HTML:

  • We included the css and js libraries required to run the app as well as the app.js and the first controller addSuperheroController. This will be used to control the addSuperhero view.
  • We defined the navigation bar and the div with ng-view which is where the views will be routed. Since we are developing a single-page app, ng-view basically tells angular where the views we define will be attached, thus there is no need to ask the server for a new page.

Add a superhero

Now open the app.js file and paste the following code:

//Main file
var app = angular.module('superheroApp', ['addSuperheroCtrl',  'ngRoute', 'angular-filepicker'])
    .config(function($routeProvider, filepickerProvider){
        //The route provider handles the client request to switch route
        $routeProvider.when('/addSuperhero', {          
            templateUrl: 'partials/addSuperhero.html',
                        controller: 'addSuperheroController'            
        })
        //Redirect to addSuperhero in all the other cases.
        .otherwise({redirectTo:'/addSuperhero'});
        //Add the API key to use filestack service
        filepickerProvider.setKey('YOUR_API_KEY');
});

As you can see, in the main module we included ngRoute and angular-filepicker: The first helps in switching views while the second handles the connection with Filestack webservices.

We defined the first route /addSuperhero which goes to partials/addSuperhero.html and has a controller associated addSuperheroController.

Regarding Filestack, our filepickerProvider has a method called setKey which defines the key *globally* so we do not need to add it in all our upload buttons.

Finally, create the file addSuperheroController.js in the folder /public/controllers and paste the following code:

var addCtrl = angular.module('addSuperheroCtrl', []);
addCtrl.controller('addSuperheroController', function($scope, $http, filepickerService){
    $scope.superhero = {};
    //Send the newly created superhero to the server to store in the db
    $scope.createSuperhero = function(){
        $http.post('/superhero', $scope.superhero)
            .success(function(data){
                console.log(JSON.stringify(data));
                //Clean the form to allow the user to create new superheroes
                $scope.superhero = {};
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };
    //Single file upload, you can take a look at the options
    $scope.upload = function(){
        filepickerService.pick(
            {
                mimetype: 'image/*',
                language: 'en',
                services: ['COMPUTER','DROPBOX','GOOGLE_DRIVE','IMAGE_SEARCH', 'FACEBOOK', 'INSTAGRAM'],
                openTo: 'IMAGE_SEARCH'
            },
            function(Blob){
                console.log(JSON.stringify(Blob));
                $scope.superhero.picture = Blob;
                $scope.$apply();
            }
        );
    };
    //Multiple files upload set to 3 as max number
    $scope.uploadMultiple = function(){
        filepickerService.pickMultiple(
            {
                mimetype: 'image/*',
                language: 'en',
                maxFiles: 3, //pickMultiple has one more option
                services: ['COMPUTER','DROPBOX','GOOGLE_DRIVE','IMAGE_SEARCH', 'FACEBOOK', 'INSTAGRAM'],
                openTo: 'IMAGE_SEARCH'
            },
      function(Blob){
                console.log(JSON.stringify(Blob));
                $scope.superhero.morePictures = Blob;
                $scope.$apply();
            }
        );
    };  
});

What does this controller actually do?

The controller takes care of all the data inserted by the user and sends them to the server. Moreover, it handles the file upload through the two methods upload() and uploadMultiple().

Thanks to filepickerService included in the controller we can call the functions pick() and pickMultiple(). The two functions accept an object which specifies the options for uploading. If you take a look at the documentation you may notice there are loads of them.

In our case let's take a look at the pick function options:

  • mimetype: you can define what kind of files to upload, in my case I only allow pictures of any kind (jpeg, png...).
  • language: the language displayed by Filestack modal/window, if not defined it will try to get the language from the browser settings. If it fails, it sets it to English. In our case we directly set it to English.
  • services: Filestack gives us the complete control to choose the source from where the user can upload a file. Again, the are a lot of choices but for this tutorial we choose to limit the source locations to computer, Dropbox, Google Drive, web search, Facebook and Instagram.
  • openTo: We can even choose which source location to show first. In our case the web search.

Don't hesitate to customize them to get some practice with it!

The function also allows us to enter some callbacks in case of success, error and progress. We simply define a success function which stores the returned object into the picture property of our superhero.

NB $scope.$apply() updates $scope and the view by showing the thumbnail of the picture we have just uploaded.

Basically the same behavior happens with pickMultiple with a single difference:

We also defined the max number of files to be uploaded, 3, by adding maxFiles. The returned array of objects is stored into the morePictures property of superhero and again we run $scope.$apply.

The last function createSuperhero uses the HTTP service to POST the newly created superhero to the server for consequent storage.

Now let's see the view in action. Create a new file addSuperhero.html in the folder public/partials and paste the following code:

<div class="col-md-8 col-md-offset-2">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h2 class="panel-title text-center">
                <span class="glyphicon glyphicon-user"></span>Superhero form 
            </h2>
        </div>
        <div class="panel-body">
            <!-- The form to add superheroes information -->
            <form name ="addForm" novalidate>
                <div class="form-group">
                    <label for="username">Name</label>
                    <input type="text" class="form-control" placeholder="Deadpool?" ng-model="superhero.name" required>
                </div>

                <label class="radio control-label">Gender</label>
                <div class="radio" >
                    <label>
                        <input type="radio" name="optionsRadios" value="Male" ng-model="superhero.gender" >
                        Male
                    </label>
                </div>
                <div class="radio" >
                    <label>
                        <input type="radio" name="optionsRadios" value="Female" ng-model="superhero.gender" required>
                        Female
                    </label>
                </div>
                <div class="form-group">
                    <label for="username">Super Powers</label>
                    <input type="text" class="form-control" placeholder="SenseOfHumor" ng-model="superhero.superPowers" required>
                </div>
                <div class="form-group">
                    <label for="picture">Main picture</label>
                    <div class="text-center">
                        <button type="button" class="btn btn-default" ng-click="upload()">
                            Upload <span class="caret"></span>
                        </button>
                        <div style="margin-top:10px;">
                            <!-- Show the thumbnail only when the picture is uploaded -->
                            <a href="{{superhero.picture.url}}" class="thumbnail" ng-if="superhero.picture">
                            <!-- the picture is rendered with width: 500 and sharpened -->
                            <img ng-src="{{superhero.picture.url | fpConvert: {filter:'sharpen'} }}">
                            </a>
                        </div>                  
                    </div>
                </div>
                <div class="form-group">
                    <label for="picture">More pictures? (Max 3, optional!)</label>
                    <div class="text-center">
                    <button type="button" class="btn btn-default" ng-click="uploadMultiple()">
                    Upload <span class="caret"></span>
                    </button>
                    <!-- Show the thumbanil of more pictures -->
                        <div style="margin-top:10px;" ng-repeat="picture in superhero.morePictures">     <div class="col-md-{{12 / superhero.morePictures.length}}">
                            <a href="{{picture.url}}" class="thumbnail">
                              <img ng-src="{{picture.url | fpConvert: {filter:'sharpen'} }}">
                            </a>
                        </div>  
                    </div>
                    </div>
                </div>
                <hr />              
                <button type="submit" class="btn btn-danger btn-block"  ng-click="createSuperhero()" ng-disabled="superhero.$invalid || !superhero.picture">Submit</button>             
            </form>
        </div>
    </div>
</div>

It's a form where the user can enter the superhero information.

Note that after both the upload buttons we will show the uploaded pictures in a thumbnail. Thanks to ng-if these divs are initially hidden as we don't have uploaded pictures.

Another cool feature of Filestack is the possibility to apply image transformations such as crop, resize, filter etc. Angular-filepicker can exploit this feature and if you read the code above you may have noticed that after inserting the URL of the picture we added a filter called fpConvert:

We can define some image transformations to the picture to show! For the example we used the sharpen filter.

Finally, notice the ng-repeat in the div for uploading more pictures: It creates a number of thumbnails based on the number of pics the user created and make sure they fit the view.

Ok, save the files, run the server and in the browser run http://localhost:3000, you should see this:

Great, now try to add as many superheroes as you can and do not forget to check the console of the browser. If you successfully saved the superhero you should get it back from the server in the console.

We still don't have the gallery view but if you use POSTMAN and send a GET request to /superhero you should get back all of the superheroes you added.

Gallery

Let's create the gallery now.

First update the index.html to add the galleryController right under the previous one:

<!-- Here is the addSuperheroController.js -->
<script src="./controllers/galleryController.js"></script>

Second, update the app.js to include the new controller and the add a new route. Paste the following code to replace the one into app.js:

//Main file
var app = angular.module('superheroApp', ['addSuperheroCtrl', 'galleryCtrl', 'ngRoute', 'angular-filepicker'])
    .config(function($routeProvider, filepickerProvider){
        //The route provider handles the client request to switch route
        $routeProvider.when('/addSuperhero', {          
            templateUrl: 'partials/addSuperhero.html',
            controller: 'addSuperheroController'            
        })
        .when('/gallery', {
            templateUrl: 'partials/gallery.html',
            controller: 'galleryController'
        })
        //Redirect to addSuperhero in all the other cases.
        .otherwise({redirectTo:'/addSuperhero'});
        //Add the API key to use filestack service
        filepickerProvider.setKey('AynkfxksOQNSa83fviAQKz');
});

In public/controllers create galleryController.js and paste the following code:

var galleryCtrl = angular.module('galleryCtrl', []);
galleryCtrl.controller('galleryController', function($scope, $http){
    $scope.superheroes = [];
    //Retrieve all the superheroes to show the gallery
    $http.get('/superhero')
        .success(function(data){
            console.log(JSON.stringify(data));
            $scope.superheroes = data;
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });

});

The controller initially sends a GET request to the server to get all the superheroes which will be showed in the view.

Finally, create a new file in public/partials called gallery.html and paste the following code:

<!-- view the gallery of all the superheroes in the db -->
<div class="row">
<div ng-repeat="superhero in superheroes">
    <div class="col-md-4 col-xs-6  col-sm-offset-0">            
    <div class="thumbnail">
      <img ng-src="{{superhero.picture.url | fpConvert: {filter:'sharpen', w:300, h:150} }}" />
      <div class="caption text-center">
        <h3>{{superhero.name}}</h3>
        <p><label>Super powers: </label> {{superhero.superPowers}}</p>
        <div class="text-right"><a ng-href="/#/detail/{{superhero._id}}" class="btn btn-danger" role="button">View</a> </div>
      </div>
    </div>
    </div>  
</div>
</div>

The code is straightforward:

For each superhero it creates a div element which shows the main picture (this time I also set the width in the fpConvert to 300px), the name, the super powers, and a button to take a look at the detail:

By clicking on the button we will be redirected to the detail page for a single superhero (notice the id passed to the link).

Save all the files and run the app again, once you click on gallery in the navigation bar you should this:

That's the list of superheroes I personally created.

Superhero details

We are almost at the end of the tutorial, it's time to create the last view.

Again, first add the detailController to index.html right after the galleryController:

<!-- Here is the galleryController.js -->
<script src="./controllers/detailController.js"></script>

Let's update app.js to include the controller and the last route to go to details. Copy and paste this code into your file:

//Main file
var app = angular.module('superheroApp', ['addSuperheroCtrl', 'galleryCtrl','detailCtrl', 'ngRoute', 'angular-filepicker'])
    .config(function($routeProvider, filepickerProvider){
        //The route provider handles the client request to switch route
        $routeProvider.when('/addSuperhero', {          
            templateUrl: 'partials/addSuperhero.html',
            controller: 'addSuperheroController'            
        })
        .when('/gallery', {
            templateUrl: 'partials/gallery.html',
            controller: 'galleryController'
        })
        .when('/detail/:id', {
            templateUrl: 'partials/detail.html',
            controller: 'detailController'
        })
        //Redirect to addSuperhero in all the other cases.
        .otherwise({redirectTo:'/addSuperhero'});
        //Add the API key to use filestack service
        filepickerProvider.setKey('AynkfxksOQNSa83fviAQKz');
});

Create the new controller galleryController in public/controller and paste the following code:

var detailCtrl = angular.module('detailCtrl', []);
detailCtrl.controller('detailController', function($scope, $http, $routeParams){
    $scope.superhero = {};
    //get the id to query the db and retrieve the correct superhero
    var id = $routeParams.id;
    $http.get('/superhero/' + id)
        .success(function(data){
            console.log(JSON.stringify(data));
            $scope.superhero = data;
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });     
});

As we can see, the controller simply sends a GET request including the id of the superhero we are looking for and send back his/her information to the client.

Finally, create a new view called detail.html in public/partials and paste the following code:

<!-- View the details of a superhero -->
<div class="col-md-8 col-md-offset-2">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h2 class="panel-title text-center"><span class="glyphicon glyphicon-user"></span> Superhero details </h2>
        </div>
        <div class="panel-body text-center">
            <img ng-src="{{superhero.picture.url | fpConvert: {filter:'sharpen', w:500} }}" />
            <h2>{{superhero.name}}</h2>
            <hr />
            <p class="text-left"><label>gender: </label> {{superhero.gender}}</p>
            <p class="text-left"><label>Super powers: </label> {{superhero.superPowers}}</p>
            <!-- Only if there are other pictures -->
            <div ng-if="superhero.morePictures">
                <h3> More Pictures <h3>
                <div ng-repeat="picture in superhero.morePictures">
                    <div class="col-md-{{12 / superhero.morePictures.length}}">
                        <a ng-href="{{picture.url}}" class="thumbnail">
                          <img ng-src="{{picture.url | fpConvert: {filter:'sharpen', w:500} }}">
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

This time the view shows all the information, gender, and all the pictures included. Again, with fpConvert we resized the pictures to have width = 500 and we show the more pictures div only if the superhero has them.

Refresh the browser, click on gallery and select a superhero, you should be able to see the full details now!

Conclusion

In this tutorial we created a simple MEAN app and included filestack in webservice to facilitate the users to upload their own pictures.

Filestack is very easy to use, easy to integrate, and with angular-filepicker we only need to include the service to experience all of its features.

Filestack does not only limit itself to upload pictures, we can also upload videos, audio files, documents such as pdf or office files etc. The documentation is rich with examples and very well written, so I suggest taking a look to anyone interested in diving in deeper.

For now... Congratulations, we finished our app!

Samuele Zaza

I am a full-stack web developer working for Taroko Software as front-end web developer and Filestack Tech Evangelist. When not coding I may be spotted in a gym lifting or planning to conquer the world LOL.