Spring Boot + Angular CRUD Example

By Arvind Rai, November 28, 2023
On this page, I will create an application using Spring Boot and Angular. I will create REST APIs using Spring Boot that fetches data from MySQL using Hibernate. I will also create an Angular application that will act as client for REST API. Here I will perform CRUD operation i.e. create, read, update and delete operation.
If our client application is running on different domain from web service domain, then the Spring Boot web service controller needs to configure client domain URL using @CrossOrigin annotation that handles Cross-Origin-Resource-Sharing (CORS).Using REST web service response status codes, my Angular application will display messages for success and failure of CRUD operations.
Here on this page, I will perform CRUD operation on article using Angular 16 and Spring Boot 3.

1. List of REST APIs to Create

In my application, following REST APIs will be created.
1. CREATE :
HTTP Method: POST, URL: /app/article
Angular API: HttpClient.post()
HTTP Response Status Code: 201 CREATED and 409 CONFLICT

2. READ :
HTTP Method: GET, URL: /app/article?id={id} (Fetches article by id)
HTTP Method: GET, URL: /app/allarticles (Fetches all articles)
Angular API: HttpClient.get()
HTTP Response Status Code: 200 OK

3. UPDATE :
HTTP Method: PUT, URL: /app/article
Angular API: HttpClient.put()
HTTP Response Status Code: 200 OK

4. DELETE :
HTTP Method: DELETE, URL: /app/article?id={id}
Angular API: HttpClient.delete()
HTTP Response Status Code: 204 NO CONTENT

Find the print screen of the front-end of our application.
Spring Boot + Angular CRUD Example

2. Create REST API using Spring Boot

In my application REST API will be exposed by Spring Boot that will fetch data from MySQL database using Hibernate. We will create a table for article and perform CRUD operation on this table. Find the application code step-by-step.

2.1 Creating Table in MySQL

Find SQL query to create a table for article.
Table: articles
CREATE TABLE `articles` (
  `article_id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL,
  `category` varchar(100) NOT NULL,
  PRIMARY KEY (`article_id`)
)

2.2 Maven Dependencies

Find the Maven dependencies for Spring Boot and Hibernate.
pom.xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.3</version>
    <relativePath/>
</parent>
<dependencies>
  <dependency>
	 <groupId>org.springframework.boot</groupId>
	 <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
	 <groupId>org.springframework.boot</groupId>
	 <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
	 <groupId>mysql</groupId>
	 <artifactId>mysql-connector-java</artifactId>
	 <version>8.0.32</version>
  </dependency>
</dependencies> 

2.3 Property File

Find the Spring Boot property file used in my application.
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/cp
spring.datasource.username=root
spring.datasource.password=Mysql@1234

spring.datasource.tomcat.max-wait=20000
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-idle=20
spring.datasource.tomcat.min-idle=15

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.id.new_generator_mappings = false
spring.jpa.properties.hibernate.format_sql = true 
The property file contains configurations for MySQL connections, Tomcat connection pool and Hibernate.

2.4 Create Controller

Find the controller class.
ArticleController.java
@RestController
@RequestMapping("app")
@CrossOrigin(origins = { "http://localhost:4200" })
public class ArticleController {
	@Autowired
	private IArticleService articleService;

	@GetMapping("article")
	public ResponseEntity<Article> getArticleById(@RequestParam("id") String id) {
		System.out.println("id: " + id);
		Article article = articleService.getArticleById(Integer.parseInt(id));
		return new ResponseEntity<Article>(article, HttpStatus.OK);
	}

	@GetMapping("allarticles")
	public ResponseEntity<List<Article>> getAllArticles() {
		List<Article> list = articleService.getAllArticles();
		return new ResponseEntity<List<Article>>(list, HttpStatus.OK);
	}

	@PostMapping("article")
	public ResponseEntity<Void> createArticle(@RequestBody Article article, UriComponentsBuilder builder) {
		boolean flag = articleService.createArticle(article);
		if (flag == false) {
			return new ResponseEntity<Void>(HttpStatus.CONFLICT);
		}
		HttpHeaders headers = new HttpHeaders();
		headers.setLocation(builder.path("/article?id={id}").buildAndExpand(article.getArticleId()).toUri());
		return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
	}

	@PutMapping("article")
	public ResponseEntity<Article> updateArticle(@RequestBody Article article) {
		articleService.updateArticle(article);
		return new ResponseEntity<Article>(article, HttpStatus.OK);
	}

	@DeleteMapping("article")
	public ResponseEntity<Void> deleteArticle(@RequestParam("id") String id) {
		articleService.deleteArticle(Integer.parseInt(id));
		return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
	}
} 
@RestController : Used to create REST API that is itself annotated with @Controller and @ResponseBody.
@RequestMapping : Annotation for mapping web requests.
@GetMapping : Maps HTTP GET request.
@PostMapping : Maps HTTP POST request.
@PutMapping : Maps HTTP PUT request.
@DeleteMapping : Maps HTTP DELETE request.
@CrossOrigin : Handles Cross-Origin-Resource-Sharing (CORS). This annotation can be used at class level as well as method level in Spring controller. In my example, Angular project will run on following URL.
http://localhost:4200 
And REST webs service project will run on following URL.
http://localhost:8080 
To allow our Angular project script to hit the web service, we need to configure @CrossOrigin as below.
@CrossOrigin(origins = {"http://localhost:4200"}) 
I am using it at class level so that all the REST web service method will be available to our Angular application.

2.5 Create DAO and Service

IArticleDAO.java
public interface IArticleDAO {
    List<Article> getAllArticles();
    Article getArticleById(int articleId);
    void createArticle(Article article);
    void updateArticle(Article article);
    void deleteArticle(int articleId);
    boolean articleExists(String title, String category);
} 
ArticleDAO.java
@Transactional
@Repository
public class ArticleDAO implements IArticleDAO {
	@PersistenceContext
	private EntityManager entityManager;

	@Override
	public Article getArticleById(int articleId) {
		return entityManager.find(Article.class, articleId);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Article> getAllArticles() {
		String hql = "FROM Article as atcl ORDER BY atcl.articleId DESC";
		return (List<Article>) entityManager.createQuery(hql).getResultList();
	}

	@Override
	public void createArticle(Article article) {
		entityManager.persist(article);
	}

	@Override
	public void updateArticle(Article article) {
		Article artcl = getArticleById(article.getArticleId());
		artcl.setTitle(article.getTitle());
		artcl.setCategory(article.getCategory());
		entityManager.flush();
	}

	@Override
	public void deleteArticle(int articleId) {
		entityManager.remove(getArticleById(articleId));
	}

	@Override
	public boolean articleExists(String title, String category) {
		String hql = "FROM Article as atcl WHERE atcl.title = ?1 and atcl.category = ?2";
		int count = entityManager.createQuery(hql)
		        .setParameter(1, title)
		        .setParameter(2, category)
		        .getResultList()
		        .size();
		return count > 0 ? true : false;
	}
} 
Article.java
@Entity
@Table(name = "articles")
public class Article implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "article_id")
	private int articleId;
	@Column(name = "title")
	private String title;
	@Column(name = "category")
	private String category;
	//Setters and Getters
} 
IArticleService.java
public interface IArticleService {
     List<Article> getAllArticles();
     Article getArticleById(int articleId);
     boolean createArticle(Article article);
     void updateArticle(Article article);
     void deleteArticle(int articleId);
} 
ArticleService.java
@Service
public class ArticleService implements IArticleService {
	@Autowired
	private IArticleDAO articleDAO;

	@Override
	public Article getArticleById(int articleId) {
		Article obj = articleDAO.getArticleById(articleId);
		return obj;
	}

	@Override
	public List<Article> getAllArticles() {
		return articleDAO.getAllArticles();
	}

	@Override
	public synchronized boolean createArticle(Article article) {
		if (articleDAO.articleExists(article.getTitle(), article.getCategory())) {
			return false;
		} else {
			articleDAO.createArticle(article);
			return true;
		}
	}

	@Override
	public void updateArticle(Article article) {
		articleDAO.updateArticle(article);
	}

	@Override
	public void deleteArticle(int articleId) {
		articleDAO.deleteArticle(articleId);
	}
} 

2.6 Main

Find the Main class to execute the Spring Boot application.
MyApplication.java
@SpringBootApplication
public class MyApplication {
	public static void main(String[] args) {
		SpringApplication.run(MyApplication.class, args);
	}
} 

3. Create Client using Angular

Now I will create client application using Angular to interact with REST API. Angular is a TypeScript based single-page web application framework. Angular provides HttpClient to perform HTTP requests.

HttpClient

HttpClient is imported from @angular/common/http. In my example, I will use following methods of HttpClient.
HttpClient.get() : Performs HTTP GET requests.
HttpClient.post() : Performs HTTP POST requests.
HttpClient.put() : Performs HTTP PUT requests.
HttpClient.delete() : Performs HTTP DELETE requests.

HttpClient is instantiated using dependency injection as given below.
constructor(private http: HttpClient) { 
} 
To enable HttpClient, make sure to import HttpClientModule in application module.
import { HttpClientModule } from '@angular/common/http'; 

3.1 Create Service

Find the service class using HttpClient to perform CRUD operation.
article.service.ts
import { Injectable } from '@angular/core';
import { Article } from './article';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, map } from 'rxjs';

@Injectable()
export class ArticleService {
	//URLs for CRUD operations
	allArticlesUrl = "http://localhost:8080/app/allarticles";
	articleUrl = "http://localhost:8080/app/article";
	//Create constructor to get Http instance
	constructor(private http: HttpClient) {
	}
	//Fetch all articles
	getAllArticles(): Observable<Article[]> {
		return this.http.get<Article[]>(this.allArticlesUrl);
	}
	//Create article
	createArticle(article: Article): Observable<number> {
		const httpHeaders = new HttpHeaders({
			'Content-Type': 'application/json'
		});
		console.log(article);
		return this.http.post(this.articleUrl, article, {
			headers: httpHeaders,
			observe: 'response'
		}).pipe(map(success => success.status));
	}
	//Fetch article by id
	getArticleById(articleId: string): Observable<Article> {
		const httpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' });
		const cpParams = new HttpParams().set('id', articleId);
		console.log(cpParams.has('id'));
		return this.http.get<Article>(this.articleUrl, {
			headers: httpHeaders,
			params: cpParams
		});
	}
	//Update article
	updateArticle(article: Article): Observable<number> {
		const httpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' });
		return this.http.put(this.articleUrl, article, {
			headers: httpHeaders,
			observe: 'response'
		}).pipe(map(success => success.status));
	}
	//Delete article	
	deleteArticleById(articleId: string): Observable<number> {
		const httpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' });
		const cpParams = new HttpParams().set('id', articleId);
		return this.http.delete(this.articleUrl, {
			headers: httpHeaders,
			params: cpParams,
			observe: 'response'
		}).pipe(map(success => success.status));
	}
} 
article.ts
export interface Article {
   articleId: string;
   title: string;
   category: string;
} 

3.2 Create Components

Find the component and its HTML template that displays data on UI.
article.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

import { ArticleService } from './article.service';
import { Article } from './article';

@Component({
   selector: 'app-article',
   templateUrl: './article.component.html',
   styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
   //Component properties
   allArticles = [] as Article[];
   statusCode = 0 as number | null;
   requestProcessing = false;
   articleIdToUpdate = '' as string | null;
   processValidation = false;
   //Create form
   articleForm = new FormGroup({
      title: new FormControl('', Validators.required),
      category: new FormControl('', Validators.required)
   });
   //Create constructor to get service instance
   constructor(private articleService: ArticleService) {
   }
   //Create ngOnInit() and and load articles
   ngOnInit(): void {
      this.getAllArticles();
   }
   //Fetch all articles
   getAllArticles() {
      this.articleService.getAllArticles()
         .subscribe({
            next: data => this.allArticles = data,
            error: errorCode => this.statusCode = errorCode
         });
   }
   //Handle create and update article
   onArticleFormSubmit() {
      this.processValidation = true;
      if (this.articleForm.invalid) {
         return; //Validation failed, exit from method.
      }
      //Form is valid, now perform create or update
      this.preProcessConfigurations();
      let title = this.articleForm.get('title')?.value?.trim() ?? '';
      let category = this.articleForm.get('category')?.value?.trim() ?? '';
      if (this.articleIdToUpdate === null || this.articleIdToUpdate === '') {
         //Handle create article
         const article: Article = { articleId: '', title: title, category: category };
         this.articleService.createArticle(article)
            .subscribe({
               next: successCode => {
                  this.statusCode = successCode;
                  this.getAllArticles();
                  this.backToCreateArticle();
               },
               error: errorCode => this.statusCode = errorCode
            });
      } else {
         //Handle update article
         const article: Article = { articleId: this.articleIdToUpdate, title: title, category: category };
         this.articleService.updateArticle(article)
            .subscribe({
               next: successCode => {
                  this.statusCode = successCode;
                  this.getAllArticles();
                  this.backToCreateArticle();
               },
               error: errorCode => this.statusCode = errorCode
            });
      }
   }
   //Load article by id to edit
   loadArticleToEdit(articleId: string) {
      this.preProcessConfigurations();
      this.articleService.getArticleById(articleId)
         .subscribe({
            next: article => {
               this.articleIdToUpdate = article.articleId;
               this.articleForm.setValue({ title: article.title, category: article.category });
               this.processValidation = true;
               this.requestProcessing = false;
            },
            error: errorCode => this.statusCode = errorCode
         });
   }
   //Delete article
   deleteArticle(articleId: string) {
      this.preProcessConfigurations();
      this.articleService.deleteArticleById(articleId)
         .subscribe({
            next: successCode => {
               this.statusCode = successCode;
               this.getAllArticles();
               this.backToCreateArticle();
            },
            error: errorCode => this.statusCode = errorCode
         });
   }
   //Perform preliminary processing configurations
   preProcessConfigurations() {
      this.statusCode = null;
      this.requestProcessing = true;
   }
   //Go back from update to create
   backToCreateArticle() {
      this.articleIdToUpdate = null;
      this.articleForm.reset();
      this.processValidation = false;
   }
} 
article.component.html
<h1>Spring Boot + Angular CRUD Operation</h1>
<h3 *ngIf="articleIdToUpdate; else create">
  Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
  <h3> Create New Article </h3>
</ng-template>
<div>
  <form [formGroup]="articleForm" (ngSubmit)="onArticleFormSubmit()">
    <table>
      <tr>
        <td>Enter Title</td>
        <td><input formControlName="title">
          <label *ngIf="articleForm.get('title')?.invalid && processValidation" [ngClass]="'error'"> Title is required.
          </label>
        </td>
      </tr>
      <tr>
        <td>Enter Category</td>
        <td><input formControlName="category">
          <label *ngIf="articleForm.get('category')?.invalid && processValidation" [ngClass]="'error'"> Category is
            required. </label>
        </td>
      </tr>
      <tr>
        <td colspan="2">
          <button *ngIf="!articleIdToUpdate">CREATE</button>
          <button *ngIf="articleIdToUpdate">UPDATE</button>
          <button (click)="backToCreateArticle()" *ngIf="articleIdToUpdate">Go Back</button>
        </td>
      </tr>
    </table>
  </form>
  <br />
  <div *ngIf="statusCode; else processing">
    <div *ngIf="statusCode === 201" [ngClass]="'success'">
      Article added successfully.
    </div>
    <div *ngIf="statusCode === 409" [ngClass]="'success'">
      Article already exists.
    </div>
    <div *ngIf="statusCode === 200" [ngClass]="'success'">
      Article updated successfully.
    </div>
    <div *ngIf="statusCode === 204" [ngClass]="'success'">
      Article 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>Article Details</h3>
<table>
  <tr>
    <th> Id</th>
    <th>Title</th>
    <th>Category</th>
    <th></th>
    <th></th>
  </tr>
  <tr *ngFor="let article of allArticles">
    <td>{{article.articleId}}</td>
    <td>{{article.title}}</td>
    <td>{{article.category}}</td>
    <td><button type="button" (click)="loadArticleToEdit(article.articleId)">Edit</button> </td>
    <td><button type="button" (click)="deleteArticle(article.articleId)">Delete</button></td>
  </tr>
</table> 

3.3 Create Module

Find the application module used in my application.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { ArticleComponent } from './article.component';
import { ArticleService } from './article.service';

@NgModule({
      imports: [
            BrowserModule,
            HttpClientModule,
            ReactiveFormsModule
      ],
      declarations: [
            AppComponent,
            ArticleComponent
      ],
      providers: [
            ArticleService
      ],
      bootstrap: [
            AppComponent
      ]
})
export class AppModule { } 

4. Run Application

Find the steps to run REST web service application and angular application.

A. Run Spring Boot Application

To run the REST web service application, first create table in MySQL as given above in the example and configure database username and password in application.properties file. Now run REST web service application in following ways.
1. Using Eclipse: Download Spring Boot REST application using download link given on this page in download section. Import the project into eclipse. Using command prompt, go to the root folder of the project and run.
mvn clean eclipse:eclipse 
Refresh the project in eclipse. Run Main class MyApplication by clicking Run as -> Java Application. Embedded tomcat server will start.

2. Using Maven Command: Download the source code. Go to the root folder of the project using command prompt and run the command.
mvn spring-boot:run 
Embedded tomcat server will start.

3. Using Executable JAR: Using command prompt, go to the root folder of the project and run the command.
mvn clean package 
We will get executable JAR spring-boot-demo-0.0.1-SNAPSHOT.jar in target folder. Run this JAR as
java -jar target/spring-boot-demo-0.0.1-SNAPSHOT.jar 
Embedded tomcat server will start.

B. Run Angular Application

To run the angular application, find the following steps.
1. Go to the link and install the Angular
2. Download Angular project source code using download link given on this page in download section.
3. In your Angular application, replace src folder by the downloaded src.
4. Run npm start command.
5. Our Angular application is ready on the following URL.
http://localhost:4200 

5. References

6. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us