Spring Boot 2.1 + Angular 8 + MySQL example | Angular HTTP Client + RestAPIs + Spring JPA CRUD + MySQL tutorial

spring-boot-angular-8-example-crud-mysql-feature-image

In this tutorial, we show you Angular 8 Http Client & Spring Boot Server example that uses Spring JPA to do CRUD with MySQL and Angular 8 as a front-end technology to make HTTP request and receive response.

Related Post:
How to use Spring JPA MySQL | Spring Boot
How to install Angular 8 locally

Technologies

– Java 8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.4.RELEASE
– Spring Boot: 2.1.3.RELEASE
– Angular 8
– RxJS 6

Video

Spring Boot + Angular 8 + MySQL example Overview

Spring Boot RestApis Server

spring-boot-angular-8-example-crud-mysql-spring-boot-server-architecture

Our Spring Boot Server can work with MySQL Database and provides APIs:

  • GET /customers/: get all customers
  • GET /customers/[id]: get a customer by id
  • GET /customers/age/[age]: find all customers by age
  • POST /customers/: save a customer
  • PUT /customers/[id]: update a customer by id
  • DELETE /customers/[id]: delete a customer by id
  • DELETE /customers/: delete all customers

Angular 8 Client

The image below shows overview about Angular Components that we will create:

spring-boot-angular-8-example-crud-mysql-angular-8-client-components

Implement Spring Boot Server

Project Structure

spring-boot-angular-8-example-crud-mysql-spring-boot-server-project-structure

Customer class corresponds to entity and table customer.
CustomerRepository is an interface extends CrudRepository, will be autowired in CustomerController for implementing repository methods and custom finder methods.
CustomerController is a REST Controller which has request mapping methods for RESTful requests such as: getAllCustomers, postCustomer, deleteCustomer, deleteAllCustomers, findByAge, updateCustomer.
– Configuration for Spring Datasource and Spring JPA properties in application.properties
Dependencies for Spring Boot and MySQL in pom.xml

We will keep the tutorial as simple as possible, so for post with Spring JPA details, you can visit:
How to use Spring JPA MySQL | Spring Boot

Dependencies

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

Data Model

model/Customer.java


package com.grokonez.spring.restapi.mysql.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "customer")
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  @Column(name = "name")
  private String name;

  @Column(name = "age")
  private int age;

  @Column(name = "active")
  private boolean active;

  public Customer() {
  }

  public Customer(String name, int age) {
    this.name = name;
    this.age = age;
    this.active = false;
  }

  public long getId() {
    return id;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public int getAge() {
    return this.age;
  }

  public boolean isActive() {
    return active;
  }

  public void setActive(boolean active) {
    this.active = active;
  }

  @Override
  public String toString() {
    return "Customer [id=" + id + ", name=" + name + ", age=" + age + ", active=" + active + "]";
  }
}

JPA Repository

repo/CustomerRepository.java


package com.grokonez.spring.restapi.mysql.repo;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

import com.grokonez.spring.restapi.mysql.model.Customer;

public interface CustomerRepository extends CrudRepository {
  List findByAge(int age);
}

REST Controller

controller/CustomerController.java

package com.grokonez.spring.restapi.mysql.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.grokonez.spring.restapi.mysql.model.Customer;
import com.grokonez.spring.restapi.mysql.repo.CustomerRepository;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api")
public class CustomerController {

  @Autowired
  CustomerRepository repository;

  @GetMapping("/customers")
  public ResponseEntity> getAllCustomers() {
    List customers = new ArrayList<>();
    try {
      repository.findAll().forEach(customers::add);
      
      if (customers.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }
      return new ResponseEntity<>(customers, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
  
  @GetMapping("/customers/{id}")
  public ResponseEntity getCustomerById(@PathVariable("id") long id) {
    Optional customerData = repository.findById(id);

    if (customerData.isPresent()) {
      return new ResponseEntity<>(customerData.get(), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  @PostMapping(value = "/customers")
  public ResponseEntity postCustomer(@RequestBody Customer customer) {
    try {
      Customer _customer = repository.save(new Customer(customer.getName(), customer.getAge()));
      return new ResponseEntity<>(_customer, HttpStatus.CREATED);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.EXPECTATION_FAILED);
    }
  }

  @DeleteMapping("/customers/{id}")
  public ResponseEntity deleteCustomer(@PathVariable("id") long id) {
    try {
      repository.deleteById(id);
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);
    }
  }

  @DeleteMapping("/customers")
  public ResponseEntity deleteAllCustomers() {
    try {
      repository.deleteAll();
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);
    }

  }

  @GetMapping(value = "customers/age/{age}")
  public ResponseEntity> findByAge(@PathVariable int age) {
    try {
      List customers = repository.findByAge(age);

      if (customers.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
      }
      return new ResponseEntity<>(customers, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);
    }
  }

  @PutMapping("/customers/{id}")
  public ResponseEntity updateCustomer(@PathVariable("id") long id, @RequestBody Customer customer) {
    Optional customerData = repository.findById(id);

    if (customerData.isPresent()) {
      Customer _customer = customerData.get();
      _customer.setName(customer.getName());
      _customer.setAge(customer.getAge());
      _customer.setActive(customer.isActive());
      return new ResponseEntity<>(repository.save(_customer), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }
}

Configuration for Spring Datasource & JPA properties

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.generate-ddl=true

Implement Angular 8 HTTP Client

Project Structure

spring-boot-angular-8-example-crud-mysql-angular-8-client-project-structure

We have:
– 4 components: customers-listcustomer-detailscreate-customersearch-customer.
– 3 modules: FormsModuleHttpClientModuleAppRoutingModule.
– customer.ts: class Customer (id, firstName, lastName)
– customer.service.ts: Service for Http Client methods
– app-routing.module.ts: Routing configuration

Create Service & Components

On Project folder, run commands below:
– ng g s customer
– ng g c create-customer
– ng g c customer-details
– ng g c customers-list
– ng g c search-customers

App Module

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.module';
import { AppComponent } from './app.component';
import { CreateCustomerComponent } from './create-customer/create-customer.component';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
import { CustomersListComponent } from './customers-list/customers-list.component';
import { SearchCustomersComponent } from './search-customers/search-customers.component';

@NgModule({
  declarations: [
    AppComponent,
    CreateCustomerComponent,
    CustomerDetailsComponent,
    CustomersListComponent,
    SearchCustomersComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add Router

app-routing.module.ts

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

import { CustomersListComponent } from './customers-list/customers-list.component';
import { CreateCustomerComponent } from './create-customer/create-customer.component';
import { SearchCustomersComponent } from './search-customers/search-customers.component';

const routes: Routes = [
  { path: '', redirectTo: 'customer', pathMatch: 'full' },
  { path: 'customer', component: CustomersListComponent },
  { path: 'add', component: CreateCustomerComponent },
  { path: 'findbyage', component: SearchCustomersComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

AppComponent HTML for routing:
app.component.html

<div style="padding: 20px;">
  <h1 style="color: blue">grokonez.com</h1>
  <h3>{{title}}</h3>
  <nav>
    <a routerLink="customer" class="btn btn-primary active" role="button" routerLinkActive="active">Customers</a>
    <a routerLink="add" class="btn btn-primary active" role="button" routerLinkActive="active">Add</a>
    <a routerLink="findbyage" class="btn btn-primary active" role="button" routerLinkActive="active">Search</a>
  </nav>
  <router-outlet></router-outlet>
</div>

Data Model

Create new file named customer.ts:

export class Customer {
    id: number;
    name: string;
    age: number;
    active: boolean;
}

Data Service

customer.service.ts


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CustomerService {

  private baseUrl = 'http://localhost:8080/api/customers';

  constructor(private http: HttpClient) { }

  getCustomer(id: number): Observable {
    return this.http.get(`${this.baseUrl}/${id}`);
  }

  createCustomer(customer: any): Observable {
    return this.http.post(this.baseUrl, customer);
  }

  updateCustomer(id: number, value: any): Observable {
    return this.http.put(`${this.baseUrl}/${id}`, value);
  }

  deleteCustomer(id: number): Observable {
    return this.http.delete(`${this.baseUrl}/${id}`);
  }

  getCustomersList(): Observable {
    return this.http.get(this.baseUrl);
  }

  getCustomersByAge(age: number): Observable {
    return this.http.get(`${this.baseUrl}/age/${age}`);
  }

  deleteAll(): Observable {
    return this.http.delete(this.baseUrl);
  }
}

Components

List of Customers

customers-list/customers-list.component.ts


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';

@Component({
  selector: 'app-customers-list',
  templateUrl: './customers-list.component.html',
  styleUrls: ['./customers-list.component.css']
})
export class CustomersListComponent implements OnInit {

  customers: Observable;

  constructor(private customerService: CustomerService) { }

  ngOnInit() {
    this.reloadData();
  }

  deleteCustomers() {
    this.customerService.deleteAll()
      .subscribe(
        data => {
          console.log(data);
          this.reloadData();
        },
        error => console.log('ERROR: ' + error));
  }

  reloadData() {
    this.customers = this.customerService.getCustomersList();
  }
}

customers-list/customers-list.component.html

<h3>Customers</h3>
<div *ngFor="let customer of customers | async" style="width: 300px;">
  <app-customer-details [customer]='customer'></app-customer-details>
</div>

<div>
  <button type="button" class="button btn-danger" (click)='deleteCustomers()'>Delete All</button>
</div>

Customer Details

customer-details/customer-details.component.ts

import { Component, OnInit, Input } from '@angular/core';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';
import { CustomersListComponent } from '../customers-list/customers-list.component';

@Component({
  selector: 'app-customer-details',
  templateUrl: './customer-details.component.html',
  styleUrls: ['./customer-details.component.css']
})
export class CustomerDetailsComponent implements OnInit {

  @Input() customer: Customer;

  constructor(private customerService: CustomerService, private listComponent: CustomersListComponent) { }

  ngOnInit() {
  }

  updateActive(isActive: boolean) {
    this.customerService.updateCustomer(this.customer.id,
      { name: this.customer.name, age: this.customer.age, active: isActive })
      .subscribe(
        data => {
          console.log(data);
          this.customer = data as Customer;
        },
        error => console.log(error));
  }

  deleteCustomer() {
    this.customerService.deleteCustomer(this.customer.id)
      .subscribe(
        data => {
          console.log(data);
          this.listComponent.reloadData();
        },
        error => console.log(error));
  }
}

customer-details/customer-details.component.html

<div *ngIf="customer">
  <div>
    <label>Name: </label> {{customer.name}}
  </div>
  <div>
    <label>Age: </label> {{customer.age}}
  </div>
  <div>
    <label>Active: </label> {{customer.active}}
  </div>
 
  <span class="button is-small btn-primary" *ngIf='customer.active' (click)='updateActive(false)'>Inactive</span>
 
  <span class="button is-small btn-primary" *ngIf='!customer.active' (click)='updateActive(true)'>Active</span>
 
  <span class="button is-small btn-danger" (click)='deleteCustomer()'>Delete</span>
 
  <hr />
</div>

Create Customer

create-customer/create-customer.component.ts

import { Component, OnInit } from '@angular/core';

import { Customer } from '../customer';
import { CustomerService } from '../customer.service';

@Component({
  selector: 'app-create-customer',
  templateUrl: './create-customer.component.html',
  styleUrls: ['./create-customer.component.css']
})
export class CreateCustomerComponent implements OnInit {

  customer: Customer = new Customer();
  submitted = false;

  constructor(private customerService: CustomerService) { }

  ngOnInit() {
  }

  newCustomer(): void {
    this.submitted = false;
    this.customer = new Customer();
  }

  save() {
    this.customerService.createCustomer(this.customer)
      .subscribe(
        data => {
          console.log(data);
          this.submitted = true;
        },
        error => console.log(error));
    this.customer = new Customer();
  }

  onSubmit() {
    this.save();
  }
}

create-customer/create-customer.component.html

<h3>Create Customer</h3>
<div [hidden]="submitted" style="width: 300px;">
  <form (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" id="name" required [(ngModel)]="customer.name" name="name">
    </div>

    <div class="form-group">
      <label for="age">Age</label>
      <input type="text" class="form-control" id="age" required [(ngModel)]="customer.age" name="age">
    </div>

    <button type="submit" class="btn btn-success">Submit</button>
  </form>
</div>

<div [hidden]="!submitted">
  <h4>You submitted successfully!</h4>
  <button class="btn btn-success" (click)="newCustomer()">Add</button>
</div>

Search Customers

search-customers/search-customers.component.ts

import { Component, OnInit } from '@angular/core';

import { Customer } from '../customer';
import { CustomerService } from '../customer.service';

@Component({
  selector: 'app-search-customers',
  templateUrl: './search-customers.component.html',
  styleUrls: ['./search-customers.component.css']
})
export class SearchCustomersComponent implements OnInit {

  age: number;
  customers: Customer[];

  constructor(private dataService: CustomerService) { }

  ngOnInit() {
    this.age = 0;
  }

  private searchCustomers() {
    this.customers = [];
    this.dataService.getCustomersByAge(this.age)
      .subscribe(customers => this.customers = customers);
  }

  onSubmit() {
    this.searchCustomers();
  }
}

search-customers/search-customers.component.html

<h3>Find By Age</h3>
<div style="width: 300px;">
  <form (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="lastname">Age</label>
      <input type="text" class="form-control" id="age" required [(ngModel)]="age" name="age">
    </div>

    <div class="btn-group">
      <button type="submit" class="btn btn-success">Submit</button>
    </div>
  </form>
</div>
<ul>
  <li *ngFor="let customer of customers">
    <h4>{{customer.id}} - {{customer.name}} {{customer.age}}</h4>
  </li>
</ul>

Run & Check Results

– Build and Run Spring Boot project with commandlines: mvn clean install and mvn spring-boot:run.
– Run the Angular App with command: ng serve.

– Open browser for url http://localhost:4200/

Add Customer:

spring-boot-angular-8-example-crud-mysql-add-customer

Show Customers:

spring-boot-angular-8-example-crud-mysql-show-customers

Click on Active button to update Customer status:

spring-boot-angular-8-example-crud-mysql-update-customers

Search Customers by Age:

spring-boot-angular-8-example-crud-mysql-search-customers

Delete a Customer:

spring-boot-angular-8-example-crud-mysql-delete-customer

Delete All Customers:

spring-boot-angular-8-example-crud-mysql-delete-all-customers

Source Code

server-SpringBootRestApiMySQL
client-Angular8SpringBoot



By grokonez | February 25, 2019.

Last updated on March 18, 2021.



Related Posts


7 thoughts on “Spring Boot 2.1 + Angular 8 + MySQL example | Angular HTTP Client + RestAPIs + Spring JPA CRUD + MySQL tutorial”

  1. Hi, I’m new to angular and spring. I have a background in jsp and java. When creating the client and server is it better to split the two into to project or can you put them both under one?

    I had started one and made them both under the same project then I found this example to for how to hook into database and noticed you have them separate.

    BTW based on the example here I will be definitely be visiting your site more often.

  2. Hello,in my application.properties all my spring.datasource… are unused property and i have a java.sql.SQLSyntaxErrorException: Unknown database ‘testdb’
    it’s a problem with my sql connection? Thanks.Julien

    1. You should create a new database named “testdb”, and create a table named “customer” where you can find relevant columns in file “Customer.java”

  3. Thank you for the great tutorial. I have a question, If I use abstract class for model how can I handle controller and other stuff?

Got Something To Say:

Your email address will not be published. Required fields are marked *

*