Spring Boot + Angular CRUD Operation
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.
Contents
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.
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
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); } }
@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
http://localhost:8080
@CrossOrigin
as below.
@CrossOrigin(origins = {"http://localhost:4200"})
2.5 Create DAO and Service
IArticleDAO.javapublic 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); }
@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; } }
@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 }
public interface IArticleService { List<Article> getAllArticles(); Article getArticleById(int articleId); boolean createArticle(Article article); void updateArticle(Article article); void deleteArticle(int articleId); }
@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 providesHttpClient
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) { }
HttpClient
, make sure to import HttpClientModule
in application module.
import { HttpClientModule } from '@angular/common/http';
3.1 Create Service
Find the service class usingHttpClient
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)); } }
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; } }
<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
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
3. Using Executable JAR: Using command prompt, go to the root folder of the project and run the command.
mvn clean package
java -jar target/spring-boot-demo-0.0.1-SNAPSHOT.jar
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