Spring Boot 3 + Angular 18 CRUD Example

author : Sai K

In this tutorial, we will create a CRUD (Create, Read, Update, Delete) application using Spring Boot 3.3 for the

backend and Angular 18 for the frontend. We will handle CORS issues to ensure smooth communication

between the Angular frontend and the Spring Boot backend.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed

  • Apache Maven installed

  • Node.js and npm installed

  • Angular CLI installed (npm install -g @angular/cli)

  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Step 1: Setting Up the Spring Boot Backend

1.1 Create a Spring Boot Project

1.Open Spring Initializr:

  • Go to Spring Initializr in your web browser.
  • 1.Configure Project Metadata:

    • Project: Maven Project

    • Language: Java

    • Spring Boot: Select the latest version of Spring Boot 3.3

    • Group: com.example

    • Artifact: product-service

    • Name: product-service

    • Description: Product Service

    • Package Name: com.example.productservice

    • Packaging: Jar

    • Java Version: 17 (or your preferred version)

    • Click Next.

    Select Dependencies:

    • On the Dependencies screen, select:
  • Spring Web

  • Spring Data JPA

  • H2 Database

  • Spring Boot DevTools

    • Click Next.

    4.Generate the Project:

    • Click Generate to download the project zip file.

    • Extract the zip file to your desired location.

    5.Open the Project in Your IDE:

    Open your IDE and import the project as a Maven project.

    1.2 Update application.properties

    Open the application.properties file located in the src/main/resources directory and add the following configuration:

    
    server.port=8080
    
    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=password
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    spring.h2.console.enabled=true
    spring.jpa.hibernate.ddl-auto=update
                  

    Explanation:

  • Configures the server port to 8080.

  • Sets up an in-memory H2 database.

  • Enables the H2 console for database inspection.

  • Automatically updates the database schema based on JPA entities.

  • 1.3 Create Product Entity

    Create a Product entity class in the com.example.productservice.model package:

    
    package com.example.productservice.model;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class Product {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private double price;
    
        // Getters and Setters
    }
                    

    Explanation:

    • @Entity: Marks the class as a JPA entity.

    • @Id: Marks the id field as the primary key.

    • @GeneratedValue(strategy = GenerationType.IDENTITY): Configures auto-increment for the id field.

    • name, price: Fields representing product attributes.

    • Getters and Setters: Methods to access and modify the fields.

    1.4 Create Product Repository

    Create a ProductRepository interface in the com.example.productservice.repository package:

    
    package com.example.productservice.repository;
    
    import com.example.productservice.model.Product;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface ProductRepository extends JpaRepository {
    } 

    Explanation:

  • @Repository: Marks the interface as a Spring Data repository.

  • Extends JpaRepository: Provides CRUD operations for the Product entity.

  • 1.5 Create Product Service

    Create a ProductService class in the com.example.productservice.service package:

    
                package com.example.productservice.service;
    
    import com.example.productservice.model.Product;
    import com.example.productservice.repository.ProductRepository;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.Optional;
    
    @Service
    public class ProductService {
    
        private final ProductRepository productRepository;
    
        public ProductService(ProductRepository productRepository) {
            this.productRepository = productRepository;
        }
    
        public List getAllProducts() {
            return productRepository.findAll();
        }
    
        public Optional getProductById(Long id) {
            return productRepository.findById(id);
        }
    
        public Product saveProduct(Product product) {
            return productRepository.save(product);
        }
    
        public void deleteProduct(Long id) {
            productRepository.deleteById(id);
        }
    }
             

    Explanation:

    • @Service: Marks the class as a service component.

    • ProductRepository productRepository: Repository to interact with the database.

    • getAllProducts(): Retrieves all products.

    • getProductById(Long id): Retrieves a product by ID.

    • saveProduct(Product product): Saves a new or updated product.

    • deleteProduct(Long id): Deletes a product by ID.

    1.6 Create Product Controller

    Create a ProductController class in the com.example.productservice.controller package:

    
    package com.example.productservice.controller;
    
    import com.example.productservice.model.Product;
    import com.example.productservice.service.ProductService;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    import java.util.Optional;
    
    @CrossOrigin(origins = "http://localhost:4200")
    @RestController
    @RequestMapping("/products")
    public class ProductController {
    
        private final ProductService productService;
    
        @Autowired
        public ProductController(ProductService productService) {
            this.productService = productService;
        }
    
        @GetMapping
        public List getAllProducts() {
            return productService.getAllProducts();
        }
    
        @GetMapping("/{id}")
        public Optional getProductById(@PathVariable Long id) {
            return productService.getProductById(id);
        }
    
        @PostMapping
        public Product createProduct(@RequestBody Product product) {
            return productService.saveProduct(product);
        }
    
        @PutMapping("/{id}")
        public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
            product.setId(id);
            return productService.saveProduct(product);
        }
    
        @DeleteMapping("/{id}")
        public void deleteProduct(@PathVariable Long id) {
            productService.deleteProduct(id);
        }
    }
     

    Explanation:

    • @CrossOrigin(origins = "http://localhost:4200"): Enables CORS for requests from the Angular frontend running on localhost:4200.

    • @RestController: Marks the class as a REST controller.

    • @RequestMapping("/products"): Maps requests to /products.

    • getAllProducts(): Handles GET requests to retrieve all products.

    • getProductById(Long id): Handles GET requests to retrieve a product by ID.

    • createProduct(@RequestBody Product product): Handles POST requests to create a new product.

    • updateProduct(@PathVariable Long id, @RequestBody Product product): Handles PUT requests to update an existing product.

    • deleteProduct(@PathVariable Long id): Handles DELETE requests to delete a product by ID.

    1.7 Run the Spring Boot Application

    Run the application by executing the ProductServiceApplication class. The backend should be up and running on http://localhost:8080.

    Step 2: Setting Up the Angular Frontend

    2.1 Create an Angular Project

    1.Open a terminal and run the following command to create a new Angular project:

    ng new product-client

    2.Navigate to the project directory:

    cd product-client 

    2.2 Install Dependencies

    Install Bootstrap for styling:

    npm install bootstrap

    Add Bootstrap to angular.json:

    
    "styles": [
      "src/styles.css",
      "node_modules/bootstrap/dist/css/bootstrap.min.css"
    ],
                    

    2.3 Create Angular Services and Components

    2.3.1 Create Product Service

    Generate the ProductService:

    
    ng generate service services/product
    

    Edit product.service.ts:

    Explanation:

    • @Injectable({ providedIn: 'root' }): Marks the service as injectable and available throughout the app.

    • HttpClient: Service for making HTTP requests.

    • getAllProducts(): Sends a GET request to retrieve all products.

    • getProductById(id: number): Sends a GET request to retrieve a product by ID.

    • createProduct(product: Object): Sends a POST request to create a new product.

    • updateProduct(id: number, product: Object): Sends a PUT request to update an existing product.

    • deleteProduct(id: number): Sends a DELETE request to delete a product by ID.

    2.3.2 Create Components

    Generate the components for displaying and managing products:

    
    ng generate component components/product-list
    ng generate component components/product-form
    

    Edit product-list.component.ts:

    
    import { Component, OnInit } from '@angular/core';
    import { ProductService } from '../../services/product.service';
    
    @Component({
      selector: 'app-product-list',
      templateUrl: './product-list.component.html',
      styleUrls: ['./product-list.component.css']
    })
    export class ProductListComponent implements OnInit {
      products: any[] = [];
    
      constructor(private productService: ProductService) { }
    
      ngOnInit(): void {
        this.productService.getAllProducts().subscribe(data => {
          this.products = data;
        });
      }
    
      deleteProduct(id: number) {
        this.productService.deleteProduct(id).subscribe(() => {
          this.products = this.products.filter(product => product.id !== id);
        });
      }
    }
    

    Edit product-list.component.html:

    
    < div class="container mt-5">
      < div class="row">
        < div class="col-md-12">
          < h2>Product List< /h2>
          < a routerLink="/add-product" class="btn btn-primary mb-3">Add Product< /a>
          < table class="table table-striped">
            < thead>
              < tr>
                < th>ID
                < th>Name
                < th>Price
                < th>Actions
              < /tr>
            < /thead>
            < tbody>
              < tr *ngFor="let product of products">
                < td>{{ product.id }}< /td>
                < td>{{ product.name }}< /td>
                < td>{{ product.price }}< /td>
                < td>
                  < a [routerLink]="['/edit-product', product.id]" class="btn btn-info btn-sm">Edit< /a>
                  < button (click)="deleteProduct(product.id)" class="btn btn-danger btn-sm">Delete< /button>
                < /td>
              < /tr>
            < /tbody>
          < /table>
        < /div>
      < /div>
    < /div>
    

    Edit product-form.component.ts:

    
    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute, Router } from '@angular/router';
    import { ProductService } from '../../services/product.service';
    
    @Component({
      selector: 'app-product-form',
      templateUrl: './product-form.component.html',
      styleUrls: ['./product-form.component.css']
    })
    export class ProductFormComponent implements OnInit {
      product: any = { name: '', price: 0 };
      isEditMode: boolean = false;
    
      constructor(private productService: ProductService, private route: ActivatedRoute, private router: Router) { }
    
      ngOnInit(): void {
        const id = this.route.snapshot.paramMap.get('id');
        if (id) {
          this.isEditMode = true;
          this.productService.getProductById(Number(id)).subscribe(data => {
            this.product = data;
          });
        }
      }
    
      saveProduct() {
        if (this.isEditMode) {
          this.productService.updateProduct(this.product.id, this.product).subscribe(() => {
            this.router.navigate(['/products']);
          });
        } else {
          this.productService.createProduct(this.product).subscribe(() => {
            this.router.navigate(['/products']);
          });
        }
      }
    }
    

    Edit product-form.component.html:

    
    < div class="container mt-5">
      < div class="row justify-content-center">
         < div class="col-md-6">
          < div class="card">
            < div class="card-header">
              < h2>{{ isEditMode ? 'Edit Product' : 'Add Product' }}< /h2>
            < /div>
            < div class="card-body">
              < form (ngSubmit)="saveProduct()">
                < div class="form-group">
                  < label for="name">Name< /label>
                  < input type="text" id="name" class="form-control" [(ngModel)]="product.name" name="name" required>
                < /div>
                < div class="form-group">
                  < label for="price">Price< /label>
                  < input type="number" id="price" class="form-control" [(ngModel)]="product.price" name="price" required>
                < /div>
                < button type="submit" class="btn btn-primary">{{ isEditMode ? 'Update' : 'Save' }}< /button>
                < a routerLink="/products" class="btn btn-secondary ml-2">Cancel< /a>
              < /form>
            < /div>
          < /div>
        < /div>
      < /div>
    < /div>
    

    2.4 Update Angular Routing

    Edit app-routing.module.ts:

    
    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    import { ProductListComponent } from './components/product-list/product-list.component';
    import { ProductFormComponent } from './components/product-form/product-form.component';
    
    const routes: Routes = [
      { path: 'products', component: ProductListComponent },
      { path: 'add-product', component: ProductFormComponent },
      { path: 'edit-product/:id', component: ProductFormComponent },
      { path: '', redirectTo: '/products', pathMatch: 'full' }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    

    Explanation:

    • Defines routes for the product list and product form components.

    • Redirects the root path to the product list component.

    2.5 Update Angular App Module

    Edit app.module.ts:

    
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    import { HttpClientModule } from '@angular/common/http';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { ProductListComponent } from './components/product-list/product-list.component';
    import { ProductFormComponent } from './components/product-form/product-form.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        ProductListComponent,
        ProductFormComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        FormsModule,
        HttpClientModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    Explanation:

    • Imports necessary modules for the Angular app.

    • Declares the components used in the app.

    • Sets up the app's root module.

    2.6 Run the Angular Application

    Open a terminal in the Angular project directory and run the application:

    ng serve

    Visit http://localhost:4200 in your web browser to see the application.

    Conclusion:

    In this tutorial, we created a CRUD application using Spring Boot 3.3 for the backend and Angular 18 for the

    We handled CORS issues to ensure smooth communication between the Angular frontend and the Spring Boot

    backend. By following this structure, you can extend and customize the application as needed.


    Related Spring and Spring Boot Tutorials/Guides: