Angular Spring Boot Microservices Example

In this tutorial, we will create a microservices architecture using Spring Boot and Angular. We will build two microservices, an API Gateway, a Service Registry, and an Angular client that calls the API Gateway. We will use the latest versions of Spring Boo,  Angular, and Spring Cloud.

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

Overview of the Architecture

  1. Service Registry: Manages service discovery.
  2. API Gateway: Routes client requests to appropriate microservices.
  3. Employee Service: Manages employee data.
  4. Department Service: Manages department data.
  5. Angular Client: Provides a user interface to interact with the services via the API Gateway.

Step 1: Create the Service Registry

1.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: service-registry
    • Name: service-registry
    • Package Name: com.example.serviceregistry
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select:
      • Spring Cloud Discovery
    • 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.yml

Create an application.yml file in the src/main/resources directory and add the following configuration:

server:
  port: 8761

spring:
  application:
    name: service-registry

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  instance:
    hostname: localhost
  server:
    enable-self-preservation: false

1.3 Enable Eureka Server

Create a class ServiceRegistryApplication and annotate it with @EnableEurekaServer:

package com.example.serviceregistry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceRegistryApplication.class, args);
    }
}

1.4 Run the Service Registry

Run the application by executing the ServiceRegistryApplication class. The Eureka Dashboard should be accessible at http://localhost:8761.

Step 2: Create the Employee Service

2.1 Create a Spring Boot Project

  1. Open Spring Initializr and configure a new project with the following metadata:

    • Group: com.example
    • Artifact: employee-service
    • Name: employee-service
    • Package Name: com.example.employeeservice
    • Dependencies: Spring Web, Spring Data JPA, H2 Database, Spring Cloud Discovery, Spring Boot DevTools
  2. Generate the Project and open it in your IDE.

2.2 Update application.yml

Create an application.yml file in the src/main/resources directory:

server:
  port: 8081

spring:
  application:
    name: employee-service

  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

2.3 Create Employee Entity

Create an Employee entity class in the com.example.employeeservice.model package:

package com.example.employeeservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    // Getters and Setters
}

2.4 Create Employee Repository

Create an EmployeeRepository interface in the com.example.employeeservice.repository package:

package com.example.employeeservice.repository;

import com.example.employeeservice.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

2.5 Create Employee Controller

Create an EmployeeController class in the com.example.employeeservice.controller package:

package com.example.employeeservice.controller;

import com.example.employeeservice.model.Employee;
import com.example.employeeservice.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeRepository employeeRepository;

    @Autowired
    public EmployeeController(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @GetMapping
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @PostMapping
    public Employee createEmployee(@RequestBody Employee employee) {
        return employeeRepository.save(employee);
    }
}

2.6 Enable Discovery Client

Annotate the main application class with @EnableDiscoveryClient:

package com.example.employeeservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class EmployeeServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(EmployeeServiceApplication.class, args);
    }
}

2.7 Run the Employee Service

Run the application by executing the EmployeeServiceApplication class. The service should register itself with the Eureka server.

Step 3: Create the Department Service

Repeat the same steps as for the Employee Service, but with the following changes:

3.1 Create a Spring Boot Project

  1. Open Spring Initializr and configure a new project with the following metadata:

    • Group: com.example
    • Artifact: department-service
    • Name: department-service
    • Package Name: com.example.departmentservice
    • Dependencies: Spring Web, Spring Data JPA, H2 Database, Spring Cloud Discovery, Spring Boot DevTools
  2. Generate the Project and open it in your IDE.

3.2 Update application.yml

Create an application.yml file in the src/main/resources directory:

server:
  port: 8082

spring:
  application:
    name: department-service

  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

3.3 Create Department Entity

Create a Department entity class in the com.example.departmentservice.model package:

package com.example.departmentservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    // Getters and Setters
}

3.4 Create Department Repository

Create a DepartmentRepository interface in the com.example.departmentservice.repository package:

package com.example.departmentservice.repository;

import com.example.departmentservice.model.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}

3.5 Create Department Controller

Create a DepartmentController class in the com.example.departmentservice.controller package:

package com.example.departmentservice.controller;

import com.example.departmentservice.model.Department;
import com.example.departmentservice.repository.DepartmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/departments")
public class DepartmentController {

    private final DepartmentRepository departmentRepository;

    @Autowired
    public DepartmentController(DepartmentRepository departmentRepository) {
        this.departmentRepository = departmentRepository;
    }

    @GetMapping


    public List<Department> getAllDepartments() {
        return departmentRepository.findAll();
    }

    @PostMapping
    public Department createDepartment(@RequestBody Department department) {
        return departmentRepository.save(department);
    }
}

3.6 Enable Discovery Client

Annotate the main application class with @EnableDiscoveryClient:

package com.example.departmentservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class DepartmentServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(DepartmentServiceApplication.class, args);
    }
}

3.7 Run the Department Service

Run the application by executing the DepartmentServiceApplication class. The service should register itself with the Eureka server.

Step 4: Create the API Gateway

4.1 Create a Spring Boot Project

  1. Open Spring Initializr and configure a new project with the following metadata:

    • Group: com.example
    • Artifact: api-gateway
    • Name: api-gateway
    • Package Name: com.example.apigateway
    • Dependencies: Spring Cloud Gateway, Spring Cloud Discovery
  2. Generate the Project and open it in your IDE.

4.2 Update application.yml

Create an application.yml file in the src/main/resources directory:

server:
  port: 8080

spring:
  application:
    name: api-gateway

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

spring:
  cloud:
    gateway:
      routes:
        - id: employee_service
          uri: lb://employee-service
          predicates:
            - Path=/employees/**
        - id: department_service
          uri: lb://department-service
          predicates:
            - Path=/departments/**

4.3 Enable Discovery Client

Annotate the main application class with @EnableDiscoveryClient:

package com.example.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

4.4 Run the API Gateway

Run the application by executing the ApiGatewayApplication class. The API Gateway should register itself with the Eureka server.

Step 5: Create the Angular Client

5.1 Create an Angular Project

  1. Open a terminal and run the following command to create a new Angular project:
ng new client-app
  1. Navigate to the project directory:
cd client-app

5.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"
],

5.3 Create Angular Services and Components

5.3.1 Create API Service

Generate the ApiService:

ng generate service services/api

Edit api.service.ts:

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

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

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

  constructor(private http: HttpClient) { }

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

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

Explanation:

  • @Injectable({ providedIn: 'root' }): Marks the service as injectable and available throughout the app.
  • HttpClient: Service for making HTTP requests.
  • getEmployees(): Sends a GET request to the employee service via the API Gateway.
  • getDepartments(): Sends a GET request to the department service via the API Gateway.

5.3.2 Create Components

Generate the components for displaying employees and departments:

ng generate component components/employees
ng generate component components/departments

Edit employees.component.ts:

import { Component, OnInit } from '@angular/core';
import { ApiService } from '../../services/api.service';

@Component({
  selector: 'app-employees',
  templateUrl: './employees.component.html',
  styleUrls: ['./employees.component.css']
})
export class EmployeesComponent implements OnInit {
  employees: any[] = [];

  constructor(private apiService: ApiService) { }

  ngOnInit(): void {
    this.apiService.getEmployees().subscribe(data => {
      this.employees = data;
    });
  }
}

Edit employees.component.html:

<div class="container mt-5">
  <div class="row">
    <div class="col-md-12">
      <h2>Employees</h2>
      <table class="table table-striped">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Department</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let employee of employees">
            <td>{{ employee.id }}</td>
            <td>{{ employee.name }}</td>
            <td>{{ employee.department }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

Edit departments.component.ts:

import { Component, OnInit } from '@angular/core';
import { ApiService } from '../../services/api.service';

@Component({
  selector: 'app-departments',
  templateUrl: './departments.component.html',
  styleUrls: ['./departments.component.css']
})
export class DepartmentsComponent implements OnInit {
  departments: any[] = [];

  constructor(private apiService: ApiService) { }

  ngOnInit(): void {
    this.apiService.getDepartments().subscribe(data => {
      this.departments = data;
    });
  }
}

Edit departments.component.html:

<div class="container mt-5">
  <div class="row">
    <div class="col-md-12">
      <h2>Departments</h2>
      <table class="table table-striped">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let department of departments">
            <td>{{ department.id }}</td>
            <td>{{ department.name }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

5.4 Update Angular Routing

Edit app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EmployeesComponent } from './components/employees/employees.component';
import { DepartmentsComponent } from './components/departments/departments.component';

const routes: Routes = [
  { path: 'employees', component: EmployeesComponent },
  { path: 'departments', component: DepartmentsComponent },
  { path: '', redirectTo: '/employees', pathMatch: 'full' }
];

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

Explanation:

  • Defines routes for the employees and departments components.
  • Redirects the root path to the employees component.

5.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 { EmployeesComponent } from './components/employees/employees.component';
import { DepartmentsComponent } from './components/departments/departments.component';

@NgModule({
  declarations: [
    AppComponent,
    EmployeesComponent,
    DepartmentsComponent
  ],
  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.

5.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.

Handling CORS Issue

CORS (Cross-Origin Resource Sharing) issues occur when a web application running at one origin (domain) tries to make requests to a resource at a different origin (domain). Browsers enforce the same-origin policy for security reasons, which restricts how resources on one origin can interact with resources on another origin. If the server does not explicitly allow requests from the requesting origin by including appropriate CORS headers, the browser blocks the request, resulting in a CORS issue.

To handle CORS (Cross-Origin Resource Sharing) issues in the API Gateway, you need to configure CORS settings in the application.yml file and create a CORS configuration class in the API Gateway project. Here's how you can do it:

Update application.yml to Include CORS Configuration

Add the CORS configuration under spring.cloud.gateway in the application.yml file:

server:
  port: 8080

spring:
  application:
    name: api-gateway

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

cloud:
  gateway:
    routes:
      - id: employee_service
        uri: lb://employee-service
        predicates:
          - Path=/employees/**
      - id: department_service
        uri: lb://department-service
        predicates:
          - Path=/departments/**
    globalcors:
      corsConfigurations:
        '[/**]':
          allowedOrigins: "*"
          allowedMethods:
            - GET
            - POST
            - PUT
            - DELETE
          allowedHeaders:
            - "*"

Create a CORS Configuration Class

Create a class CorsConfiguration in the com.example.apigateway.config package:

package com.example.apigateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.addAllowedOrigin("*");
        corsConfig.addAllowedMethod("*");
        corsConfig.addAllowedHeader("*");
        corsConfig.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);

        return new CorsWebFilter(source);
    }
}

Explanation:

  • CorsConfiguration class defines the CORS configuration.
  • CorsWebFilter bean applies the CORS configuration to all routes.
  • UrlBasedCorsConfigurationSource is used to map the CORS configuration to the URL patterns.

With this configuration, CORS is globally enabled for all routes handled by the API Gateway, allowing requests from any origin with any HTTP method and headers. This should resolve any CORS issues when your Angular client makes requests to the API Gateway.

Now, your API Gateway will properly handle CORS issues.

Conclusion

In this tutorial, we created a microservices architecture using Spring Boot and Angular. We built two microservices (Employee Service and Department Service), an API Gateway, a Service Registry, and an Angular client that interacts with the microservices through the API Gateway. This setup provides a scalable and maintainable architecture for building enterprise-level applications.

Comments