MEAN Stack Tutorial - Part 3
In the next step we deal with the database part
The MongoDB database
We have used MongoDB as a database to build our API but we still didn’t dive deep on it. We will not do it in this tutorial, but at least let’s check out some of the features we have available with it.
MongoDB stores the data as JSON documents. This makes this DB document oriented and also has dynamic schemas that we have been using in our API because of mongoose. We have been using it because of it, but in MongoDB you don’t need to stick to schemas as you would do in relational database. Inside the same collection or table you can have two documents or rows with different schemas.
What we have to bear in mind is that we use the mongoose driver for the interaction with the database from the server side code. Since this is doing almost all the job for us we will not dive deep on it. To work in a dev environment, it is much better that the databases are created automatically when the API is fired up. We can change this configuration but it is not advisable.
First thing, run the database by running the command (if you are in Windows you may have to go where the installation is):
mongod
If you want to check out the stats of the database running, take a peek at http://localhost:28017.
We are using http://robomongo.org because it is a tool for database management which is cross-platform but you can manage it also with interactive JavaScript shell if you prefer.
Basic Shell Commands
First, use:
mongo
If you want to list all the databases:
show dbs
If you want to change to the database we are using:
use northwindnode-dev
List the tables (they are called collections) in the active DB:
show collections
This will show the documents from the categories collection:
db.categories.find();
And this will count them:
db.categories.count();
If you want to create a new category:
db.categories.insert({ name: 'Electronics' });
And then search for it:
db.categories.find({ name: "Electronics" }).pretty();
So, we will get this:
{
"_id" : ObjectId("54d7edbc84e3603a777589b6"),
"name" : "Electronics"
}
If you want to create categories with different schema:
db.categories.insert({ title: 'Electronics' });
And now query the new document with different schema and being in the same collection:
db.categories.find({ title: 'Electronics' }).pretty();
So, we will get:
{
"_id" : ObjectId("54d7ef3c84e3603a777589b8"),
"title" : "Electronics"
}
This is used for updating categories:
db.categories.update({ name: 'Electronics' }, { $set: { name: 'Gadgets' } });
And this will check it:
db.categories.find({ name: 'Gadgets' }).pretty();
And this is for deleting categories:
db.categories.remove({ name: 'Gadgets' });
We will use the mongoose abstraction so, the commands will be a bit different than in the mongo shell, however it is good to know the basics of the database (although we are not going to use them much in this tutorial).
With this, we have taken a look at the basics of the MongoDB. All the DB work will be done by Mongoose so we don’t have to bother too much about it and now let’s move to the final part of the API, preparing the front-end with Angular.js. This will be in charge of the /Public/ folder and we aim to add a basic screen for the category API that reflects the logic in the User’s Interface (UI). Angular.js is a really complete framework and will do a lot for us, so we will not get much into details on how it does X or Y and we will better try to do something that is working for our API.
Angular is a JavaScript network that will do a lot and has a lot of functionalities that are not available in other frameworks. However, it is easy to test and we will see shortly how to handle it in a very beginner’s way.
Create a Module with Angular.js
First thing with Angular is to create a simple module for the components that will manage the functionality of our categories. We will use Yeoman and the CLI so run it and just leave the default options but don’t get the tests as they are way beyond the scope of this tutorial. Run the following and let’s get it done!
yo meanjs:angular-module categories
So, now we have a folder structure and some files. Go to public/modules/categories folder and inside it there should be config, controllers, services, views and categories.client.module.js. The folders will be empty for now and the file should show the following:
'use strict';
// Use application configuration module to register a new module
ApplicationConfiguration.registerModule('categories');
Now we have to make some changes in the UI to add some items to the menu. Run the following and then choose the module named categories by using the arrows:
yo meanjs:angular-config categories
Now browse at public/modules/categories/config/categories.client.config.js and replace all the contents by the following:
'use strict';
// Configuring the Articles module
angular.module('categories').run(['Menus',
function(Menus) {
// Set top bar menu items
Menus.addMenuItem('topbar', 'Categories', 'categories', 'dropdown', '/categories(/create)?');
Menus.addSubMenuItem('topbar', 'categories', 'List Categories', 'categories');
Menus.addSubMenuItem('topbar', 'categories', 'New Category', 'categories/create');
}
]);
Now you can start your app via npm and create an account if you didn’t have one already. Login and use the default values and you will be able to see the drop down options in the Categories menu.
Warning
You may experience an error from JavaScript when you click on the drop down menu. If you experience the Uncaught TypeError: Cannot read property “getToggleElement” of null in the console you can fix it by going to the bower.json file and change:
"angular-bootstrap": "~0.11.2",
To:
"angular-bootstrap": "~0.12.0",
And then run the following command:
bower update angular-bootstrap
This is just an error in the console but the functionality will remain intact so, if you don’t care much about it and you want to follow with the tutorial, it won’t harm the API. You can also update the contents of the file public/modules/core/views/header.client.view.html with the following if the menu doesn’t work:
<div class="container" data-ng-controller="HeaderController">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/#!/" class="navbar-brand">NorthwindNode</a>
</div>
<nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
<ul class="nav navbar-nav" data-ng-if="menu.shouldRender(authentication.user);">
<li data-ng-repeat="item in menu.items | orderBy: 'position'" data-ng-if="item.shouldRender(authentication.user);" ng-switch="item.menuItemType" ui-route="{{item.uiRoute}}" class="{{item.menuItemClass}}" ng-class="{active: ($uiRoute)}" dropdown="item.menuItemType === 'dropdown'">
<a ng-switch-when="dropdown" class="dropdown-toggle" dropdown-toggle>
<span data-ng-bind="item.title"></span>
<b class="caret"></b>
</a>
<ul ng-switch-when="dropdown" class="dropdown-menu">
<li data-ng-repeat="subitem in item.items | orderBy: 'position'" data-ng-if="subitem.shouldRender(authentication.user);" ui-route="{{subitem.uiRoute}}" ng-class="{active: $uiRoute}">
<a href="/#!/{{subitem.link}}" data-ng-bind="subitem.title"></a>
</li>
</ul>
<a ng-switch-default href="/#!/{{item.link}}" data-ng-bind="item.title"></a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.user">
<li ui-route="/signup" ng-class="{active: $uiRoute}">
<a href="/#!/signup">Sign Up</a>
</li>
<li class="divider-vertical"></li>
<li ui-route="/signin" ng-class="{active: $uiRoute}">
<a href="/#!/signin">Sign In</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-show="authentication.user">
<li class="dropdown" dropdown>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" dropdown-toggle>
<span data-ng-bind="authentication.user.displayName"></span> <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/#!/settings/profile">Edit Profile</a>
</li>
<li>
<a href="/#!/settings/accounts">Manage Social Accounts</a>
</li>
<li data-ng-show="authentication.user.provider === 'local'">
<a href="/#!/settings/password">Change Password</a>
</li>
<li class="divider"></li>
<li>
<a href="/auth/signout">Signout</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
Adding the views options
Now we have two options in the menu which are listing and creating categories but since the pages related to them don’t exist yet, the menu will do nothing. So, first we will start with listing the categories. We will need to add a basic template to the view, route and controller so just run the following and accept the following options for the name “categories”.
yo meanjs:angular-route categories
It will create the following files:
- public/modules/categories/config/categories.client.routes.js
- public/modules/categories/controllers/categories.client.controller.js
- public/modules/categories/controllers/categories.client.controller.js
- public/modules/categories/views/categories.client.view.html
And then if you choose Categories > List Categories in the menu item it will send you to a page that will just say “This is the categories view”
Then we need to make the page for the New Cagegory. Generate it by running the following:
yo meanjs:angular-view create-category
Now you will be asked some questions. Just answer them as follows:
- Which module does this view belong to?
- Choose: categories
- What is the name of the controller this view will use?
- Enter: Categories
- Would you like to add a route for this view?
- Choose: Y (the default)
- What is your view route path?
- Enter: /categories/create
And you will get a new file:
- create public/modules/categories/views/create-category.client.view.html
And then just paste the following snippet to it:
<section data-ng-controller="CategoriesController">
<div class="page-header">
<h1>New Category</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="create()" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="name">Description</label>
<div class="controls">
<textarea name="description" data-ng-model="description" id="description" class="form-control" cols="30" rows="10" placeholder="Description"></textarea>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
Angular is a framework that promises to enhance the HTML for web apps, and this is done here via the data-ng-controller=”CategoriesController” by telling the framework where the logic is, in this case is located at CategoriesController.
Now we can go to (http://localhost:3000/#!/categories/create) and there you will see the UI to create a new category with the name and the description fields.
If you can’t view the page it may be because the forward slash hasn’t been correctly added to /categories/create. If that’s the case check out the contents of public/modules/categories/config/categories.client.routes.js are as follows:
'use strict';
//Setting up route
angular.module('categories').config(['$stateProvider',
function($stateProvider) {
// Categories state routing
$stateProvider.
state('categories', {
url: '/categories',
templateUrl: 'modules/categories/views/categories.client.view.html'
}).
state('createCategory', {
url: '/categories/create',
templateUrl: 'modules/categories/views/create-category.client.view.html'
});
}
]);
Now we should go to the list view located at public/modules/categories/views/categories.client.view.html and paste the following:
<section data-ng-controller="CategoriesController" data-ng-init="find()">
<div class="page-header">
<h1>Categories</h1>
</div>
<div class="list-group">
<a data-ng-repeat="category in categories" data-ng-href="#!/categories/{{category._id}}" class="list-group-item">
<h4 class="list-group-item-heading" data-ng-bind="category.name"></h4>
<span data-ng-bind="category.description"></span>
</a>
</div>
<div class="alert alert-warning text-center" data-ng-hide="!categories.$resolved || categories.length">
No categories yet, why don't you <a href="/#!/categories/create">create one</a>?
</div>
</section>
Sweet, the HTML displays the data and the routes are pointing angular to the views for each route. Now we need to add some code so that it also manages data. Since this file has already been scaffolded we can just find it at /public/modules/categories/controllers/categories.client.controller.js. And here we will see the default content
'use strict';
angular.module('categories').controller('CategoriesController', ['$scope',
function($scope) {
// Controller Logic
// ...
}
]);
Here we can find a variable called $scope to represent the elements in the view. We had in our template data-ng-model=”name” so, we can access this property via $scope.name, $scope.description etc… We can also set the property for example $scope.name = “This program is awesome”
Next thing to do is to paste the following in the file we just found:
'use strict';
angular.module('categories').controller('CategoriesController', ['$scope', '$location',
function($scope, $location) {
// Create new Category
$scope.create = function() {
// Redirect after save
$location.path('categories');
// Clear form fields
$scope.name = '';
};
// Find a list of Categories
$scope.find = function() {
// hard coded data
$scope.categories = [{
'name': 'Beverages',
'description': 'Soft drinks, coffees, teas, beers, and ales'
},
{
'name': 'Condiments',
'description': 'Sweet and savory sauces, relishes, spreads, and seasonings'
}];
};
}
]);
Next thing is to check the hardcoded categories listed at http://localhost:3000/#!/categories and replace this hardcoded data. To do so we will need an Angular Service so we will create this with yo
yo meanjs:angular-service categories
And this is going to create a new file at public/modules/categories/services/categories.client.service.js
We will need to change the contents of the file with the following:
'use strict';
//Categories service used to communicate Categories REST endpoints
angular.module('categories').factory('Categories', ['$resource',
function($resource) {
return $resource('categories/:categoryId', { categoryId: '@_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
We are going to pass the service to the categories controller and it will just call the API endpoint /categories. This is done by using an Angular resource named $resource. Our endpoint follows a RESTful approach and the $resource is an object that will let us interact with the server side data. By doing this we are going to save a lot of time and energy because this Angular resource follows the same pattern of GET, POST, DELETE when it interacts with the /categories endpoint.
So now we have a service talking to the server side category model so all we have to do now is to tell the categories controller of its existence. To do so we will change the category controller contents with the following:
'use strict';
angular.module('categories').controller('CategoriesController', ['$scope', '$location', 'Categories',
function($scope, $location, Categories) {
// Create new Category
$scope.create = function() {
// Create new Category object
var category = new Categories ({
name: this.name,
description: this.description
});
// Redirect after save
category.$save(function(response) {
$location.path('categories/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
// Find a list of Categories
$scope.find = function() {
$scope.categories = Categories.query();
};
}
]);
Last Stuff To Do
We have already done the basic functionalities but still we must implement View, Update and Delete. To do so we will replace the contents of some files with the added functionality.
First we will replace categories.client.controller.js with the following:
'use strict';
// Categories controller
angular.module('categories').controller('CategoriesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Categories',
function($scope, $stateParams, $location, Authentication, Categories) {
$scope.authentication = Authentication;
// Create new Category
$scope.create = function() {
// Create new Category object
var category = new Categories ({
name: this.name,
description: this.description
});
// Redirect after save
category.$save(function(response) {
$location.path('categories/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
// Remove existing Category
$scope.remove = function(category) {
if ( category ) {
category.$remove();
for (var i in $scope.categories) {
if ($scope.categories [i] === category) {
$scope.categories.splice(i, 1);
}
}
} else {
$scope.category.$remove(function() {
$location.path('categories');
});
}
};
// Update existing Category
$scope.update = function() {
var category = $scope.category;
category.$update(function() {
$location.path('categories/' + category._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
// Find a list of Categories
$scope.find = function() {
$scope.categories = Categories.query();
};
// Find existing Category
$scope.findOne = function() {
$scope.category = Categories.get({
categoryId: $stateParams.categoryId
});
};
}
]);
Next we will replace the contents of the route located at categories.client.routes.js with the following one:
'use strict';
//Setting up route
angular.module('categories').config(['$stateProvider',
function($stateProvider) {
// Categories state routing
$stateProvider.
state('listCategories', {
url: '/categories',
templateUrl: 'modules/categories/views/categories.client.view.html'
}).
state('createCategory', {
url: '/categories/create',
templateUrl: 'modules/categories/views/create-category.client.view.html'
}).
state('viewCategory', {
url: '/categories/:categoryId',
templateUrl: 'modules/categories/views/view-category.client.view.html'
}).
state('editCategory', {
url: '/categories/:categoryId/edit',
templateUrl: 'modules/categories/views/edit-category.client.view.html'
});
}
]);
Then for the view we will need to create two files. First the view-category.client.view.html and fill it with the following:
<section data-ng-controller="CategoriesController" data-ng-init="findOne()">
<div class="page-header">
<h1 data-ng-bind="category.name"></h1>
<p data-ng-bind="category.description"></p>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="/#!/categories/{{category._id}}/edit">
<i class="glyphicon glyphicon-edit"></i>
</a>
<a class="btn btn-primary" data-ng-click="remove();">
<i class="glyphicon glyphicon-trash"></i>
</a>
</div>
</section>
And for the last file create edit-category.client.view.html and paste the following
<section data-ng-controller="CategoriesController" data-ng-init="findOne()">
<div class="page-header">
<h1>Edit Category</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="update()" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" data-ng-model="category.name" id="name" class="form-control" placeholder="Name" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea name="description" data-ng-model="category.description" id="description" class="form-control" cols="30" rows="10" placeholder="Description" required></textarea>
</div>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
We also want to enhance the Searching and paging functionality in our API, to do so we will replace the contents at categories.client.view.html with the following:
<section data-ng-controller="CategoriesController" data-ng-init="find()">
<div class="page-header">
<h1>Categories</h1>
</div>
<!-- this is the new search input field -->
<div class="form-group">
<input type="text" ng-model="search" typeahead="category as category.name for category in categories | filter:$viewValue | limitTo:8" class="form-control" typeahead-on-select="categorySearch($item)" placeholder="Search Category...">
</div>
<!-- end -->
<div class="list-group">
<a data-ng-repeat="category in categories | limitTo: offset - categories.length | limitTo: pageSize" data-ng-href="#!/categories/{{category._id}}" class="list-group-item">
<h4 class="list-group-item-heading" data-ng-bind="category.name"></h4>
<span data-ng-bind="category.description"></span>
</a>
</div>
<!-- this is the paging control -->
<pagination total-items="categories.length" ng-model="currentPage" ng-change="pageChanged()" max-size="pageSize" ng-show="categories.length > pageSize"></pagination>
<!-- end -->
<div class="alert alert-warning text-center" data-ng-hide="!categories.$resolved || categories.length">
No categories yet, why don't you <a href="/#!/categories/create">create one</a>?
</div>
</section>
You may have noticed we are using Pagination and Typeahead. These are part of Angular UI Bootstrap. This is how Angular enhances HTML by rendering in the browser the pagination tag as an unordered list. This is what is called Angular Directives and we will not even get close to them because they are way beyond the scope of this tutorial but for now, just bear in mind this is really a good way to Enhance HTML and one of the best features included in Angular.js.
Next thing to do is to add more functionality for these new directives so, let’s extend the code with the following
'use strict';
// Categories controller
angular.module('categories').controller('CategoriesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Categories',
function($scope, $stateParams, $location, Authentication, Categories) {
$scope.authentication = Authentication;
$scope.currentPage = 1;
$scope.pageSize = 10;
$scope.offset = 0;
// Page changed handler
$scope.pageChanged = function() {
$scope.offset = ($scope.currentPage - 1) * $scope.pageSize;
};
// Create new Category
$scope.create = function() {
// Create new Category object
var category = new Categories ({
name: this.name,
description: this.description
});
// Redirect after save
category.$save(function(response) {
$location.path('categories/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
// Remove existing Category
$scope.remove = function(category) {
if ( category ) {
category.$remove();
for (var i in $scope.categories) {
if ($scope.categories [i] === category) {
$scope.categories.splice(i, 1);
}
}
} else {
$scope.category.$remove(function() {
$location.path('categories');
});
}
};
// Update existing Category
$scope.update = function() {
var category = $scope.category;
category.$update(function() {
$location.path('categories/' + category._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
// Find a list of Categories
$scope.find = function() {
$scope.categories = Categories.query();
};
// Find existing Category
$scope.findOne = function() {
$scope.category = Categories.get({
categoryId: $stateParams.categoryId
});
};
// Search for a category
$scope.categorySearch = function(product) {
$location.path('categories/' + product._id);
};
}
]);
Now, you ended the tutorial. Thanks a lot for reading this post to the end and learning with us. The MEAN stack is a popular choice for full stack developers because of its functionality and being simpler than other stacks.
We did not approach technically to many concepts because they are not for beginners and we wanted this tutorial to be a reference for programmers new to the MEAN stack. We did not cover the testing procedures in Angular because these concepts should need a tutorial themselves.
The original code used in this tutorial is open source with MIT license and you can find it here
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment