Angular 12 + Nodejs/Express + Mongoose CRUD MongoDB – Get/Post/Put/Delete
Mongoose is a MongoDB object modeling tool that provides a schema-based solution to model data. In the tutorial, we will show how to build get/post/put/delete
requests from Angular 12 Client to MongoDB with NodeJs/Express RestAPIs using Mongoose ODM.
Related posts:
– Node.js/Express RestAPIs – Angular 12 HttpClient – Get/Post/Put/Delete requests + Bootstrap 4
– Crud RestAPIs with NodeJS/Express, MongoDB using Mongoose
Technologies
- Angular 12
- RxJS 6
- Bootstrap 4
- Visual Studio Code – version 1.24.0
- Nodejs – v8.11.3
- Mongoose
- MongoDB
Demo
Overview
Goal
We create 2 projects:
– Angular Client Project:
– Node.js RestAPIs project:
UserCase
Start Node.js server -> Logs:
App listening at http://:::8080
Successfully connected to MongoDB.
Customer collection removed
>>> Done - Initial Data!
-> MongoDB’s documents:
> db.customers.find();
{ "_id" : ObjectId("5b432fb558cbf6427cc7d527"), "firstname" : "Joe", "lastname" : "Thomas", "age" : 36, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d528"), "firstname" : "Peter", "lastname" : "Smith", "age" : 18, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d529"), "firstname" : "Lauren", "lastname" : "Taylor", "age" : 31, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d52a"), "firstname" : "Mary", "lastname" : "Taylor", "age" : 24, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d52b"), "firstname" : "David", "lastname" : "Moore", "age" : 25, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d52d"), "firstname" : "Michael", "lastname" : "Brown", "age" : 45, "__v" : 0 }
{ "_id" : ObjectId("5b432fb558cbf6427cc7d52c"), "firstname" : "Holly", "lastname" : "Davies", "age" : 27, "__v" : 0 }
– Angular client retrieve all customers from Node.js RestAPIs:
– Angular client update a customer -> Change the firstname of first customer: ‘Joe’ to ‘Robert’.
-> result:
– Delete ‘Peter’ customer:
– Add a new customer:
-> result:
– Check final customer’s list:
-> MongoDB’s documents:
> db.customers.find();
{ "_id" : ObjectId("5b434e5ce30d33364834a8e9"), "firstname" : "Robert", "lastname" : "Thomas", "age" : 36, "__v" : 0 }
{ "_id" : ObjectId("5b434e5ce30d33364834a8eb"), "firstname" : "Lauren", "lastname" : "Taylor", "age" : 31, "__v" : 0 }
{ "_id" : ObjectId("5b434e5ce30d33364834a8ec"), "firstname" : "Mary", "lastname" : "Taylor", "age" : 24, "__v" : 0 }
{ "_id" : ObjectId("5b434e5ce30d33364834a8ed"), "firstname" : "David", "lastname" : "Moore", "age" : 25, "__v" : 0 }
{ "_id" : ObjectId("5b434e5ce30d33364834a8ef"), "firstname" : "Michael", "lastname" : "Brown", "age" : 45, "__v" : 0 }
{ "_id" : ObjectId("5b434e5ce30d33364834a8ee"), "firstname" : "Holly", "lastname" : "Davies", "age" : 27, "__v" : 0 }
{ "_id" : ObjectId("5b435016e30d33364834a8f0"), "firstname" : "Maria", "lastname" : "Garcia", "age" : 39, "__v" : 0 }
>
Node.js/Express RestAPIs
Node.js exposes 5 RestAPIs as below:
router.post(‘/api/customers’, customers.create);
router.get(‘/api/customers’, customers.findAll);
router.get(‘/api/customers/:id’, customers.findOne);
router.put(‘/api/customers’, customers.update);
router.delete(‘/api/customers/:id’, customers.delete);
– Configure cross-origin
for Angular-Client which running at port: 4200
.
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
Angular 12 HttpClient
Use Angular HttpClient APIs to do Get/Post/Put/Delete
requests to Node.js RestAPIs:
getCustomers (): Observable {
return this.http.get(this.customersUrl)
}
getCustomer(id: string): Observable {
const url = `${this.customersUrl}/${id}`;
return this.http.get(url);
}
addCustomer (customer: Customer): Observable {
return this.http.post(this.customersUrl, customer, httpOptions);
}
deleteCustomer (customer: Customer | string): Observable {
const id = typeof customer === 'string' ? customer : customer._id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete(url, httpOptions);
}
updateCustomer (customer: Customer): Observable {
return this.http.put(this.customersUrl, customer, httpOptions);
}
Practice
Express Mongoose RestAPIs
Setting up NodeJs/Express project
Following the guide to create a NodeJS/Express project.
Install Express, Mongoose, Body-Parser, and Cors:
$npm install express mongoose body-parse cors --save
– Body-Parser -> Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
– Express is one of the most popular web frameworks for NodeJs which is built on top of Node.js http module, and adds support for routing, middleware, view system etc.
– Cors is a mechanism that uses HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin.
– Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL …
-> package.json file:
{
"name": "nodejs-restapi-mongodb",
"version": "1.0.0",
"description": "NodeJs-RestApis-MongoDB",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"NodeJs-RestAPIs-MongoDB"
],
"author": "JSA",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.2",
"cors": "^2.8.4",
"express": "^4.16.3",
"mongoose": "^5.0.13",
"npm": "^5.8.0"
}
}
Mongoose Schema
– Create file ‘./app/models/customer.model.js’ ->
const mongoose = require('mongoose');
const CustomerSchema = mongoose.Schema({
firstname: String,
lastname: String,
age: { type: Number, min: 18, max: 65, required: true }
});
Configure MongoDB’s URL
In the root folder, create a file ‘app/config/mongodb.config.js’ as below content ->
module.exports = {
url: 'mongodb://localhost:27017/nodejs-demo'
}
Implement CRUD APIs
Routes ->
In the root folder, create a file ‘app/routes/customer.routes.js’ as below content ->
module.exports = function(app) {
const customers = require('../controllers/customer.controller.js');
// Create a new Customer
app.post('/api/customers', customers.create);
// Retrieve all Customer
app.get('/api/customers', customers.findAll);
// Retrieve a single Customer by Id
app.get('/api/customers/:customerId', customers.findOne);
// Update a Customer with Id
app.put('/api/customers', customers.update);
// Delete a Customer with Id
app.delete('/api/customers/:customerId', customers.delete);
}
Controllers
In root folder ‘nodejs-restapi’, create a controller folder ‘/app/controllers’. Then create a file ‘/app/controllers/customer.controller.js’ that contains methods for executing above URL requests ->
const Customer = require('../models/customer.model.js');
// POST a Customer
exports.create = (req, res) => {
// Create a Customer
const customer = new Customer(req.body);
// Save a Customer in the MongoDB
customer.save()
.then(data => {
res.json(data);
}).catch(err => {
res.status(500).json({
msg: err.message
});
});
};
// FETCH all Customers
exports.findAll = (req, res) => {
Customer.find()
.then(customers => {
res.json(customers);
}).catch(err => {
res.status(500).send({
msg: err.message
});
});
};
// FIND a Customer
exports.findOne = (req, res) => {
Customer.findById(req.params.customerId)
.then(customer => {
if(!customer) {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
res.json(customer);
}).catch(err => {
if(err.kind === 'ObjectId') {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
return res.status(500).json({
msg: "Error retrieving Customer with id " + req.params.customerId
});
});
};
// UPDATE a Customer
exports.update = (req, res) => {
// Find customer and update it
Customer.findByIdAndUpdate(req.body._id, req.body, {new: true})
.then(customer => {
if(!customer) {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
res.json(customer);
}).catch(err => {
if(err.kind === 'ObjectId') {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
return res.status(500).json({
msg: "Error updating customer with id " + req.params.customerId
});
});
};
// DELETE a Customer
exports.delete = (req, res) => {
Customer.findByIdAndRemove(req.params.customerId)
.then(customer => {
if(!customer) {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
res.json({msg: "Customer deleted successfully!"});
}).catch(err => {
if(err.kind === 'ObjectId' || err.name === 'NotFound') {
return res.status(404).json({
msg: "Customer not found with id " + req.params.customerId
});
}
return res.status(500).json({
msg: "Could not delete customer with id " + req.params.customerId
});
});
};
Server.js
‘server.js’ ->
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json())
// Configuring the database
const dbConfig = require('./app/config/mongodb.config.js');
const mongoose = require('mongoose');
const Customer = require('./app/models/customer.model.js');
mongoose.Promise = global.Promise;
// Connecting to the database
mongoose.connect(dbConfig.url)
.then(() => {
console.log("Successfully connected to MongoDB.");
Customer.remove({}, function(err) {
if(err){
console.log(err);
process.exit();
}
console.log('Customer collection removed');
// -> initial new data
initial();
});
}).catch(err => {
console.log('Could not connect to MongoDB.');
process.exit();
});
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
require('./app/routes/customer.routes.js')(app);
// Create a Server
const server = app.listen(8080, function () {
let host = server.address().address
let port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
function initial(){
let customers = [
{
firstname: "Joe",
lastname: "Thomas",
age: 36
},
{
firstname: "Peter",
lastname: "Smith",
age: 18
},
{
firstname: "Lauren",
lastname: "Taylor",
age: 31
},
{
firstname: "Mary",
lastname: "Taylor",
age: 24
},
{
firstname: "David",
lastname: "Moore",
age: 25
},
{
firstname: "Holly",
lastname: "Davies",
age: 27
},
{
firstname: "Michael",
lastname: "Brown",
age: 45
}
]
// Init data -> save to MongoDB
for (let i = 0; i < customers.length; i++) {
const customer = new Customer(customers[i]);
customer.save();
}
console.log(">>> Done - Initial Data!");
}
Angular 12 Client
Data Model
customer.ts
->
export class Customer {
_id: string;
firstname: string;
lastname: string;
age: number;
}
Configure AppModule
In the developed application, we use:
- Angular
Forms
-> for building form HttpClient
-> for httpGet/Post/Put/Delete
requestsAppRouting
-> for app routing
-> Modify AppModule app.module.ts
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing/app-routing.module';
import { AppComponent } from './app.component';
import { CustomerComponent } from './customer/customer.component';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
import { AddCustomerComponent } from './add-customer/add-customer.component';
@NgModule({
declarations: [
AppComponent,
CustomerComponent,
CustomerDetailsComponent,
AddCustomerComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
HttpClient DataService
Implement CustomerService customer.service.ts
with HttpClient
for Get/Post/Put/Delete
:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Customer } from './customer';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable({
providedIn: 'root'
})
export class CustomerService {
private customersUrl = 'http://localhost:8080/api/customers'; // URL to web api
constructor(
private http: HttpClient
) { }
getCustomers (): Observable {
return this.http.get(this.customersUrl)
}
getCustomer(id: string): Observable {
const url = `${this.customersUrl}/${id}`;
return this.http.get(url);
}
addCustomer (customer: Customer): Observable {
return this.http.post(this.customersUrl, customer, httpOptions);
}
deleteCustomer (customer: Customer | string): Observable {
const id = typeof customer === 'string' ? customer : customer._id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete(url, httpOptions);
}
updateCustomer (customer: Customer): Observable {
return this.http.put(this.customersUrl, customer, httpOptions);
}
}
Angular Router
Implement App-Routing module app-routing.module.ts
->
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomerComponent } from '../customer/customer.component';
import { AddCustomerComponent } from '../add-customer/add-customer.component';
import { CustomerDetailsComponent } from '../customer-details/customer-details.component';
const routes: Routes = [
{
path: 'customers',
component: CustomerComponent
},
{
path: 'customer/add',
component: AddCustomerComponent
},
{
path: 'customers/:id',
component: CustomerDetailsComponent
},
{
path: '',
redirectTo: 'customers',
pathMatch: 'full'
},
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
Router Outlet & Router Links
-> Questions:
- How to show Componenets with Angular Routers? -> Solution: using
Router Outlet
- How to handle the routing that comes from user’s actions? (like clicks on anchor tag) -> Solution: using
Router Link
-> We can achieve above functions by using Angular’s router-outlet
and routerLink
.
Modify the template file app.component.html
of AppComponenet component as below:
<div class="container">
<div class="row">
<div class="col-sm-4">
<h1>Angular HttpClient</h1>
<ul class="nav justify-content-center">
<li class="nav-item">
<a routerLink="customers" class="btn btn-light btn-sm" role="button" routerLinkActive="active">Retrieve</a>
</li>
<li class="nav-item">
<a routerLink="customer/add" class="btn btn-light btn-sm" role="button" routerLinkActive="active">Create</a>
</li>
</ul>
<hr>
<router-outlet></router-outlet>
</div>
</div>
</div>
Customer Component
Customer Component
->
– Implement CustomerComponent class customer.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
@Component({
selector: 'app-customer',
templateUrl: './customer.component.html',
styleUrls: ['./customer.component.css']
})
export class CustomerComponent implements OnInit {
customers: Customer[];
constructor(private customerService: CustomerService) {}
ngOnInit(): void {
this.getCustomers();
}
getCustomers() {
return this.customerService.getCustomers()
.subscribe(
customers => {
console.log(customers);
this.customers = customers
}
);
}
}
– Implement the template customer.component.html
:
<h3>All Customers</h3>
<div *ngFor="let cust of customers; let rowIndex=index">
<a [routerLink]="['/customers', cust._id]" style="color:black"><span class="badge badge-dark">{{rowIndex + 1}}</span> -> {{cust.firstname}}</a>
</div>
Customer Detail Component
Customer Detail
->
-> results:
– Implement CustomerDetails class customer-details.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
@Component({
selector: 'app-customer-details',
templateUrl: './customer-details.component.html',
styleUrls: ['./customer-details.component.css']
})
export class CustomerDetailsComponent implements OnInit {
customer = new Customer() ;
submitted = false;
message: string;
constructor(
private customerService: CustomerService,
private route: ActivatedRoute,
private location: Location
) {}
ngOnInit(): void {
const id = this.route.snapshot.paramMap.get('id');
this.customerService.getCustomer(id)
.subscribe(customer => this.customer = customer);
}
update(): void {
this.submitted = true;
this.customerService.updateCustomer(this.customer)
.subscribe(result => this.message = "Customer Updated Successfully!");
}
delete(): void {
this.submitted = true;
this.customerService.deleteCustomer(this.customer._id)
.subscribe(result => this.message = "Customer Deleted Successfully!");
}
goBack(): void {
this.location.back();
}
}
– Implement CustomerDetailsComponent template customer-details.component.html
:
<h5><span class="badge badge-light ">{{customer._id}}</span> -> {{customer.firstname}}</h5>
<div [hidden]="submitted">
<form #detailCustomerForm="ngForm">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" required
[(ngModel)]="customer.firstname" name="firstname" #firstname="ngModel">
<div [hidden]="firstname.valid || firstname.pristine"
class="alert alert-danger">
First Name is required
</div>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" required
[(ngModel)]="customer.lastname" name="lastname" #lastname="ngModel">
<div [hidden]="lastname.valid || lastname.pristine"
class="alert alert-danger">
Last Name is required
</div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" class="form-control" id="age" required
[(ngModel)]="customer.age" name="age" #age="ngModel">
<div [hidden]="age.valid || age.pristine"
class="alert alert-danger">
Age is required
</div>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-dark" (click)="goBack()">Back</button>
<button type="button" class="btn btn-dark" (click)="update()" [disabled]="!detailCustomerForm.form.valid">Update</button>
<button type="button" class="btn btn-dark" (click)="delete()">Delete</button>
</div>
</form>
</div>
<div [hidden]="!submitted">
<p>{{message}}</p>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-dark" (click)="goBack()">Back</button>
</div>
</div>
We can change the value of ng-valid
& ng-invalid
for more visual feedback,
-> Create ./assets/forms.css
file:
.ng-valid[required], .ng-valid.required {
border-left: 5px solid rgba(32, 77, 32, 0.623);
}
.ng-invalid:not(form) {
border-left: 5px solid rgb(148, 27, 27);
}
Add ./assets/forms.css
file to index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular6Httpclient</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="assets/forms.css">
</head>
<body>
<app-root></app-root>
</body>
</html>
Add-Customer Component
AddCustomer Component
->
-> result:
– Implement AddCustomerComponent class add-customer.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
import { Location } from '@angular/common';
@Component({
selector: 'app-add-customer',
templateUrl: './add-customer.component.html',
styleUrls: ['./add-customer.component.css']
})
export class AddCustomerComponent{
customer = new Customer();
submitted = false;
constructor(
private customerService: CustomerService,
private location: Location
) { }
newCustomer(): void {
this.submitted = false;
this.customer = new Customer();
}
addCustomer() {
this.submitted = true;
this.save();
}
goBack(): void {
this.location.back();
}
private save(): void {
console.log(this.customer);
this.customerService.addCustomer(this.customer)
.subscribe();
}
}
– Implement the template add-customer.component.html
:
<h3>Add Customer</h3>
<div [hidden]="submitted">
<form #addCustomerForm="ngForm">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" placeholder="Give Customer's FirstName"
required
[(ngModel)]="customer.firstname" name="firstname" #firstname="ngModel">
<div [hidden]="firstname.valid || firstname.pristine"
class="alert alert-danger">
First Name is required
</div>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" placeholder="Give Customer's LastName"
required
[(ngModel)]="customer.lastname" name="lastname" #lastname="ngModel">
<div [hidden]="lastname.valid || lastname.pristine"
class="alert alert-danger">
Last Name is required
</div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" class="form-control" id="age"
placeholder="Give Customer's Age"
required
[(ngModel)]="customer.age" name="age" #age="ngModel">
<div [hidden]="age.valid || age.pristine"
class="alert alert-danger">
Age is required
</div>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-dark" (click)="goBack()">Back</button>
<button type="button" class="btn btn-dark" (click)="addCustomer()" [disabled]="!addCustomerForm.form.valid">Add</button>
</div>
</form>
</div>
<div [hidden]="!submitted">
<p>Submitted Successfully! -> <span class="badge badge-light">{{customer.firstname}} {{customer.lastname}}</span></p>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-dark" (click)="goBack()">Back</button>
<button type="button" class="btn btn-dark" (click)="newCustomer(); addCustomerForm.reset()">Continue</button>
</div>
</div>
SourceCode
– Angular-6-Http-Client
– Node.js-RestAPIs-Mongoose-MongDB