Angular6 CRUD Application with NodeJS and Mongodb

    Aug 30, 2018       by Pankaj Kumar
MEAN-CRUD.jpg

Hey there, Today we will proceed to create a demo for CRUD with Mongo, Express, Angular6(MEAN) and Nodejs from scratch using Angular CLI.

Below are the  requirements for creating the  CRUD on MEAN

- Node.js
- Angular CLI
- Angular 6
- MongoDB
- IDE or Text Editor

We  assume that you have already available the above tools/frameworks and you are familiar with all the  above that what individually actually does.

So now we will proceed step by step to achieve the task.

1. Update Angular CLI and Create Angular 6 Application

At first,  We have to update the Angular CLI to the latest version. Open the  terminal then go to the  project folder and then type the  below command to update the Angular CLI

sudo npm install -g @angular/cli

 

Once the above task finishes, Next task is to create new angular application with below command. So go to your project folder and then type below command:

ng new angular6-crud

 

then go to the newly created folder of angular application with cd /angular5-crud  and type ng serve. Now, open the browser then go to http://localhost:4200 you should see this page. 

MEAN CRUD demo

 

2. Create a server with node.js express and MongoDB for REST APIs

create a separate folder named server for server-side stuff,  Then move inside folder and create server.js by typing touch server.js

Let's have a look on the server.js file

 

 
let app = require('express')(),
server = require('http').Server(app),
bodyParser = require('body-parser')
express = require('express'),
cors = require('cors'),
http = require('http'),
path = require('path');
 
let documentRoute = require('./Routes/document'),
util = require('./Utilities/util');
 
let mongoose = require('./Utilities/mongooseConfig')();
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false }));
 
app.use(cors());
 
app.use(function(err, req, res, next) {
return res.send({ "statusCode": util.statusCode.ONE, "statusMessage": util.statusMessage.SOMETHING_WENT_WRONG });
});
 
app.use('/document', documentRoute);
 
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next();
});
 
/*Code to run the angular build*/
// app.get('*', (req, res) => {
// res.sendFile(path.join(__dirname, '../server/client/dist/index.html'));
// })
 
 
 
server.listen(3000,function(){
console.log('app listening on port: 3000');
});
 

 

In the above file we can see, at the top, there are required packages for the app. Below that body parsing, middleware and routing is done.

The next task is to create routes and create a file document.js . So creating a folder name 'Routes' and adding document.js within it.

Add the below code for routing in document.js inside routing folder

 

 
let express = require('express'),
router = express.Router(),
util = require('../Utilities/util'),
documentService = require('../Services/document');
 
/**Api to create document */
router.post('/create-document', (req, res) => {
documentService.createDocument(req.body, (data) => {
res.send(data);
});
});
 
// /**Api to update document */
router.put('/update-document', (req, res) => {
documentService.updateDocument(req.body, (data) => {
res.send(data);
});
});
 
// /**Api to delete the document */
router.delete('/delete-document', (req, res) => {
documentService.deleteDocument(req.query, (data) => {
res.send(data);
});
});
 
/**Api to get the list of document */
router.get('/get-document', (req, res) => {
documentService.getDocument(req.query, (data) => {
res.send(data);
});
});
 
// /**API to get the document by id... */
router.get('/get-document-by-id', (req, res) => {
documentService.getDocumentById(req.query, (data) => {
res.send(data);
});
});
 
module.exports = router;
 

 

Now create a folder named Utilities for all config, common methods and mongoose config.

Now I am adding config  values in a file named config.js

 

 
let environment = "dev";
 
let serverURLs = {
"dev": {
"NODE_SERVER": "http://localhost",
"NODE_SERVER_PORT": "3000",
"MONGO_DB": 'mongodb://localhost:27017/angular6-crud'
}
}
 
let config = {
"NODE_SERVER_PORT": {
"port": `${serverURLs[environment].NODE_SERVER_PORT}`
},
"NODE_SERVER_URL": {
"url": `${serverURLs[environment].NODE_SERVER}`
},
"DB_URL": {
"url": `${serverURLs[environment].MONGO_DB}`
},
};
 
module.exports = {
config: config
};
 

 

Now configure mongoose. So I am writing the connection with database in a separate file. So creating a file named mongooseConfig.js under Utilities folder and adding the below line of code for mongoose:

 

 
var mongoose = require('mongoose');
mongoose.set('debug', true);
var config = require("../Utilities/config").config;
 
module.exports = function() {
mongoose.Promise = require('bluebird');
console.log(config.DB_URL.url);
var db = mongoose.connect('mongodb://localhost:27017/angular6-crud');
require('../Models/Document');

return db;

};
 

 

Now I am creating separate file name util.js to save common methods and common status code/message:

 

 
// Define Error Codes
let statusCode = {
OK: 200,
FOUR_ZERO_FOUR: 404,
FOUR_ZERO_THREE: 403,
FOUR_ZERO_ONE: 401,
FIVE_ZERO_ZERO: 500
};
 
// Define Error Messages
let statusMessage = {
SERVER_BUSY : 'Our Servers are busy. Please try again later.',
DATA_UPDATED: 'Data updated successfully.',
DELETE_DATA : 'Delete data successfully',
 
};
 
module.exports = {
statusCode: statusCode,
statusMessage: statusMessage
}
 

 

Now the next part is model, So create a folder named Models and create a file Document.js and add the below code in it:

 

 
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
 
let Document = new Schema({
document_title: {
type: String,
trim: true,
default: ""
},
desc: {
type: String,
trim: true,
default: ""
},
publisher: {
type: String,
trim: true,
default: ""
},
created_on:{
type : Date,
default: Date.now
}
});
 
module.exports = mongoose.model('Document', Document);
 

 

Now create DAO folder and add a file documentDAO.js for mongoose common functions:

 

 
'use strict';
 
var Models = require('../Models/Document');
 
var updateDocument = function(criteria, dataToSet, options, callback) {
options.lean = true;
options.new = true;
Models.findOneAndUpdate(criteria, dataToSet, options, callback);
};
 
//Insert Student in DB
var createDocument = function(objToSave, callback) {
new Models(objToSave).save(callback)
};
 
//Delete Student in DB
var deleteDocument = function(criteria, callback) {
Models.findOneAndRemove(criteria, callback);
};
 
//Get Students from DB
var getDocument = function(criteria, projection, options, callback) {
options.lean = true;
Models.find(criteria, projection, options, callback);
};
 
module.exports = {
updateDocument: updateDocument,
createDocument: createDocument,
deleteDocument: deleteDocument,
getDocument: getDocument
}
 

 

Now one create Services folder and add a file document.js for all the logic of API

 

 
let async = require('async'),
parseString = require('xml2js').parseString;
 
let util = require('../Utilities/util'),
documentDAO = require('../DAO/documentDAO');
 
 
/**API to create the document */
let createDocument = (data, callback) => {
async.auto({
document: (cb) => {
var dataToSet = {
"document_title":data.document_title?data.document_title:'',
"desc":data.desc,
"publisher":data.publisher
}
documentDAO.createDocument(dataToSet, (err, dbData) => {
console.log(err,'error',dbData);
if (err) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.SERVER_BUSY });
return;
}
 
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DATA_UPDATED });
return;
});
}
//]
}, (err, response) => {
callback(response.document);
});
}
 
/**API to update the document */
let updateDocument = (data,callback) => {
async.auto({
documentUpdate :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.PARAMS_MISSING })
return;
}
 
var criteria = {
_id : data.id
}
var dataToSet={
"document_title": data.document_title?data.document_title:"",
"desc":data.desc?data.desc:"",
"publisher":data.publisher?data.publisher:""
}
console.log(criteria,'test',dataToSet);
                    documentDAO.updateDocument(criteria, dataToSet, (err, dbData)=>{
                        if(err){
cb(null,{"statusCode":util.statusCode.FOUR_ZERO_ONE,"statusMessage":util.statusMessage.SERVER_BUSY});
                        return
                        }
                        else{
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DATA_UPDATED,"result":dbData });                       
                        }
                    });
}
}, (err,response) => {
callback(response.documentUpdate);
});
}
 
// /**API to delete the subject */
let deleteDocument = (data,callback) => {
async.auto({
removeDocument :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.PARAMS_MISSING })
return;
}
var criteria = {
_id : data.id,
}
documentDAO.deleteDocument(criteria,(err,dbData) => {
if (err) {
console.log(err);
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.SERVER_BUSY });
return;
}
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DELETE_DATA });
});
}
}, (err,response) => {
callback(response.removeDocument);
});
}
 
// /***API to get the document list */
let getDocument = (data, callback) => {
async.auto({
document: (cb) => {
documentDAO.getDocument({},{},{},(err, data) => {
if (err) {
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage": util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data);
return;
});
}
}, (err, response) => {
callback(response.document);
})
}
 
// /***API to get the document detail by id */
let getDocumentById = (data, callback) => {
async.auto({
document: (cb) => {
let criteria = {
"_id":data.id
}
documentDAO.getDocument(criteria,{},{},(err, data) => {
if (err) {
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage": util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data[0]);
return;
});
}
}, (err, response) => {
callback(response.document);
})
}
 
module.exports = {
createDocument : createDocument,
updateDocument : updateDocument,
deleteDocument : deleteDocument,
getDocument : getDocument,
getDocumentById : getDocumentById
};
 

 

3. Create angular component for performing CRUD task of document

ng g component document

Above command will generate all required files for build document component and also automatically added this component to app.module.ts.

create src/app/document/document.component.css (0 bytes)
create src/app/document/document.component.html (23 bytes)
create src/app/document/document.component.spec.ts (614 bytes)
create src/app/document/document.component.ts (321 bytes)
update src/app/app.module.ts (390 bytes)sdfsdf s f sd fsdf

 

Now we need to add `HttpClientModule` to `app.module.ts`. Open and edit `src/app/app.module.ts` then add this import. And add it to `@NgModule` imports after `BrowserModule`. Now our app.module.ts will have following code:

 

 
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
 
import { AppComponent } from './app.component';
import { DocumentComponent } from './document.component';
import { DocumentService } from './document.service';
 
@NgModule({
imports: [
BrowserModule,
HttpModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
DocumentComponent
],
providers: [
DocumentService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
 

 

Now create a service file where we will make all the request to the server for CRUD operation. Command for creating service is ng g service crud , for now I have just created a file named it document.service.ts. Let's have a look in the code inside this file.

 

import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
 
import { Document } from './document';
 
@Injectable()
export class DocumentService {
//URL for CRUD operations
    documentUrl = "http://localhost:3000/document";
    //Create constructor to get Http instance
    constructor(private http:Http) {
    }
    
    //Fetch all documents
 
    
getAllDocuments(): Observable<Document[]> {
return this.http.get(this.documentUrl+"/get-document")
              .map(this.extractData)
         .catch(this.handleError);
    }
    
    //Create document
createDocument(document: Document):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: cpHeaders });
return this.http.post(this.documentUrl+"/create-document", document, options)
.map(success => success.status)
.catch(this.handleError);
}
    //Fetch document by id
getDocumentById(documentId: string): Observable<Document> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        return this.http.get(this.documentUrl +"/get-document-by-id?id="+ documentId)
             .map(this.extractData)
             .catch(this.handleError);
}   
    //Update document
updateDocument(document: Document):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
return this.http.put(this.documentUrl +"/update-document", document, options)
.map(success => success.status)
.catch(this.handleError);
}
//Delete document   
deleteDocumentById(documentId: string): Observable<number> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        return this.http.delete(this.documentUrl +"/delete-document?id="+ documentId)
             .map(success => success.status)
             .catch(this.handleError);
}   
    private extractData(res: Response) {
        let body = res.json();
return body;
}
private handleError (error: Response | any) {
        console.error(error.message || error);
        return Observable.throw(error.status);
}
}

 

In the above file we have made all the http request for the CRUD operation. Observables of rxjs library has been used to handle the data fetching from http request.

 

Now let's move to the next file, document.component.ts. Here we have all the login part of the app. Let's have a look code inside this file:

 

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
 
import { DocumentService } from './document.service';
import { Document } from './document';
 
@Component({
selector: 'app-document',
templateUrl: './document.component.html',
styleUrls: ['./document.component.css']
})
export class DocumentComponent implements OnInit {
//Component properties
allDocuments: Document[];
statusCode: number;
requestProcessing = false;
documentIdToUpdate = null;
processValidation = false;
//Create form
documentForm = new FormGroup({
document_title: new FormControl('', Validators.required),
     desc: new FormControl('', Validators.required),
     publisher: new FormControl('', Validators.required)  
});
//Create constructor to get service instance
constructor(private documentService: DocumentService) {
}
//Create ngOnInit() and and load documents
ngOnInit(): void {
     this.getAllDocuments();
}
 
//Fetch all documents
 getAllDocuments() {
        this.documentService.getAllDocuments()
         .subscribe(
data => {
                    this.allDocuments = data;
                },
                errorCode => this.statusCode = errorCode);
    }
 
//Handle create and update document
onDocumentFormSubmit() {
     this.processValidation = true;
     if (this.documentForm.invalid) {
     return; //Validation failed, exit from method.
     }
     //Form is valid, now perform create or update
this.preProcessConfigurations();
     let document = this.documentForm.value;
     if (this.documentIdToUpdate === null) {
     //Generate v id then create document
this.documentService.getAllDocuments()
     .subscribe(documents => {
         //Generate document id   
         let maxIndex = documents.length - 1;
         let documentWithMaxIndex = documents[maxIndex];
         let documentId = documentWithMaxIndex._id + 1;
         document.id = documentId;
         //Create document
    this.documentService.createDocument(document)
             .subscribe(successCode => {
                    this.statusCode = successCode;
                    this.getAllDocuments(); 
                    this.backToCreateDocument();
                 },
                 errorCode => this.statusCode = errorCode
             );
         });        
     } else {
  //Handle update document
         document.id = this.documentIdToUpdate;       
     this.documentService.updateDocument(document)
     .subscribe(successCode => {
         this.statusCode = successCode;
                 this.getAllDocuments(); 
                    this.backToCreateDocument();
             },
         errorCode => this.statusCode = errorCode);  
     }
}
//Load document by id to edit
loadDocumentToEdit(documentId: string) {
this.preProcessConfigurations();
this.documentService.getDocumentById(documentId)
     .subscribe(document => {
         this.documentIdToUpdate = document._id;
                    this.documentForm.setValue({ document_title: document.document_title, publisher: document.publisher, desc: document.desc });
                    this.processValidation = true;
                    this.requestProcessing = false;
         },
         errorCode => this.statusCode = errorCode);
}
//Delete document...
deleteDocument(documentId: string) {
this.preProcessConfigurations();
this.documentService.deleteDocumentById(documentId)
     .subscribe(successCode => {
         //this.statusCode = successCode;
                    //Expecting success code 204 from server
                    this.statusCode = 204;
                 this.getAllDocuments(); 
                 this.backToCreateDocument();
             },
         errorCode => this.statusCode = errorCode);
}
//Perform preliminary processing configurations
preProcessConfigurations() {
this.statusCode = null;
     this.requestProcessing = true;
}
//Go back from update to create
backToCreateDocument() {
this.documentIdToUpdate = null;
this.documentForm.reset();    
     this.processValidation = false;
}
}
 

 

Now we have to show the task over browser, So lets have a look inside document.component.html file.

 

<h1>Angular 6 CRUD Example</h1>
<h3 *ngIf="articleIdToUpdate; else create">
Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
<h3> Create New Document </h3>
</ng-template>
<div>
<form [formGroup]="documentForm" (ngSubmit)="onDocumentFormSubmit()">
<table>
<tr>
<td>Enter Document Title</td>
<td>
<input formControlName="document_title">
<label *ngIf="documentForm.get('document_title').invalid && processValidation" [ngClass] = "'error'"> Title is required. </label>
</td>
</tr>
<tr>
<td>Enter Description</td>
<td>
<input formControlName="desc">
   <label *ngIf="documentForm.get('desc').invalid && processValidation" [ngClass] = "'error'"> Description is required. </label>
</td>
</tr> 
<tr>
<td>Enter Publisher</td>
<td>
<input formControlName="publisher">
<label *ngIf="documentForm.get('publisher').invalid && processValidation" [ngClass] = "'error'"> Publisher is required. </label>
</td>
</tr>
<tr><td colspan="2">
   <button *ngIf="!documentIdToUpdate">CREATE</button>
    <button *ngIf="documentIdToUpdate">UPDATE</button>
   <button (click)="backToCreateDocument()" *ngIf="documentIdToUpdate">Go Back</button>
  </td></tr>
</table>
</form>
<br/>
<div *ngIf="statusCode; else processing">
<div *ngIf="statusCode === 201" [ngClass] = "'success'">
   Document added successfully.
</div>
<div *ngIf="statusCode === 409" [ngClass] = "'success'">
Document already exists.
</div>   
<div *ngIf="statusCode === 200" [ngClass] = "'success'">
Document updated successfully.
</div>   
<div *ngIf="statusCode === 204" [ngClass] = "'success'">
Document deleted successfully.
</div>   
<div *ngIf="statusCode === 500" [ngClass] = "'error'">
Internal Server Error.
</div> 
</div>
<ng-template #processing>
  <img *ngIf="requestProcessing" src="assets/images/loading.gif">
</ng-template>
</div>
<h3>Document List</h3>
<table *ngIf="allDocuments">
<tr>
<th> Id</th>
<th>Title</th>
<th>Publisher</th>
<th>Description</th>
<th></th>
<th></th>
</tr>
<tr *ngFor="let document of allDocuments" >
<td>{{document._id}}</td>
<td>{{document.document_title}}</td>
<td>{{document.desc}}</td>
<td>{{document.publisher}}</td>
<td><button type="button" (click)="loadDocumentToEdit(document._id)">Edit</button> </td>
<td><button type="button" (click)="deleteDocument(document._id)">Delete</button></td>
</tr>
</table>

 

Now since I have created server and client two separate folder for nodejs and angular task. So will run both the apps with npm start over two tabs of terminal.

On the browser, over link http://localhost:4200. App will look like below

angular6-crud-app

 

That’s all for now. Thank you for reading and I hope this post will be very helpful for creating CRUD operations with angular6,nodejs & mongodb.

Let me know your thoughts over the email demo.jsonworld@gmail.com. I would love to hear them and If you like this article, share with your friends.

 

Find complete source code over GitHub