A comprehensive step by step tutorial on building MEAN Stack (Angular 5) Create-Read-Update-Delete (CRUD) Web Application from scratch using Angular CLI. As you know that Angular 5 has been launched a few days ago, we need to test out their feature especially with MEAN Stack. The steps in this tutorial will be started with Angular 5 application generation then replace the Angular server with Express.js server.

Shortcut to the steps:

What is MEAN Stack?

MEAN Stack is the Full-Stack application that uses Javascript as the development language. MEAN stands for MongoDB, Express, Angular, and Node.js. MongoDB and Express on Node.js environment used as backend and Angular 5 used as the front end. Like the previous tutorial about MEAN stack, we will be wrapping Express and Angular 5 together. run as one server.

The following tools, frameworks, and modules are required for this tutorial:

Node.js (recommended version) Angular CLI 1.5 Angular 5 MongoDB Express.js Mongoose.js IDE or Text Editor

We assume that you already installed Node.js and runnable in the Terminal (Linux/Mac) or Node.js command line (Windows). Also, you have installed MongoDB and run Mongo daemon on your machine.



Update Angular CLI and Create Angular 5 Application

We start this MEAN Stack tutorial with an abbreviation "A" or Angular side. First, we have to update the Angular CLI to the latest version (1.5 when this tutorial was written). Open the terminal or Node command line then go to your projects folder. Type this command for updating Angular CLI.

sudo npm install -g @angular/cli

Every module, libraries or framework installation are using NPM that means we are implementing an abbreviation "N" or Node.js of MEAN Stack. You can exclude `sudo` when you update or install Angular CLI on Windows/Node command line. Now, type this command to create a new Angular 2 application.

ng new mean-angular5

Go to the newly created application folder.

cd ./mean-angular5

Run the Angular 2 application by typing this command.

ng serve

You see the compilation process faster than the previous Angular version.

** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** Date: 2017-11-10T23:12:58.186Z - Hash: a8de16d629b34a42bbda Time: 9459ms chunk {inline} inline.bundle.js (inline) 5.79 kB [entry] [rendered] chunk {main} main.bundle.js (main) 20.6 kB [initial] [rendered] chunk {polyfills} polyfills.bundle.js (polyfills) 553 kB [initial] [rendered] chunk {styles} styles.bundle.js (styles) 33.8 kB [initial] [rendered] chunk {vendor} vendor.bundle.js (vendor) 7.03 MB [initial] [rendered] webpack: Compiled successfully.

Now, open the browser then go to `http://localhost:4200` you should see this page.



Replace Web Server with Express.js

Now, we continue this MEAN stack with an abbreviation "E" or Express.js. Close the running Angular app first by press `ctrl+c` then type this command for adding Express.js modules and its dependencies.

npm install --save express body-parser morgan body-parser serve-favicon

Then, add bin folder and www file inside the bin folder.

mkdir bin touch bin/www

Open and edit www file then add these 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": "ng build && node ./bin/www", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" },

Now, create app.js in the root of the project folder.

touch app.js

Open and edit app.js then add all these 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('/books', 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 `routes/book.js` file then add these lines of codes.

var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.send('Express RESTful API'); }); module.exports = router;

Now, run the server using this command.

npm start

You will see the previous Angular landing page when you point your browser to `http://localhost:3000`. When you change the address to `http://localhost:3000/book` you will see this page.

Now, we have RESTful API with the compiled Angular 5 front end.



Install and Configure Mongoose.js

Now, we will accomplish an abbreviation "M" or MongoDB of MEAN Stack. We need to access data from MongoDB. For that, we will install and configure Mongoose.js. On the terminal type this command after stopping the running Express server.

npm install --save mongoose bluebird

Open and edit `app.js` then add these lines after another variable line.

var mongoose = require('mongoose'); mongoose.Promise = require('bluebird'); mongoose.connect('mongodb://localhost/mean-angular5', { useMongoClient: true, promiseLibrary: require('bluebird') }) .then(() => console.log('connection succesful')) .catch((err) => console.error(err));

Now, run the MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection succesful

If you are still using built-in Mongoose Promise library, you will get this deprecated warning on the terminal.

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

That's the reason why we added `bluebird` modules and register it as Mongoose Promise library.



Create Mongoose.js Model

Add a models folder on the root of the project folder for hold Mongoose.js model files.

mkdir models

Create new Javascript file that uses for Mongoose.js model. We will create a model of Book collection.

touch models/Book.js

Now, open and edit that file and add Mongoose require.

var mongoose = require('mongoose');

Then add model fields like this.

var BookSchema = new mongoose.Schema({ isbn: String, title: String, author: String, description: String, published_year: String, publisher: String, updated_date: { type: Date, default: Date.now }, });

That Schema will mapping to MongoDB collections called book. If you want to know more about Mongoose Schema Datatypes you can find it here. Next, export that schema.

module.exports = mongoose.model('Book', BookSchema);



Create Routes for Accessing Book Data via REST API

Open and edit again "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;

Run again the Express server then open the other terminal or command line to test the Restful API by type this command.

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-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w" Date: Fri, 10 Nov 2017 23:53:52 GMT Connection: keep-alive

Now, let's populate Book collection with initial data sent from REST API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author": "Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com" }' localhost:3000/book

You will see this response to the terminal if success.

HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 415 ETag: W/"19f-SB/dEQyffaTjobOBJbvmwCn7WJA" Date: Fri, 10 Nov 2017 23:58:11 GMT Connection: keep-alive {"__v":0,"isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author":"Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com","_id":"5a063d123cf0792af12ce45d","updated_date":"2017-11-10T23:58:10.971Z"}MacBook-Pro:mean-angular5



Create Angular 5 Component for Displaying Book List

To create an Angular 5 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.

create src/app/book/book.component.css (0 bytes) create src/app/book/book.component.html (23 bytes) create src/app/book/book.component.spec.ts (614 bytes) create src/app/book/book.component.ts (321 bytes) update src/app/app.module.ts (390 bytes)

Before adding any functionality to the component, we need to add `HttpClientModule` to `app.module.ts`. Open and edit `src/app/app.module.ts` then add this import.

import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http';

Add it to `@NgModule` imports after `BrowserModule`.

imports: [ BrowserModule, FormsModule, HttpClientModule ],

Now, we will be making a request to Book REST API using this Angular `HttpClient` module. Open and edit `src/app/book/book.component.ts` then add this import.

import { HttpClient } from '@angular/common/http';

Inject `HttpClient` to the constructor.

constructor(private http: HttpClient) { }

Add an array variable for holding books data before the constructor.

books: any;

Add a few lines of codes for getting a list of book data from RESTful API inside `ngOnInit` function.

ngOnInit() { this.http.get('/book').subscribe(data => { this.books = data; }); }

Now, we can display the book list on the page. Open and edit `src/app/book/book.component.html` then replace all tags with these lines of HTML tags.

<div class="container"> <h1>Book List</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>

That HTML tags include style class from Bootstrap CSS library. Open and edit `src/index.html` then add the Bootstrap CSS and JS library.

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MeanAngular5</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></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>



Create Angular 5 Routes to Book Component

To use book component as the default landing page, open and edit `src/app/app.module.ts` the added import for Routing.

import { RouterModule, Routes } from '@angular/router';

Create constant router for routing to book component before `@NgModule`.

const appRoutes: Routes = [ { path: 'books', component: BookComponent, data: { title: 'Book List' } }, { path: '', redirectTo: '/books', pathMatch: 'full' } ];

In @NgModule imports, section adds ROUTES constant, so the imports section will be like this.

imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot( appRoutes, { enableTracing: true } // <-- debugging purposes only ) ],

To activate that routes in Angular 5, open and edit `src/app/app.component.html` then replace all codes with this.

<router-outlet></router-outlet>

Now, we have to test our MEAN app with only list page. Build then run the application.

npm start

You should see this page when pointing to `http://localhost:3000` or `http://localhost:3000/books`.



Create Angular 5 Component for Displaying Book Detail

The same as the previous section, type this command to generate a new component.

ng g component book-detail

Add router to `src/app/app.module.ts` routes constant.

const appRoutes: Routes = [ { path: 'books', component: BookComponent, data: { title: 'Book List' } }, { path: 'book-details/:id', component: BookDetailComponent, data: { title: 'Book Details' } }, { path: '', redirectTo: '/books', pathMatch: 'full' } ];

Open and edit `src/app/book-detail/book-detail.component.ts`. Replace all codes with this.

import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-book-detail', templateUrl: './book-detail.component.html', styleUrls: ['./book-detail.component.css'], encapsulation: ViewEncapsulation.None }) export class BookDetailComponent implements OnInit { book = {}; constructor(private route: ActivatedRoute, private http: HttpClient) { } ngOnInit() { this.getBookDetail(this.route.snapshot.params['id']); } getBookDetail(id) { this.http.get('/book/'+id).subscribe(data => { this.book = data; }); } }

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>



Create Angular 5 Component for Add New Book

To create a component to add new Book, type this command as usual.

ng g component book-create

Add router to `src/app/app.module.ts` routes constant.

const appRoutes: Routes = [ { path: 'books', component: BookComponent, data: { title: 'Book List' } }, { path: 'book-details/:id', component: BookDetailComponent, data: { title: 'Book Details' } }, { path: 'book-create', component: BookCreateComponent, data: { title: 'Create Book' } }, { path: '', redirectTo: '/books', pathMatch: 'full' } ];

Add 'book-create' link on `src/app/book/book.component.html`.

<h1>Book List <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 these codes.

import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-book-create', templateUrl: './book-create.component.html', styleUrls: ['./book-create.component.css'], encapsulation: ViewEncapsulation.None }) export class BookCreateComponent implements OnInit { book = {}; constructor(private http: HttpClient, private router: Router) { } ngOnInit() { } saveBook() { this.http.post('/book', this.book) .subscribe(res => { let id = res['_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">Published Year</label> <input type="number" class="form-control" [(ngModel)]="book.published_year" name="published_year" 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"> <button type="submit" class="btn btn-success" [disabled]="!bookForm.form.valid">Save</button> </div> </form> </div> </div> </div>



Create Angular 5 Component for Edit Book

As usual, we will generate a 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: 'books', component: BookComponent, data: { title: 'Book List' } }, { path: 'book-details/:id', component: BookDetailComponent, data: { title: 'Book Details' } }, { path: 'book-create', component: BookCreateComponent, data: { title: 'Create Book' } }, { path: 'book-edit/:id', component: BookEditComponent, data: { title: 'Edit Book' } }, { path: '', redirectTo: '/books', pathMatch: 'full' } ];

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, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-book-edit', templateUrl: './book-edit.component.html', styleUrls: ['./book-edit.component.css'], encapsulation: ViewEncapsulation.None }) export class BookEditComponent implements OnInit { book = {}; constructor(private http: HttpClient, private router: Router, private route: ActivatedRoute) { } ngOnInit() { this.getBook(this.route.snapshot.params['id']); } getBook(id) { this.http.get('/book/'+id).subscribe(data => { this.book = data; }); } updateBook(id, data) { this.http.put('/book/'+id, data) .subscribe(res => { let id = res['_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">Published Year</label> <input type="number" class="form-control" [(ngModel)]="book.published_year" name="published_year" 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"> <button type="submit" class="btn btn-success" [disabled]="!bookForm.form.valid">Update</button> </div> </form> </div> </div> </div>



Create Delete Function on Book-Detail Component

Open and edit `src/app/book-detail/book-detail`.component.ts then add `Router` module to `@angular/router`.

import { ActivatedRoute, Router } from '@angular/router';

Inject `Router` in the constructor params.

constructor(private router: Router, private route: ActivatedRoute, private http: HttpClient) { }

Add this function for delete book.

deleteBook(id) { this.http.delete('/book/'+id) .subscribe(res => { this.router.navigate(['/books']); }, (err) => { console.log(err); } ); }

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>



Run and Test the MEAN Stack (Angular 5) CRUD Application

Now, it's a time for testing the MEAN Stack (Angular 5) CRUD Application.

npm start

And here we are.

If you need the full working source code, you can find it on our GitHub.

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:

Thanks!