Node.js/Express RestAPIs – Angular 6 HttpClient – Get/Post/Put/Delete requests + Bootstrap 4
Angular provides the HttpClient in @angular/common/http
for front-end applications communicate with backend services. In the tutorial, we show how to build an Angular application that uses the HttpClient
to make get/post/put/delete
requests with Observable
apis to Node.js RestAPIs.
Related posts:
– Angular 6 Service – with Observable Data for Asynchronous Operation
– Angular 6 Routing/Navigation – with Angular Router Service
– Angular 6 Template Driven Form – NgModel for Two-Way Data Binding
Technologies
- Angular 6
- RxJS 6
- Bootstrap 4
- Visual Studio Code – version 1.24.0
- Nodejs – v8.11.3
Overview
Goal
We create 2 projects:
– Angular Client Project:
– Node.js RestAPIs project:
UserCase
– Retrieve all customers from Node.js RestAPIs:
– 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:
Node.js 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 6 HttpClient
Use Angular HttpClient APIs to do Get/Post/Put/Delete
requests to Node.js RestAPIs:
// 1. GET All Customers from remote SpringBoot API @GetMapping(value="/api/customers")
getCustomers (): Observable {
return this.http.get(this.customersUrl)
}
// 2. GET a Customer from remote SpringBoot API @GetMapping(value="/api/customers/{id}")
getCustomer(id: number): Observable {
const url = `${this.customersUrl}/${id}`;
return this.http.get(url);
}
// 3. POST a Customer to remote SpringBoot API @PostMapping(value="/api/customers")
addCustomer (customer: Customer): Observable {
return this.http.post(this.customersUrl, customer, httpOptions);
}
// 4.DELETE a Customer from remote SpringBoot API @DeleteMapping(value="/api/customers/{id}")
deleteCustomer (customer: Customer | number): Observable {
const id = typeof customer === 'number' ? customer : customer.id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete(url, httpOptions);
}
// 5. PUT a Customer to remote SpringBoot API @PutMapping(value="/api/customers")
updateCustomer (customer: Customer): Observable {
return this.http.put(this.customersUrl, customer, httpOptions);
}
Practice
Node.js RestAPIs
Setup Node.js/Express project
Create application directory:
mkdir nodejs-restapi
cd nodejs-restapi
Use the npm init
to create ‘package.json’ file:
nodejs-restapi>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (nodejs-restapi)
version: (1.0.0)
description: Node.js RestAPI - GET/POST/PUT/DELETE
entry point: (index.js) server.js
test command:
git repository:
keywords: Node.js,RestAPI,
author: grokonez.com
license: (ISC)
About to write to C:\workspace\nodejs-restapi\package.json:
{
"name": "nodejs-restapi",
"version": "1.0.0",
"description": "Node.js RestAPI - GET/POST/PUT/DELETE",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Node.js",
"RestAPI"
],
"author": "grokonez.com",
"license": "ISC"
}
Is this OK? (yes) yes
-> Check content of ‘package.json’ file:
{
"name": "nodejs-restapi",
"version": "1.0.0",
"description": "Node.js RestAPI - GET/POST/PUT/DELETE",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Node.js",
"RestAPI"
],
"author": "grokonez.com",
"license": "ISC"
}
We need express
, body-parse
and cors
modules.
– 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.
– Body-parser: parses/extract the body of an incoming HTTP request.
- Cors: 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.
-> Installing them as commandline npm install express body-parser cors--save
:
-> see ‘package.json’ file:
{
"name": "nodejs-restapi",
"version": "1.0.0",
"description": "Node.js RestAPI - GET/POST/PUT/DELETE",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Node.js",
"RestAPI"
],
"author": "grokonez.com",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"cors": "^2.8.4",
"express": "^4.16.3"
}
}
Implement Node.js/Express RestAPIs
- In root folder 'nodejs-restapi', create a ‘server.js’ file:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json())
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
let customerRouter = require('./app/routes/customer.routes.js');
app.use('/', customerRouter);
// Create a Server
let 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)
})
Implement Express Application ->
In root folder ‘nodejs-restapi’, create a router folder ‘/app/routes’. Then create a file ‘/app/routes/customer.routes.js’ for routing requests ‘GET/POST/DELETE/UPDATE’
:
let express = require('express');
let router = express.Router();
let customers = require('../controllers/customer.controller.js');
// Create a new Customer
router.post('/api/customers', customers.create);
// Retrieve all Customer
router.get('/api/customers', customers.findAll);
// Retrieve a single Customer by Id
router.get('/api/customers/:id', customers.findOne);
// Update a Customer with Id
router.put('/api/customers', customers.update);
// Delete a Customer with Id
router.delete('/api/customers/:id', customers.delete);
module.exports = router;
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:
let customers = {
customer1: {
id: 1,
firstname: "Joe",
lastname: "Thomas",
age: 36
},
customer2: {
id: 2,
firstname: "Peter",
lastname: "Smith",
age: 18
},
customer3: {
id: 3,
firstname: "Lauren",
lastname: "Taylor",
age: 31
},
customer4: {
id: 4,
firstname: "Mary",
lastname: "Taylor",
age: 24
},
customer5: {
id: 5,
firstname: "David",
lastname: "Moore",
age: 25
},
customer6: {
id: 6,
firstname: "Holly",
lastname: "Davies",
age: 27
},
customer7: {
id: 7,
firstname: "Michael",
lastname: "Brown",
age: 45
}
}
exports.create = function(req, res) {
// find the largest ID
let arr = Object.keys( customers ).map(function ( key ) { return customers[key].id; });
let newId = Math.max.apply( null, arr ) + 1;
let newCustomer = req.body;
newCustomer.id = newId;
customers["customer" + newId] = newCustomer;
res.json(newCustomer);
};
exports.findAll = function(req, res) {
res.json(Object.values(customers));
};
exports.findOne = function(req, res) {
let customer = customers["customer" + req.params.id];
res.json(customer);
};
exports.update = function(req, res) {
let updatedCustomer = req.body;
customers["customer" + updatedCustomer.id] = updatedCustomer;
res.json({msg: "Customer Updated Successfully!"});
};
exports.delete = function(req, res) {
delete customers["customer" + req.params.id];
res.json({msg: "Customer Deleted Successfully!"});
};
Angular 6 Client
– Create Angular project:
ng new angular6-httpclient
– Generate:
- Customer Class
- Customer Service
- Customer Components
- App Routing Module
-> Details:
ng generate class Customer
ng generate service Customer
ng generate component Customer
ng generate component CustomerDetails
ng generate component AddCustomer
ng generate module AppRouting
– Install Bootstrap 4:
npm install bootstrap jquery --save
-> Configure installed Bootstrap & JQuery in angular.json
file:
...
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
...
Data Model
Implement Customer model customer.ts
:
export class Customer {
id: number;
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: number): 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 | number): Observable {
const id = typeof customer === 'number' ? 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
:
<h5>All Customers</h5>
<div *ngFor="let cust of customers">
<a [routerLink]="['/customers', cust.id]" style="color:black"><span class="badge badge-dark">{{cust.id}}</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(() => this.message = "Customer Updated Successfully!");
}
delete(): void {
this.submitted = true;
this.customerService.deleteCustomer(this.customer.id)
.subscribe(()=> this.message = "Customer Deleted Successfully!");
}
goBack(): void {
this.location.back();
}
}
– Implement CustomerDetailsComponent template customer-details.component.html
:
<h4><span class="badge badge-light ">{{customer.id}}</span> -> {{customer.firstname}}</h4>
<div [hidden]="submitted">
<form (ngSubmit)="update()" #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="submit" 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 {
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
Last updated on March 28, 2021.
Hello, and how can i publish (deploy) this project in a remote server (angular + nodejs files)?
Got the same question. Maybe on Heroku? Or in firebase hosting. Don’t know really. Any ideas? Someone?
build the angular project into a folder that you app.get into your node.js server, so that when you run the node server, the built angular app is opened, then just upload that to anywhere heroku etc
gHkNYGBz