The Official Ionic Blog

Build amazing native and progressive web apps with HTML5

Given the choice between a short or tall stack of pancakes, which would you get? The correct answer is always more pancakes. In this post, we’re giving you more pancakes! So grab the syrup, and let’s make a full-stack of MEAN. This is the first post in a three-part series! Here’s part 2 and part 3 for reference.

Before we really dig in, a little bit about the MEAN stack:

For those who aren’t familiar, the MEAN stack is similar in concept to the popular LAMP (Linux, Apache, MySQL, PHP) stack, and is one of the most popular application development stacks for JavaScript developers. MEAN stands for MongoDB, Express, AngularJS, and Node.js, which are the four technologies that make up the stack. Here’s a quick breakdown of the part each of these plays:

  • MongoDB: NoSQL database/persistence
  • Express: HTTP request router/API framework
  • AngularJS: Application/MVC framework
  • Node.js: Server-side execution environment

MEAN has been a popular default dev stack for web apps in particular for quite some time now, primarily because it gives developers the ability to work with JavaScript and JSON across their entire application stack, from the server all the way to the UI. This is pretty cool for a lot of reasons, though one of the largest is that it removes a lot of complexity from an already complex system. Not only can we write all of our code in a single language (JS), since every level of the stack speaks the same language we also get to use a single data format to exchange data (JSON), as well as common idioms and object types. This continuity between layers can reduce errors and make coordinating development across the stack much easier.

Ok, enough of that. On to our app!

Project Setup, Git, Heroku and the Node.js Backend

We’ll be creating is a simple Ionic 2 app that uses Typescript and a RESTful api. We’ll also build a Node.js backend, using Express for our API and MongoDB for data storage. When we’re all done, we’ll use Git to push the app to Heroku, where it will be hosted.

The code for the app is available on GitHub, and, for the impatient, able to be deployed live to your Heroku account by using the button below:
Deploy

Project Setup

We’ll be writing the app’s frontend in TypeScript (TS), so you should probably download a TS compiler for your editor–it’ll make your life much easier.

To start, create a blank Ionic 2 app using the Ionic CLI’s start command. Feel free to name your app whatever you want. I’ve called mine todo-ionic2-heroku:

$ > ionic start todo-ionic2-heroku blank --v2

This will create a new project directory in place, with the same name as your project. The folder structure should look roughly like this:
image03

Let’s check it out. cd into your newly created directory and serve it up!

$ > cd todo-ionic2-heroku
$ /todo-ionic2-heroku > ionic serve

The serve command runs a variety of gulp tasks to build your app, then opens it in your browser (I’m using the device mode built into Chrome’s DevTools to see how the app looks on mobile):
image01

Flipping amazing, right? Okay, maybe not yet, but it’s a good start.

The Node.js Backend

If this is your first time using Heroku with a Node.js app, check out the Getting Started with Node.js on Heroku tutorial in Heroku’s Dev Center.

If you’re awesome and want to keep going, make sure you have the following installed:
Heroku toolbelt–Heroku’s CLI tool for creating and managing Heroku apps
Node.js

Git Init & Heroku Create

Let’s initialize a Git repository for our app:

$ /todo-ionic2-heroku > git init

Then set up the app with Heroku (Note: you must be logged in to your Heroku account from the command line):

$ /todo-ionic2-heroku > heroku create

This command just created a Git remote named heroku, where we’ll push our code. It also generated a random name for our app: evening-everglades-42641, in my case.

Set up MongoDB

Now that we have a blank Ionic app, and we’re linked up to Heroku, let’s provision a MongoDB instance. We could set up a local instance of MongoDB, but because we’re working with Heroku, we’ll use MongoLab’s mLab addon. This database-as-a-service through Heroku makes it very simple to provision a MongoDB database. Also, it’s free for little projects like this!

image00

Note: if you don’t want to use MongoLab, there are other data store options for Heroku.

Use the Heroku CLI to provision your database:

$ /todo-ionic2-heroku > heroku addons:create mongolab

This little command provisioned a sandbox database for us that’s connected to the Heroku app created in the last step. It also created a config variable in your Heroku environment: MONGODB_URI. We’ll use that to connect to our database in the next step.

Create an Express App Connected to MongoDB

When we first created the Ionic project, much of the plumbing for a Node app came along with it. Excellent!

The only setup left is to download the node_modules for our server and --save them to our existing package.json file.

$ /todo-ionic2-heroku > npm install express mongodb cors body-parser --save

In order to interact with the mLab database we provisioned, we’ll use the mongodb node module, which is officially supported by Node.js.

(Note: For projects with more complex data models, you may want to check out Mongoose, which provides “elegant mongodb object modeling for node.js”.)

In the main folder of your project, create a file called server.js, and put the following code in it:

//server.js (todo-ionic2-heroku/server.js)
var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var app = express();

var mongodb = require('mongodb'),
mongoClient = mongodb.MongoClient,
ObjectID = mongodb.ObjectID, // Used in API endpoints
db; // We'll initialize connection below

app.use(bodyParser.json());
app.set('port', process.env.PORT || 8080);
app.use(cors()); // CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests
app.use(express.static("www")); // Our Ionic app build is in the www folder (kept up-to-date by the Ionic CLI using 'ionic serve')

var MONGODB_URI = process.env.MONGODB_URI;

// Initialize database connection and then start the server.
mongoClient.connect(MONGODB_URI, function (err, database) {
if (err) {
process.exit(1);
}

db = database; // Our database object from mLab

console.log("Database connection ready");

// Initialize the app.
app.listen(app.get('port'), function () {
console.log("You're a wizard, Harry. I'm a what? Yes, a wizard, on port", app.get('port'));
});
});

// Todo API Routes Will Go Below

Get the MONGODB_URI for Local Development Fallback

As we develop our app, we’ll want to be able to run it locally. For this, we have the code on line 17 in server.js:

var MONGODB_URI = process.env.MONGODB_URI;

process.env.MONGODB_URI tells our server where our database is when the app is running on Heroku, but if you try starting your server locally with node server.js, you’ll get an error like this:

TypeError: Parameter 'url' must be a string, not undefined

To fix this, we need to grab the database URI from Heroku with the heroku config command:

$ /todo-ionic2-heroku > heroku config | grep MONGODB_URI

From the output, copy the part starting with mongodb://heroku and continues on with a string of gobbledegook. It should look something like this:

mongodb://heroku_g24xsxd8:[email protected]:45122/heroku_g24xsxd8

Then update line 17 of server.js:

var MONGODB_URI = process.env.MONGODB_URI || 'your_mongodb_uri_goes_in_these_quotes';

Note: Try not to share your DB URI anywhere public as it would grant full access to your database to anyone who knows how to use it.

At this point, you should be able to start the server and open up your app at http://localhost:8080/.

$ /todo-ionic2-heroku > node server.js
Database connection ready
You're a wizard, Harry. I'm a what? Yes, a wizard, on port 8080

Note: Going forward, it’s useful to have three tabs open in your terminal: one running your backend Express server, one running ionic serve to rebuild the frontend when Ionic detects changes, and one for running additional commands.

Set up Your RESTful API Endpoints

Now that our server is up and running, let’s go ahead and create the endpoints for a simple RESTful API that we’ll use to data for the front-end of our app. We’ll create two endpoints that support the following CRUD (Create, Read, Update, Delete) operations:

Screen Shot 2016-08-19 at 9.24.44 AM

 

Finally, just for reference, the database schema for our todos is very simple:

{
"_id": ,
"description": ,
"isComplete":
}

Add API Endpoints to Server.js

To create our endpoints, let’s add the following to server.js:

//server.js
...
/*
* Endpoint --> "/api/todos"
*/

// GET: retrieve all todos
app.get("/api/todos", function(req, res) {
db.collection("todos").find({}).toArray(function(err, docs) {
if (err) {
handleError(res, err.message, "Failed to get todos");
} else {
res.status(200).json(docs);
}
});
});

// POST: create a new todo
app.post("/api/todos", function(req, res) {
var newTodo = {
description: req.body.description,
isComplete: false
}

db.collection("todos").insertOne(newTodo, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to add todo");
} else {
res.status(201).json(doc.ops[0]);
}
});
});

/*
* Endpoint "/api/todos/:id"
*/

// GET: retrieve a todo by id -- Note, not used on front-end
app.get("/api/todos/:id", function(req, res) {
db.collection("todos").findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to get todo by _id");
} else {
res.status(200).json(doc);
}
});
});

// PUT: update a todo by id
app.put("/api/todos/:id", function(req, res) {
var updateTodo = req.body;
delete updateTodo._id;

db.collection("todos").updateOne({_id: new ObjectID(req.params.id)}, updateTodo, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to update todo");
} else {
res.status(204).end();
}
});
});

// DELETE: delete a todo by id
app.delete("/api/todos/:id", function(req, res) {
db.collection("todos").deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
if (err) {
handleError(res, err.message, "Failed to delete todo");
} else {
res.status(204).end();
}
});
});

// Error handler for the api
function handleError(res, reason, message, code) {
console.log("API Error: " + reason);
res.status(code || 500).json({"Error": message});
}

Push to Heroku and Test out Your API

Before we move onto our Ionic app, let’s push everything to Heroku as it currently stands and test out our API with a curl command.

Update .gitignore

By default, Ionic puts www/build in our .gitignore file, as we normally wouldn’t want to track it, but we need it to support Heroku deployment, so we’ll remove it. After removing it, .gitignore should look something like this:

#.gitignore
node_modules/
platforms/
plugins/
.DS_Store

Commit Changes and Push to Heroku

Next up, commit all of your our changes to the git repository and push it to Heroku:

$ /todo-ionic2-heroku > git add -A
$ /todo-ionic2-heroku > git commit -m "First commit"
$ /todo-ionic2-heroku > git push heroku master

Heroku will confirm your app has been deployed, but to open your app, you need at least one instance of it running. To activate your app run the following:

$/todo-ionic2-heroku > heroku ps:scale web=1

Then open your app:

$/todo-ionic2-heroku > heroku open

This opens your simple, blank Ionic app at your-app-name.heroku-app.com.

Next, let’s test the API by adding a new Todo by running the following curl command:


$/todo-ionic2-heroku > curl -H "Content-Type: application/json" -d '{"description":"Order a tall stack of pancakes, just because."}' https://your-app-name.herokuapp.com/api/todos

You can check out your newly added todo under the todos collection in your mLab management portal. Open it up by running:

$/todo-ionic2-heroku > heroku addons:open mongolab

Some Closing Thoughts

And with that, the backend is all ready to go. The next step will be to build out the frontend of our app. In the next post, we’ll write some Angular 2 code to retrieve the todos we just added to the database and display them in an Ionic 2 app.

  • Toke Refstrup

    Very nice tutorial. It seems to me that you are pushing both the backend and the app to heroku, why do we need the app there?

    • jplwood

      We made the tutorial web-only to focus in on the way to build a simple app using Ionic 2, Angular 2, connect it up to a Node.js backend and put it up on Heroku. We push the app up to Heroku so it’s accessible via the Heroku domain that’s specific to your app.

      • Toke Refstrup

        Ah ok, thanks.

  • http://mithsoft.in Nikhil Jauhari

    i was really interested in creating MEAN ionic2, and here it is thanks Justin. Appreciated.

  • jmtaleno

    Heroku Mongolab is asking for a credit card. 🙁

    • Graciele E. Victor

      Yep, faced the same here.

      Only way I’ve found to fix it was to actually insert CC information in Heroku website.. don’t worry, this sandbox option is really free..

      • Graciele E. Victor

        after inserting your CC infos, type this command:

        heroku addons:create mongolab:sandbox

        • jmtaleno

          Thanks @gracieleevictor:disqus! Yeah I did and it worked!

          • jplwood

            You shouldn’t have to put in your cc info, I never did — but I’m glad you got it working!

  • jmtaleno

    Hi Justin, I’m getting an Application Error when I tried to open my heroku app. And also curl is not working for me. Please help. Thanks!

  • Sagiv Frankel

    Thanks for the hard work – great series. The one thing I think is missing is adding the build as part of the Heroku deploy instead of committing the build directory which is bad.

    If I get it working for me I will add a pull request.

  • Abubaker Shekhani

    What if we don’t want to use Heroku? Is it possible to follow this tutorial without Heroku?

    • jplwood

      You certainly don’t have to follow the Heroku parts and just work locally. However, you would need to look into adding a local mongodb instance. It might be easier just to do the heroku part so you have a hosted DB to use (You can use it locally for the purpose of the tutorial). But generally, there’s plenty we work through that’s Ionic 2/Angular 2 specific that should be useful!

  • Graciele E. Victor

    I got a “Bad Request” response when trying to execute that curl command on CMD..

    if you’re facing the same just use Cygwin64 and it will work fine…

    ( just to save time if someone is facing the same… )

    • Jack

      What does Cygwin64 do?
      Do I still enter
      $ curl -H “Content-Type: application/json” -d ‘{“description”:”Order a tall stack of pancakes, just because.”}’ https://my-app-name.herokuapp.com/api/todos
      ?

  • Graciele E. Victor

    Great article Justin ! Thanks for posting !

  • Graciele E. Victor

    Hi Justin, where/when did you define the database schema? How mongoDB knows what fields to create in the Collection?

    • jplwood

      If you never execute the cURL command, you’ll still be sending data to it via the API in that same format. Our `todos` table will just automatically take on the format of the JSON data being sent into it. If you want to be more explicit with defining a DB schema, Mongoose, instead of the standard MongoDB node driver, might be helpful!

  • mixersoft

    Having some problems with the tutorial. The api server works but the app is not serving from heroku. The `curl` statement also fails. see: https://forum.ionicframework.com/t/problem-one-mean-ionic-2-todo-app-on-heroku/61985

    any ideas?

    • jplwood

      Hey I replied in the forum, let me know in there if that wasn’t your problem!

  • http://tester.dev/ Tumiso Marebane

    Great tutorial!! Thanks guys – this was a lot of help

  • peb7268

    Another option as well is to use local storage or localForage from Mozilla as the data store.

  • Hunter Munday

    Thanks for the great tutorial, I’m having an issue with my modules not importing in VSCode. I don’t have this problem with any other projects, just this one. Could there be something going on with heroku/git that is causing this? For example, when I created the todo.ts file, and tried to import it in the todo-service.js file, it would not import. Interestingly enough, the compiler would not give me any error at all no matter what path or classname I gave it.