MEAN is stand for Mongo, Express, Angular 2 and Node.js. This combination is popular in web application development. There is some MEAN stack bundle that ready use like MEAN.js and MEAN.io, but we are not using that ready to use the framework. We will do it from scratch creating simple CRUD app, using existing tools and generator that comes with Angular 2.
Before we get started, there are few things required for building this MEAN stack web application.
- Node.js
- Express.js
- Angular 2 CLI
Using all tools above, we will create simple CRUD (Create-Read-Update-Delete) web application.
1. Create Angular 2 Application using Angular CLI
We assume that you already install Node.js. We will use Angular CLI to create new Angular 2 application. Open terminal (Mac/Linux) or Node.js command prompt (Windows) then type this command to install Angular CLI.
sudo npm install @angular/cli -g
Now, type this command to create new Angular 2 application.
ng new mean-app
Go to the newly created application folder.
cd mean-app
Run the Angular 2 application by typing this command.
ng serve
If you see "Webpack. Compiled Successfully" in the terminal then open the browser then point to this URL "http://localhost:4200". You will see this page if everything working properly.
2. Add Express.js Server Functionality
This time will different than previous Node.js and Express.js tutorial. We will create required files for backend manually instead of generating new Express.js application. First, we install Express.js and Mongoose.js with their required dependencies. Just type this command.
npm install --save express body-parser morgan body-parser serve-favicon
Then, add bin folder and www file inside bin folder.
mkdir bin
touch bin/www
Open and edit www file then add this lines of codes.
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
To make the server run from bin/www, open and edit "package.json" then replace "start" value.
"scripts": {
"ng": "ng",
"start": "node ./bin/www",
"test": "ng test",
"pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "protractor"
},
Now, create app.js in the root of project folder.
touch app.js
Open and edit app.js then add all this lines of codes.
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');
var book = require('./routes/book');
var app = express();
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/book', book);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
Next, create routes folder then create routes file for the book.
mkdir routes
touch routes/book.js
Open and edit "book.js" file then add this lines of codes.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.send('Express REST API');
});
module.exports = router;
Because we point Express static path to "dist" directory which it is Angular build folder. For that, build Angular application first.
ng build
Now, run the server using this command.
nodemon
or
npm start
Open your browser and point to "http://localhost:3000". You will see the same page as running Angular application.
When you change to this URL "http://localhost:3000/book", it will show Express page.
3. Create REST API for Accessing Book Data
To create the REST API first thing to do is install Mongoose.js for creating a model.
npm install mongoose --save
Open and edit app.js on the root of the project. Add require line for Mongoose.
var mongoose = require('mongoose');
Create a connection to MongoDB database. Add this lines of codes after the require section.
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/mean-app')
.then(() => console.log('connection successful'))
.catch((err) => console.error(err));
Create model folder and file for building Mongoose Model Schema.
mkdir models
touch models/Book.js
Open and edit Book.js file then add this lines of codes.
var mongoose = require('mongoose');
var BookSchema = new mongoose.Schema({
isbn: String,
title: String,
author: String,
publisher: String,
published_date: Date,
price: Number,
updated_at: { type: Date, default: Date.now },
});
module.exports = mongoose.model('Book', BookSchema);
Open and edit previously created file routes/book.js then replace all codes with this.
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Book = require('../models/Book.js');
/* GET ALL BOOKS */
router.get('/', function(req, res, next) {
Book.find(function (err, products) {
if (err) return next(err);
res.json(products);
});
});
/* GET SINGLE BOOK BY ID */
router.get('/:id', function(req, res, next) {
Book.findById(req.params.id, function (err, post) {
if (err) return next(err);
res.json(post);
});
});
/* SAVE BOOK */
router.post('/', function(req, res, next) {
Book.create(req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
});
/* UPDATE BOOK */
router.put('/:id', function(req, res, next) {
Book.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
});
/* DELETE BOOK */
router.delete('/:id', function(req, res, next) {
Book.findByIdAndRemove(req.params.id, req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
});
module.exports = router;
Now, re-run our MEAN application. Open another terminal then type this command to test REST API.
curl -i -H "Accept: application/json" localhost:3000/book
If that command return response like below then REST API is ready to go.
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-11FxOYiYfpMxmANj4kGJzg"
Date: Sat, 18 Mar 2017 23:01:05 GMT
Connection: keep-alive
4. Create Angular 2 Providers or Services
To make REST API accessible from Angular 2 application first, create Angular 2 providers or services by typing this command.
ng g service book
That command will create files required by book service.
installing service
create src/app/book.service.spec.ts
create src/app/book.service.ts
Add book service manually to app.module.ts. Put import for book service.
import { BookService } from './book.service';
Add it to @NgModule providers.
providers: [BookService],
Next, open and edit src/app/book.service.ts. Replace all codes with this codes.
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class BookService {
constructor(private http: Http) { }
getAllBooks() {
return new Promise((resolve, reject) => {
this.http.get('/book')
.map(res => res.json())
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
showBook(id) {
return new Promise((resolve, reject) => {
this.http.get('/book/' + id)
.map(res => res.json())
.subscribe(res => {
resolve(res)
}, (err) => {
reject(err);
});
});
}
saveBook(data) {
return new Promise((resolve, reject) => {
this.http.post('/book', data)
.map(res => res.json())
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
updateBook(id, data) {
return new Promise((resolve, reject) => {
this.http.put('/book/'+id, data)
.map(res => res.json())
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
deleteBook(id) {
return new Promise((resolve, reject) => {
this.http.delete('/book/'+id)
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
}
That codes using Promise response instead of Observable.
5. Create Angular 2 Component for Book List
To create Angular 2 Component, simply run this command.
ng g component book
That command will generate all required files for build book component and also automatically added book component to app.module.ts.
installing component
create src/app/book/book.component.css
create src/app/book/book.component.html
create src/app/book/book.component.spec.ts
create src/app/book/book.component.ts
update src/app/app.module.ts
Now, we will create book list in book component. Open and edit src/app/book.component.ts then replace all codes with this.
import { Component, OnInit } from '@angular/core';
import { BookService } from '../book.service';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.css']
})
export class BookComponent implements OnInit {
books: any;
constructor(private bookService: BookService) { }
ngOnInit() {
this.getBookList();
}
getBookList() {
this.bookService.getAllBooks().then((res) => {
this.books = res;
}, (err) => {
console.log(err);
});
}
}
Next, we will edit the view. But firstly, add bootstrap library to index.html.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>MeanApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<app-root>Loading...</app-root>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
To use book component as default landing page, open and edit src/app/app.module.ts the add import for Routing.
import { RouterModule } from '@angular/router';
Create constant router for routing to book component.
const ROUTES = [
{ path: '', redirectTo: 'books', pathMatch: 'full' },
{ path: 'books', component: BookComponent }
];
In @NgModule imports section add ROUTES constant, so imports section will be like this.
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot(ROUTES)
],
To prevent error 404 add this import.
import { HashLocationStrategy, LocationStrategy } from '@angular/common';
Then add this configuration codes in @NgModule providers section.
providers: [
BookService,
{provide: LocationStrategy, useClass: HashLocationStrategy}
],
Now, open and edit src/app/app.component.html then replace all codes with this.
<router-outlet></router-outlet>
Back to src/app/book/book.component.html then replace all codes with this.
<div class="container">
<h1>Book Catalogue</h1>
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of books">
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>Show Detail</td>
</tr>
</tbody>
</table>
</div>
Now, we have to test our MEAN app with only list page. Build then run the application.
ng build
nodemon
Reopen again 'localhost:3000' from your browser, it should be redirected to book list page like this.
6. Create Angular 2 Component for Show Book Detail
Same as previous section, type this command to generate new component.
ng g component book-detail
Open and edit src/app/book-detail/book-detail.component.ts. Replace all codes with this.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BookService } from '../book.service';
@Component({
selector: 'app-book-detail',
templateUrl: './book-detail.component.html',
styleUrls: ['./book-detail.component.css']
})
export class BookDetailComponent implements OnInit {
book = {};
constructor(private route: ActivatedRoute, private bookService: BookService) { }
ngOnInit() {
this.getBookDetail(this.route.snapshot.params['id']);
}
getBookDetail(id) {
this.bookService.showBook(id).then((res) => {
this.book = res;
console.log(this.book);
}, (err) => {
console.log(err);
});
}
}
Open and edit src/app/book-detail/book-detail.component.html. Replace all codes with this.
<div class="container">
<h1>{{ book.title }}</h1>
<dl class="list">
<dt>ISBN</dt>
<dd>{{ book.isbn }}</dd>
<dt>Author</dt>
<dd>{{ book.author }}</dd>
<dt>Publisher</dt>
<dd>{{ book.publisher }}</dd>
<dt>Price</dt>
<dd>{{ book.price }}</dd>
<dt>Update Date</dt>
<dd>{{ book.updated_at }}</dd>
</dl>
</div>
7. Create Angular 2 Component for Add New Book
To create a component for add new Book, type this command as usually.
ng g component book-create
Add router to src/app/app.module.ts.
const appRoutes: Routes = [
{ path: '', redirectTo: 'books', pathMatch: 'full' },
{ path: 'books', component: BookComponent },
{ path: 'book-details/:id', component: BookDetailComponent },
{ path: 'book-create', component: BookCreateComponent }
];
Add 'book-create' link on book.component.html.
<h1>Book Catalogue
<a [routerLink]="['/book-create']" class="btn btn-default btn-lg">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a>
</h1>
Now, open and edit src/app/book/book-create.component.ts then replace all with this codes.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { BookService } from '../book.service';
@Component({
selector: 'app-book-create',
templateUrl: './book-create.component.html',
styleUrls: ['./book-create.component.css']
})
export class BookCreateComponent implements OnInit {
book = {};
constructor(private bookService: BookService, private router: Router) { }
ngOnInit() {
}
saveBook() {
this.bookService.saveBook(this.book).then((result) => {
let id = result['_id'];
this.router.navigate(['/book-details', id]);
}, (err) => {
console.log(err);
});
}
}
Modify src/app/book-create/book-create.component.html, replace all with this HTML tags.
<div class="container">
<h1>Add New Book</h1>
<div class="row">
<div class="col-md-6">
<form (ngSubmit)="saveBook()" #bookForm="ngForm">
<div class="form-group">
<label for="name">ISBN</label>
<input type="text" class="form-control" [(ngModel)]="book.isbn" name="isbn" required>
</div>
<div class="form-group">
<label for="name">Title</label>
<input type="text" class="form-control" [(ngModel)]="book.title" name="title" required>
</div>
<div class="form-group">
<label for="name">Author</label>
<input type="text" class="form-control" [(ngModel)]="book.author" name="author" required>
</div>
<div class="form-group">
<label for="name">Publisher</label>
<input type="text" class="form-control" [(ngModel)]="book.publisher" name="publisher" required>
</div>
<div class="form-group">
<label for="name">Price</label>
<input type="number" class="form-control" [(ngModel)]="book.price" name="price" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success" [disabled]="!bookForm.form.valid">Save</button>
</div>
</form>
</div>
</div>
</div>
8. Create Angular 2 Component for Edit Book
As usually, we will generate component for edit book. Type this command for doing that.
ng g component book-edit
Add route in src/app/app.module.ts so, it looks like this.
const appRoutes: Routes = [
{ path: '', redirectTo: 'books', pathMatch: 'full' },
{ path: 'books', component: BookComponent },
{ path: 'book-details/:id', component: BookDetailComponent },
{ path: 'book-create', component: BookCreateComponent },
{ path: 'book-edit/:id', component: BookEditComponent }
];
Open and edit again src/app/book-details/book-details.component.html and add edit routeLink in the last line.
<div class="row">
<div class="col-md-12">
<a [routerLink]="['/book-edit', book._id]" class="btn btn-success">EDIT</a>
</div>
</div>
Now, open and edit src/app/book-edit/book-edit.component.ts then replace all codes with this.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BookService } from '../book.service';
@Component({
selector: 'app-book-edit',
templateUrl: './book-edit.component.html',
styleUrls: ['./book-edit.component.css']
})
export class BookEditComponent implements OnInit {
book = {};
constructor(private bookService: BookService, private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
this.getBook(this.route.snapshot.params['id']);
}
getBook(id) {
this.bookService.showBook(id).then((res) => {
this.book = res;
console.log(this.book);
}, (err) => {
console.log(err);
});
}
updateBook(id) {
this.bookService.updateBook(id, this.book).then((result) => {
let id = result['_id'];
this.router.navigate(['/book-details', id]);
}, (err) => {
console.log(err);
});
}
}
Open and edit src/app/book-edit/book-edit.component.html then replace all codes with this.
<div class="container">
<h1>Edit Book</h1>
<div class="row">
<div class="col-md-6">
<form (ngSubmit)="updateBook(book._id)" #bookForm="ngForm">
<div class="form-group">
<label for="name">ISBN</label>
<input type="text" class="form-control" [(ngModel)]="book.isbn" name="isbn" required>
</div>
<div class="form-group">
<label for="name">Title</label>
<input type="text" class="form-control" [(ngModel)]="book.title" name="title" required>
</div>
<div class="form-group">
<label for="name">Author</label>
<input type="text" class="form-control" [(ngModel)]="book.author" name="author" required>
</div>
<div class="form-group">
<label for="name">Publisher</label>
<input type="text" class="form-control" [(ngModel)]="book.publisher" name="publisher" required>
</div>
<div class="form-group">
<label for="name">Price</label>
<input type="number" class="form-control" [(ngModel)]="book.price" name="price" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success" [disabled]="!bookForm.form.valid">Update</button>
</div>
</form>
</div>
</div>
</div>
9. Create Delete Function on Book-Detail Component
The last thing in this MEAN app tutorial is to create delete function that put on the book detail component. Open and edit src/app/book-detail/book-detail.component.ts then add this function.
deleteBook(id) {
this.bookService.deleteBook(id).then((result) => {
this.router.navigate(['/books']);
}, (err) => {
console.log(err);
});
}
Don't forget to add import for Router and inject Router in constructor.
import { ActivatedRoute, Router } from '@angular/router';
constructor(private route: ActivatedRoute, private router: Router, private bookService: BookService) { }
Add delete button in src/app/book-detail/book-detail.component.html on the right of Edit routerLink.
<div class="row">
<div class="col-md-12">
<a [routerLink]="['/book-edit', book._id]" class="btn btn-success">EDIT</a>
<button class="btn btn-danger" type="button" (click)="deleteBook(book._id)">DELETE</button>
</div>
</div>
This is it, now we have completed build CRUD web application using MEAN stack with Angular 2. Rebuild the application then run again to test the fully functionalities.
ng build
nodemon
Please feel free to give suggestion or anything to improve this tutorial.
The full source code can find on Our GitHub.
Thanks.