Spring Boot + Angular 6 example | Spring Data JPA + REST + PostgreSQL CRUD example

spring-boot-angular-6-example-spring-data-rest-postgresql-example-feature-image-new

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

Related Posts:
How to use Spring JPA with PostgreSQL | Spring Boot
Spring JPA + PostgreSQL + AngularJS example | Spring Boot

I. Technologies

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

II. Overview

angular-http-service-architecture

Demo

1. Spring Boot Server

spring-boot-angular-6-spring-rest-api-data-postgresql-spring-server-architecture

2. Angular 6 Client

spring-boot-angular-6-spring-rest-api-data-postgresql-angular-client-architecture

III. Practice

1. Project Structure

1.1 Spring Boot Server

spring-boot-angular-6-spring-rest-api-data-postgresql-spring-server-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 PostgreSQL in pom.xml

1.2 Angular 6 Client

spring-boot-angular-6-spring-rest-api-data-postgresql-angular-client-structure

In this example, we focus on:
– 4 components: customers-list, customer-details, create-customer, search-customer.
– 3 modules: FormsModule, HttpClientModule, AppRoutingModule.
customer.ts: class Customer (id, firstName, lastName)
customer.service.ts: Service for Http Client methods

2. How to do

2.1 Spring Boot Server

2.1.1 Dependency

<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>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

2.1.2 Customer – Data Model

model/Customer.java


package com.javasampleapproach.springrest.postgresql.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 + "]";
	}
}

2.1.3 JPA Repository

repo/CustomerRepository.java


package com.javasampleapproach.springrest.postgresql.repo;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

import com.javasampleapproach.springrest.postgresql.model.Customer;

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

2.1.4 REST Controller

controller/CustomerController.java


package com.javasampleapproach.springrest.postgresql.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.javasampleapproach.springrest.postgresql.model.Customer;
import com.javasampleapproach.springrest.postgresql.repo.CustomerRepository;

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

	@Autowired
	CustomerRepository repository;

	@GetMapping("/customers")
	public List getAllCustomers() {
		System.out.println("Get all Customers...");

		List customers = new ArrayList<>();
		repository.findAll().forEach(customers::add);

		return customers;
	}

	@PostMapping(value = "/customers/create")
	public Customer postCustomer(@RequestBody Customer customer) {

		Customer _customer = repository.save(new Customer(customer.getName(), customer.getAge()));
		return _customer;
	}

	@DeleteMapping("/customers/{id}")
	public ResponseEntity deleteCustomer(@PathVariable("id") long id) {
		System.out.println("Delete Customer with ID = " + id + "...");

		repository.deleteById(id);

		return new ResponseEntity<>("Customer has been deleted!", HttpStatus.OK);
	}

	@DeleteMapping("/customers/delete")
	public ResponseEntity deleteAllCustomers() {
		System.out.println("Delete All Customers...");

		repository.deleteAll();

		return new ResponseEntity<>("All customers have been deleted!", HttpStatus.OK);
	}

	@GetMapping(value = "customers/age/{age}")
	public List findByAge(@PathVariable int age) {

		List customers = repository.findByAge(age);
		return customers;
	}

	@PutMapping("/customers/{id}")
	public ResponseEntity updateCustomer(@PathVariable("id") long id, @RequestBody Customer customer) {
		System.out.println("Update Customer with ID = " + id + "...");

		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);
		}
	}
}

2.1.5 Configuration for Spring Datasource & JPA properties

application.properties


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.generate-ddl=true

2.2 Angular 6 Client

2.2.0 Create Service & Components

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
On each Component selector, delete app- prefix, then change tslint.json rules"component-selector" to false.

2.2.1 Model

customer.ts


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

2.2.2 CustomerService

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<Object> {
    return this.http.get(`${this.baseUrl}/${id}`);
  }

  createCustomer(customer: Object): Observable<Object> {
    return this.http.post(`${this.baseUrl}` + `/create`, customer);
  }

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

  deleteCustomer(id: number): Observable<any> {
    return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
  }

  getCustomersList(): Observable<any> {
    return this.http.get(`${this.baseUrl}`);
  }

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

  deleteAll(): Observable<any> {
    return this.http.delete(`${this.baseUrl}` + `/delete`, { responseType: 'text' });
  }
}

2.2.3 Components

– CustomerDetailsComponent:
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: '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>

– CustomersListComponent:
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: 'customers-list',
  templateUrl: './customers-list.component.html',
  styleUrls: ['./customers-list.component.css']
})
export class CustomersListComponent implements OnInit {

  customers: Observable<Customer[]>;

  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

<h1>Customers</h1>

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

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

– CreateCustomerComponent:
create-customer/create-customer.component.ts


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

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

@Component({
  selector: '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), error => console.log(error));
    this.customer = new Customer();
  }

  onSubmit() {
    this.submitted = true;
    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>

– SearchCustomersComponent:
search-customers/search-customers.component.ts


import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';

@Component({
  selector: '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.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>

2.2.4 AppRoutingModule

app-routing.module.ts


import { NgModule } from '@angular/core';
import { RouterModule, Routes } 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 { }

And AppComponent HTML for routing:
app.component.html

<div style="padding: 20px;">
  <h1 style="color: blue">{{title}}</h1>
  <h3>{{description}}</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>

2.2.5 AppModule

app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

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';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';

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

3. Run & Check Result

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-6-spring-rest-api-data-postgresql-add-customer

Show Customers:

spring-boot-angular-6-spring-rest-api-data-postgresql-show-customers

Click on Active button to update Customer status:

spring-boot-angular-6-spring-rest-api-data-postgresql-update-customer

Search Customers by Age:

spring-boot-angular-6-spring-rest-api-data-postgresql-search-customers

Delete a Customer:

spring-boot-angular-6-spring-rest-api-data-postgresql-delete-customer

Delete All Customers:

spring-boot-angular-6-spring-rest-api-data-postgresql-delete-all-customers

IV. Source Code

Angular6SpringBoot-Client
SpringRESTPostgreSQL-Server

73 thoughts on “Spring Boot + Angular 6 example | Spring Data JPA + REST + PostgreSQL CRUD example”

  1. I’m beginner and I’m trying to follow your tutorial. One thing puzzled me: why in CustomerController inside postCustomer method you saved Customer like this:

    repository.save(new Customer(customer.getName(), customer.getAge()));
    

    instead of just simply:

    repository.save(customer)
    

    (object from method parameter)?

    1. Hi Pawel,

      Please look at the constructor method of Customer class:

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

      You can see that we specify the value of active field, and not mention the value of id field (that will be generated automatically by JPA), if you use the customer from input param of the function:

      public Customer postCustomer(@RequestBody Customer customer) {...}
      

      customer.active field could be true by the input 🙂

      Regards,
      ozenero.

  2. I am getting an error while hitting add details

    Access to XMLHttpRequest at ‘http://localhost:8080/api/customers/create’ from origin ‘http://localhost:4200’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

    1. Please add
      import org.springframework.web.bind.annotation.CrossOrigin;

      @CrossOrigin(origins=”http://localhost:4200″)

  3. Search by age is good. But if i want to search age by comma separated.

    Example

    search 27,23,34.

    I want to display all above age group. Could you please let us know

  4. Link exchange is nothing else however it is simply placing the other person’s blog link on your page at suitable place and other person will also do same in support of you.

  5. I’ve been browsing online more than 3 hours today, yet
    I never found any interesting article like yours. It is pretty worth enough for
    me. In my view, if all site owners and bloggers made good content as you did, the web will be much more useful
    than ever before.

  6. Having read this I thought it was extremely informative.
    I appreciate you spending some time and energy to put this article
    together. I once again find myself personally spending a significant amount of time both reading and leaving comments.
    But so what, it was still worth it!

  7. Very good site you have here but I was curious if you knew of any discussion boards that cover the same topics discussed here?
    I’d really love to be a part of group where I can get comments from other knowledgeable people that share the same interest.
    If you have any recommendations, please let me know. Bless you!

  8. Good day! I could have sworn I’ve visited this web site before but
    after browsing through many of the posts I realized it’s new to me.
    Anyhow, I’m certainly pleased I found it and I’ll be book-marking it and checking back often!

  9. I’d like to thank you for the efforts you have put in writing this site.
    I’m hoping to see the same high-grade blog posts by you later on as well.
    In truth, your creative writing abilities has inspired me to get my own, personal
    site now 😉

  10. That is really fascinating, You are a very professional blogger.
    I’ve joined your feed and look forward to seeking more of your
    great post. Also, I have shared your web site in my social
    networks

  11. Do you have a spam issue on this website; I also am a blogger,
    and I was wanting to know your situation; many of us have developed
    some nice procedures and we are looking to
    swap methods with others, please shoot me an e-mail if
    interested.

  12. Ⲟh my goodness! Impreѕsive article dude! Many thanks,
    Hоwever I am encountering trօubles with your RSS. I don’t understand why
    I can’t join it. Is there anyone else gettіng sіmilaг ᎡSS
    problemѕ? Anyone who knows the answer will you kіndly respond?
    Thanx!!

  13. Do you mind if I quote a few of your posts as long as I provide credit and sources
    back to your website? My blog site is in the exact same niche
    as yours and my users would truly benefit from some
    of the information you provide here. Please let
    me know if this ok with you. Thanks!

  14. Hello there! I know this is kinda off topic however , I’d figured I’d
    ask. Would you be interested in exchanging links or maybe guest writing a blog post or vice-versa?
    My site covers a lot of the same topics as yours and I feel we could greatly benefit from each other.
    If you are interested feel free to shoot me an e-mail.
    I look forward to hearing from you! Terrific blog by the way!

  15. You’re so cool! I don’t believe I’ve truly read through
    something like this before. So nice to discover another person with some unique thoughts on this subject matter.
    Really.. thanks for starting this up. This website is one thing that
    is needed on the web, someone with a bit of originality!

  16. An impressive share! I’ve just forwarded this onto a coworker who had been conducting a little research on this.
    And he actually ordered me lunch simply because
    I found it for him… lol. So allow me to reword this….
    Thank YOU for the meal!! But yeah, thanx for spending the time
    to discuss this matter here on your internet site.

  17. We are a group of volunteers and starting a new scheme in our community.
    Your site provided us with valuable info to work on. You have done an impressive job and our whole community will be grateful to you.

  18. First off I would like to say awesome blog! I had a quick question in which I’d like to ask if you do not mind.

    I was curious to know how you center yourself and clear your mind before writing.
    I’ve had a tough time clearing my thoughts in getting my
    ideas out there. I do enjoy writing however it just
    seems like the first 10 to 15 minutes are generally lost just trying to figure out how to begin. Any ideas or
    tips? Appreciate it!

  19. Do you have a spam issue on this site; I also am a blogger, and
    I was wanting to know your situation; we have developed some nice procedures and we are looking to trade solutions with other folks, be sure to shoot
    me an email if interested.

  20. Hi there! I could have sworn I’ve been to this site before but after browsing through some
    of the post I realized it’s new to me. Anyways, I’m definitely happy I found it and I’ll be book-marking
    and checking back frequently!

  21. It’s the best time to make some plans for the future and it’s time to
    be happy. I’ve read this post and if I could I want to
    suggest you few interesting things or suggestions. Maybe you can write next articles referring to
    this article. I want to read even more things about it!

  22. Its such as you learn my mind! You seem to know so much approximately this, such as you
    wrote the e-book in it or something. I feel that you just could do with a few
    % to power the message house a little bit, but instead of that, this is fantastic blog.

    An excellent read. I will definitely be
    back.

  23. Pretty section of content. I just stumbled upon your web site and in accession capital
    to assert that I acquire actually enjoyed account
    your blog posts. Any way I will be subscribing to
    your feeds and even I achievement you access consistently fast.

  24. Hey there just wanted to give you a quick heads up.
    The text in your article seem to be running off
    the screen in Chrome. I’m not sure if this is a format issue or something to do with web browser
    compatibility but I figured I’d post to let you know. The design and style
    look great though! Hope you get the problem resolved soon. Many thanks

Leave a Reply

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