Getting MEAN with Ionic 2

Building a Review App with Ionic 2, MongoDB & Node



·

Throughout your adventures in web and mobile development, you may have heard of the MEAN stack, which (aside from sounding pretty badass) stands for MongoDB, Express.js, Angular, and Node.js. All of these technologies work together to allow you to create an application with a frontend and a backend.

In this tutorial we will be creating a simple review application in Ionic 2 powered by the MEAN stack. In a previous tutorial, An Introduction to NoSQL for HTML5 Mobile Developers, we covered how to get up an running with MongoDB, and now we’ll be looking at how to make use of it in a real life scenario. I’d recommend reading that tutorial if you aren’t already familiar with MongoDB (it gives some background into NoSQL in general, as well as MongoDB) but I will be covering the steps for getting set up in this tutorial as well so it isn’t required.

This is what the app will look like when it’s done:

Review King Screenshots

and this is how we will be using the MEAN stack for this tutorial:

  • MongoDB will be our NoSQL database, which we will use to store and retrieve reviews
  • Express will allow us to create routes for the REST API we will be creating
  • Ionic 2 will be used rather than Angular 2 for the front end of the application (I guess making this the MEIN stack)
  • Node will be our server, which will sit between the frontend of the application and the MongoDB database

This tutorial has been created with the help of these great resources, with bits and pieces being borrowed from each one, so if you’re looking for some more reading I’d recommend checking them out:

Before we Get Started

Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts. You must also already have Ionic 2 set up on your machine.

If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.

1. Creating the Database with MongoDB

The first thing we are going to do is get our MongoDB database set up. We will be setting this up locally (as we will be with the Node server as well), but if you would prefer you could also have your database hosted somewhere else.

The following install steps are specifically for Mac, and we will be using brew. If you don’t have brew installed already just follow the instructions on this page. If you are using a Windows computer, then you can follow the instructions here to install MongoDB.

You can install MongoDB with the following command:

brew install mongodb

and then start it with the following command:

brew services start mongodb

Now you can create a new MongoDB database and connect to it with the following command:

mongo reviewking

You will now be able to interact with the reviewking database through the terminal, and we will also be able to interact with it through our review application once we set up the API. To come back to this same database later you can just run mongo test again.

2. Generating a New Ionic 2 Application

We’re going to be creating our Node server before creating the front end, but let’s get our Ionic 2 project set up now.

Run the following command to create a new Ionic 2 application:

ionic start review-king blank --v2

We are also going to create an extra page (that we will use for adding reviews) and a provider (which we will be using to interact with our API), so let’s create those now too.

Run the following generate commands:

ionic g page AddReviewPage
ionic g provider Reviews

When creating new components in our application, we need to ensure that they are also added to the app.module.ts file.

Modify src/app/app.module.ts to reflect the following:

import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddReviewPage } from '../pages/add-review-page/add-review-page';
import { Reviews } from '../providers/reviews';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddReviewPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddReviewPage
  ],
  providers: [Reviews]
})
export class AppModule {}

Now let’s set up our server.

4. Create the Backend API with Node & Express

We are going to keep our server separate to the front end Ionic 2 project, so we will be creating a folder for it outside of the Ionic project.

Create a folder called server outside of your Ionic project

We are also going to need to set up some dependencies for it. You may be familiar with using the package.json file in Ionic projects (or other projects) to specify dependencies to be installed with npm. We can do the same thing for our server folder as well.

Create a file called package.json inside of the server folder and add the following:

{
  "name": "review-king",
  "version": "0.1.0",
  "description": "A sample Node.js app using Express 4",
  "engines": {
    "node": "5.9.1"
  },
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "devDependencies": {
    "mongoose": "^4.6.2",
    "body-parser": "^1.15.2",
    "cors": "^2.8.0",
    "del": "2.2.0",
    "express": "^4.14.0",
    "http": "0.0.0",
    "method-override": "^2.3.6",
    "morgan": "^1.7.0",
    "superlogin": "^0.6.1"
  }
}

Mongoose is a wrapper that will help us interact with MongoDB (clever name right?), and as I mentioned before Express is what we will be using to help create the routes for our API.

The rest of the dependencies are all helpers for our server. Body Parser will help us grab information from POST requests, Method Override provides support for DELETE and PUT, Morgan will output some helpful debugging messages, and Cors will handle CORS (Cross Origin Resource Sharing) issues for us.

To install all of the dependencies in package.json, you will need to run the npm install command.

Navigate to the server folder in your terminal, and run the following command:

npm install

Now we are going to create a server.js file, which we will run with node to create our server. I’m not going to explain this process in depth (although I will give a brief explanation afterwards), so if you’d like some more information on this step I’d recommend reading one of the resources I linked above.

NOTE: This code is heavily based on this tutorial, so credit to Chris Sevilleja for this. His tutorial also goes into a lot more detail on this step.

Create a file called server.js inside of the server folder and add the following code:

// Set up
var express  = require('express');
var app      = express();                               // create our app w/ express
var mongoose = require('mongoose');                     // mongoose for mongodb
var morgan = require('morgan');             // log requests to the console (express4)
var bodyParser = require('body-parser');    // pull information from HTML POST (express4)
var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)
var cors = require('cors');

// Configuration
mongoose.connect('mongodb://localhost/reviewking');

app.use(morgan('dev'));                                         // log every request to the console
app.use(bodyParser.urlencoded({'extended':'true'}));            // parse application/x-www-form-urlencoded
app.use(bodyParser.json());                                     // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
app.use(cors());

app.use(function(req, res, next) {
   res.header("Access-Control-Allow-Origin", "*");
   res.header('Access-Control-Allow-Methods', 'DELETE, PUT');
   res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
   next();
});

// Models
var Review = mongoose.model('Review', {
    title: String,
    description: String,
    rating: Number
});

// Routes

    // Get reviews
    app.get('/api/reviews', function(req, res) {

        console.log("fetching reviews");

        // use mongoose to get all reviews in the database
        Review.find(function(err, reviews) {

            // if there is an error retrieving, send the error. nothing after res.send(err) will execute
            if (err)
                res.send(err)

            res.json(reviews); // return all reviews in JSON format
        });
    });

    // create review and send back all reviews after creation
    app.post('/api/reviews', function(req, res) {

        console.log("creating review");

        // create a review, information comes from request from Ionic
        Review.create({
            title : req.body.title,
            description : req.body.description,
            rating: req.body.rating,
            done : false
        }, function(err, review) {
            if (err)
                res.send(err);

            // get and return all the reviews after you create another
            Review.find(function(err, reviews) {
                if (err)
                    res.send(err)
                res.json(reviews);
            });
        });

    });

    // delete a review
    app.delete('/api/reviews/:review_id', function(req, res) {
        Review.remove({
            _id : req.params.review_id
        }, function(err, review) {

        });
    });


// listen (start app with node server.js) ======================================
app.listen(8080);
console.log("App listening on port 8080");

Quite a lot is going on here, but it is split up into a few distinct sections:

  • Set up
  • Configuration
  • Models
  • Routes
  • Listen

First we set up all the dependencies we need for the server (like Mongoose and Express), then we configure some things which most importantly includes this line:

mongoose.connect('mongodb://localhost/reviewking');

which sets up the connection to our database. If you did not call your database reviewking then you should make sure to change that here. We set up a model called Review which is what is used to store our review object in the database – as you can see this object is made up of a title, description, and a rating.

Next we set up the routes, which are the endpoints we can hit on our API. Eventually we will use the Http service to hit this endpoints. We will be able to:

  • Send a GET request to /api/reviews to retrieve all the reviews
  • Send a POST request to /api/reviews to create a new review
  • Send a DELETE request to /api/reviews/{{review_id}} to delete a specific review

In the code above you can see we’re setting up each of these with app.get, app.post, and **app.delete. For each of these we specify the URL that is going to hit, and we set up handlers for each of these cases. In the case of a POST, it will handle storing the data in the database, but if there is a GET request it will handle retrieving the data and sending it back to our app.

Finally, the listen section starts our server on port 8080, which means we will be able to interact with the server by accessing:

http://localhost:8080

Now that the server is ready we can start it by running the following command:

node server.js

As you interact with the server through the application a little bit later, you should see something like this in your terminal:

Node Server

Now we just need to create our front end!

5. Create the Frontend with Ionic 2

We’ve already got the structure of our application set up, so we just need to drop in our front end code. Since this is a more advanced tutorial I will be going through this pretty quickly, and only focusing on the important bits.

Create the Reviews Provider

We’re going to start out by creating our Reviews provider, which is the most interesting thing in the application as it is what will interact with the backend.

Modify src/providers/reviews.ts to reflect the following

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class Reviews {

  data: any;

  constructor(public http: Http) {
    this.data = null;
  }

  getReviews(){

    if (this.data) {
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {

      this.http.get('http://localhost:8080/api/reviews')
        .map(res => res.json())
        .subscribe(data => {
          this.data = data;
          resolve(this.data);
        });
    });

  }

  createReview(review){

    let headers = new Headers();
    headers.append('Content-Type', 'application/json');

    this.http.post('http://localhost:8080/api/reviews', JSON.stringify(review), {headers: headers})
      .subscribe(res => {
        console.log(res.json());
      });

  }

  deleteReview(id){

    this.http.delete('http://localhost:8080/api/reviews/' + id).subscribe((res) => {
      console.log(res.json());
    });    

  }

}

We’ve created three methods here: getReviews, createReview, and deleteReview. Each of these corresponds to one of the end points on our API. In the getReviews function, we simply send a get request to /api/reviews and that will return our review data. The createReview function accepts a review object as a parameter and then posts that to the /api/reviews endpoint (where we already handle inserting it into the database). Finally, our deleteReview function accepts the id (which is automatically created by MongoDB) of a specific review, and will make a request to the API to delete it.

Create the Add Review Page

Now let’s set up the ‘Add Review’ page. This will be triggered as a Modal later, and the user will be able to enter in their title, description, and a rating using Ionic’s range component.

Modify src/pages/add-review-page/add-review-page.html to reflect the following:

<ion-header>
 <ion-toolbar transparent>
  <ion-title>Add Review</ion-title>
  <ion-buttons end>
    <button ion-button icon-only (click)="close()"><ion-icon name="close"></ion-icon></button>
  </ion-buttons>
 </ion-toolbar>
</ion-header>

<ion-content>

  <ion-list no-lines>

    <ion-item>
      <ion-label floating>Title</ion-label>
      <ion-input [(ngModel)]="title" type="text"></ion-input>
    </ion-item>

    <ion-item>
      <ion-label floating>Review</ion-label>
      <ion-textarea [(ngModel)]="description"></ion-textarea>
    </ion-item>

    <ion-item>
      <ion-range min="0" max="100" pin="true" [(ngModel)]="rating">
        <ion-icon range-left name="sad"></ion-icon>
        <ion-icon range-right name="happy"></ion-icon>
      </ion-range>
    </ion-item>

  </ion-list>

  <button ion-button full color="secondary" (click)="save()">Save</button>

</ion-content>

Modify src/pages/add-review-page/add-review-page.ts to reflect the following:

import { Component } from '@angular/core';
import { ViewController } from 'ionic-angular';

@Component({
  selector: 'add-review-page',
  templateUrl: 'add-review-page.html'
})
export class AddReviewPage {

  title: any;
  description: any;
  rating: any;

  constructor(public viewCtrl: ViewController) {

  }

  save(): void {

    let review = {
      title: this.title,
      description: this.description,
      rating: this.rating
    };

    this.viewCtrl.dismiss(review);

  }

  close(): void {
    this.viewCtrl.dismiss();
  }
}

We’re not handling any saving of the data here, we just grab the users input and then send it back to the HomePage through the use of the dimiss() method. Let’s set up the home page now.

Modify src/pages/home/home.html to reflect the following:

<ion-header>
 <ion-navbar transparent>
  <ion-title>
    Review King
  </ion-title>
  <ion-buttons end>
    <button ion-button icon-only (click)="addReview()"><ion-icon name="add"></ion-icon></button>
  </ion-buttons>
 </ion-navbar>
</ion-header>

<ion-content>

  <ion-list no-lines>

    <ion-item-sliding *ngFor="let review of reviews">

      <ion-item>

        <ion-avatar item-left>
          <img src="https://api.adorable.io/avatars/75/{{review.title}}">
        </ion-avatar>

        <h2>{{review.title}}</h2>
        <p>{{review.description}}</p>

        <ion-icon *ngIf="review.rating < 50" danger name="sad"></ion-icon>
        <ion-icon *ngIf="review.rating >= 50" secondary name="happy"></ion-icon> 
        {{review.rating}}

      </ion-item>

      <ion-item-options>
        <button ion-button color="danger" (click)="deleteReview(review)">
          <ion-icon name="trash"></ion-icon>
          Delete
        </button>
      </ion-item-options>
    </ion-item-sliding>

  </ion-list>

</ion-content>

This template is a little more interesting than the last. We’re looping over all of our reviews using *ngFor (we will define reviews in the class definition shortly). For each of these we display the data associated with the review, as well as an automatically generated avatar from adorable.io based on the title value. We also display a happy face if the rating is 50 or above, and a sad face is the rating is below 50.

Also notice that we are using the <ion-item-sliding> component, which will allow us to reveal the delete button for each review when it is swiped to the left.

Modify src/pages/home/home.ts to reflect the following:

import { Component } from "@angular/core";
import { NavController, ModalController } from 'ionic-angular';
import { AddReviewPage } from '../add-review-page/add-review-page';
import { Reviews } from '../../providers/reviews';

@Component({
  selector: 'home-page',
  templateUrl: 'home.html'
})
export class HomePage {

  reviews: any;

  constructor(public nav: NavController, public reviewService: Reviews, public modalCtrl: ModalController) {

  }

  ionViewDidLoad(){

    this.reviewService.getReviews().then((data) => {
      console.log(data);
      this.reviews = data;
    });

  }

  addReview(){

    let modal = this.modalCtrl.create(AddReviewPage);

    modal.onDidDismiss(review => {
      if(review){
        this.reviews.push(review);
        this.reviewService.createReview(review);        
      }
    });

    modal.present();

  }

  deleteReview(review){

    //Remove locally
      let index = this.reviews.indexOf(review);

      if(index > -1){
        this.reviews.splice(index, 1);
      }   

    //Remove from database
    this.reviewService.deleteReview(review._id);
  }

}

This class defines the functions that our home template calls. The addReview function launches our Add Review page as a modal, and when it receives the review data back from it, it calls the createReview function in the Review service we created before. This will add the review to our MongoDB database, but we also add the data to the local reviews array so that it displays right away without requiring a reload.

For the deleteReview function, we pass in the review and grab its id. First we remove that review locally (again, so that the change takes effect right away without having to reload), and then pass the id to the delete method of the review service (which will delete the review from our MongoDB database).

Finally, we’re going to add a bit of styling to the application.

Modify $background-color in theme/variables.scss to reflect the following:

$background-color:  #74efc4;

Modify home.scss to reflect the following:

.ios, .md {

  home-page {

    ion-item {
      background-color: #fff;
      border-bottom: none !important;
    }

    ion-label {
      white-space: normal;
    }

    ion-item-sliding {
      margin: 10px;
    }

  }

}

Modify add-review-page.scss to reflect the following:

.ios, .md {

  add-review-page {

    ion-label {
      margin-left: 10px;
    }

    ion-item {
      background-color: #74efc4 !important;
    }

    ion-input, ion-select, ion-textarea {
      background-color: #f2f2f2;
      padding: 5px 10px;
    }

  }

}

and that’s it! Your app (once you add some data to it) should now look something like this:

Review King Screenshots

Summary

Admittedly, this is a pretty basic app and its not exactly something you would submit to the app store. But we’ve done a tremendous amount of work here with very little code. We have a NoSQL database which we are able to interact with through the REST API we set up with Node and a front end to display and modify all of that data. The concepts we have covered here can be used as the basis for just about any app.

In future, we are going to be covering even more different backend options so stay tuned!

What to watch next...

  • refaelgold

    Great Tutorial !!!

  • 李滨洋

    Perfect Tutorial!!Thank You!!

  • Will Geller

    Great work on the tutorial, thanks! Will be buying the eBook soon.

    I would love to see you do a tutorial on how to do a similar app using firebase as a back end. Any idea if AngularFire2 is far along enough to be a viable option to use with Ionic2?

  • Pingback: Offline Syncing in Ionic 2 with PouchDB & CouchDB | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • Ji Josh!
    I’ve followed the full steps and when I run the app (Chrome) Ionic serve –lab I just get a green screen.
    What am I doing wrong?
    Thank you!

    • Milos Jovanovic

      Hi Diego

      do you have any data in the collection? If not you can take the following snippet as a start in the mongo shell:

      db.Review.insert({title: “Lorem”, description: “Ipsum”, rating: 75})

      or

      db.review.insert({title: “Lorem”, description: “Ipsum”, rating: 75})

      As you can see, for MongoDB it matters how you name your collections (case sensitive). I’ve run into the same pitfall as i named my collection ‘review’.

      In server.js I had to add a third parameter with the correct name to reflect this:

      var Review = mongoose.model(‘Review’, {
      title: String,
      description: String,
      rating: Number
      }, ‘review’);

      HTH

      @josh: Thanks for the tutorial!

      • Hi, Milos!
        Now I get a list of the records on my review collection, but the app still without loading the “top menú”.

        Thank you so much!

      • Hi!
        As Bak Bak said I had to change the ion-navbar to ion-toolbar and surround it with an ion-header, now it’s finally working!
        Ionic 2 is awesome!

    • Bak Bak

      Replace to and surround it with . So it looks like like on both home.html and add-review.html

  • Pingback: Building a Hotel Booking App with Ionic 2, MongoDB & Node | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • Patryk Adach

    2 days ago I was hating Ionic 2. Now I love it. Great tutorial!

  • Sam

    I’m getting http://localhost:8100/build/js/app.bundle.js Failed to load resource: the server responded with a status of 404 (Not Found) and I basically copied and pasted everything above 🙁

  • lorentz

    hello i have the modal errors and the nav.present () when i ttry to use ModalController it tells me that the property create is not found in Modalcontroller

  • Sean

    Hi Josh,
    If I would like to ‘Edit’ review. I am not sure these method are correct, It tried to add code like this :
    Please give me some advises and how to modify these code.

    server.js :
    app.request(‘/api/reviews/:review’, function(req, res) {

    console.log(‘update review’);

    Review.update({

    _id: req.params.review._id

    }, req.params.review),

    function(err, review)

    })

    home.ts:
    updateReview(review) {

    let index = this.reviews.indexOf(review);

    if (index > -1) {

    this.reviews[index] = review;

    this.reviewService.updateReview(review);

    }
    }

    reviews.ts:
    updateReview(review) {

    this.http.request(this.dbUrl + “/” + review)

    .subscribe(res => {

    console.log(res.json());

    });

    }

  • Abubaker Shekhani

    Dear Joshua,

    I have recently found your blog and it seems to be informative. I have few direct questions that will help me clarify my confusions.

    1. Is it safe to use Ionic 2 ? Is it out of Beta? or is it ok to use it even it is in Beta
    2. I got to know about MEAN recently but don’t know how to use it with Ionic 2. Your tutorial tell that. But is MongoDB free?
    3. I have my own Linux shared hosting and I want to host my application there? Can I set up the backend at my server?

    I am new to MEAN stack and Ionic, so I have these very beginner level questions.

    Thanks in advance.

  • Thanks for the tutorial. In real practice, we won’t keep the server inside the ionic project right? How will we manage that? This is what is in my mind
    1. We will host MongoDB and our server side code on the web. It will be a separate project and will be based on MEAN stack. Optionally we can have a web app front-end for it on Angular2.
    2. Ionic app will connect to the API exposed by the server app. That project will only have Ionic related code and dependencies in it.

    Am I going in the right direction?

  • Ibo Chief

    Thanks for the Tutorial. I am new to this type of development, and I’d like to know how to enable this app connect to a remote MongoDB storage. I’d appreciate any tip!

  • Peter Krizovnik

    Thanks for tutorial. I think you should return review from the server before you use this.reviews.push(review). If you don’t do that you can’t delete the same review from the server. Review doesn’t have mongodb id. My version: addReview() {
    let modal = this.modal.create(AddReview);

    modal.onDidDismiss(review => {
    if(review){
    this.reviewService.add(review)
    .subscribe(data => {
    this.reviews.push(data);
    console.log(data);
    this.loadReviews();
    });
    }
    });
    modal.present();
    }

  • Pingback: Creating Role Based Authentication with Passport in Ionic 2 - Part 1 | joshmorony - Build Mobile Apps with HTML5()

  • Responsive

    Definitely ordering your book.
    The delete button doesnt appear in the browser. Does it only work if you swipe left on a phone?
    https://uploads.disquscdn.com/images/22bb26a6e08617c51453df4534a320b0c71231b8471d66f50e9863f259b1bd07.png

    • Francis Eduardo Ribeiro

      just change this tag for this one

  • Dhameer Govind

    Thanks for this, Works in browser, but doesn’t want to run on android,
    http://localhost:8080/api/reviews Failed to load resource: net::ERR_CONNECTION_REFUSED

  • Jack

    Thanks for the tutorial Mr. Morony! I was very happy to get my application working with Node and MongoDB. I do have a question though. Do you happen to know what would need to be changed in order to run the backend with something like Heroku? Right now I think I am running into issues regarding cross origin requests. On GET and POST requests in ionic serve I get errors in my browser console that look something like this:

    No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100’ is therefore not allowed access. The response had HTTP status code 503.

    I don’t seem to be able to access my server either when I use Ionic upload, and look at it on my phone with Ionic View. I don’t know if there is a console to check in the app, so I have no Idea if it has a completely different issue.

    • Jack

      Got the app working now!
      I used heroku log and found out I had git ignoring my node_moduals, and the server was never working in the first place.

  • saikrishna

    how can i install brew in my windows

  • ironside

    What I need to modify in REST API to me submit to the app store ?

  • s3

    Thanks for the awesome tutorial 🙂