In this tutorial, we’ll walk through the process of creating a simple Jakarta EE 10 application that exposes a RESTful endpoint to fetch a list of customers. We’ll then build an Angular front end that consumes this endpoint and displays the list of customers in a user-friendly format.
Prerequisites
Before starting, ensure you have the following tools installed:
- Java Development Kit (JDK) 11 or later
- Maven (for building the Jakarta EE application)
- WildFly or GlassFish (for deploying the Jakarta EE application)
- Node.js and npm (for managing Angular and its dependencies)
- Angular CLI (for creating and managing Angular projects)
Coding the Jakarta EE back-end
The source code for our application is a Domain-Driven-Model which exposes an Endpoint with CRUD operations against a single Entity, If you are new to JAX-RS, you can find more details about how to build this example here: REST CRUD application with JPA | A complete example
Her,e we will show the core Components of our example. Firstly, the Customer
Entity:
@Entity @NamedQuery(name = "Customers.findAll", query = "SELECT c FROM Customer c ORDER BY c.id") public class Customer { @Id @SequenceGenerator( name = "customerSequence", sequenceName = "customerId_seq", allocationSize = 1, initialValue = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence") private Long id; @Column private String firstName; @Column private String lastName; @Column private String email; // Getters/Setters omitted for brevity }
Our back-end application uses the Repository
pattern to manage Database access. The following CustomerRepository
Bean controls the access to the Customer
Entity:
@ApplicationScoped public class CustomerRepository { @PersistenceContext private EntityManager entityManager; public List<Customer> findAll() { return entityManager.createNamedQuery("Customers.findAll", Customer.class) .getResultList(); } public Customer findCustomerById(Long id) { Customer customer = entityManager.find(Customer.class, id); if (customer == null) { throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404); } return customer; } @Transactional public void updateCustomer(long id, Customer customer) { Customer c = entityManager.find(Customer.class, id); c.setFirstName(customer.getFirstName()); c.setLastName(customer.getFirstName()); c.setEmail(customer.getEmail()); entityManager.persist(c); } @Transactional public void createCustomer(Customer customer) { entityManager.persist(customer); } @Transactional public void deleteCustomer(Long customerId) { Customer c = findCustomerById(customerId); entityManager.remove(c); } }
Finally, each Repository method is wrapped by the corresponding endpoint methods in the CustomerEndpoint
Class:
@Path("customer") @ApplicationScoped @Produces("application/json") @Consumes("application/json") public class CustomerEndpoint { @Inject CustomerRepository customerRepository; @GET public List<Customer> getAll() { return customerRepository.findAll(); } @POST public Response create(Customer customer) { customerRepository.createCustomer(customer); return Response.status(201).build(); } @GET @Path("/{id}") public Response findById(@PathParam("id") long id) { Customer customer = customerRepository.findCustomerById(id); return Response.ok(customer).build(); } @PUT @Path("/{id}") public Response updateCustomer(@PathParam("id") int id, Customer customer) { customerRepository.updateCustomer(id, customer); return Response.ok(customer).build(); } @DELETE @Path("/{id}") public Response deleteUser(@PathParam("id") long id) { customerRepository.deleteCustomer(id); return Response.noContent().build(); } }
The source code of the back-end also includes the JaxRsActivator and the Maven WildFly plugin to deploy the application on WildFly with:
mvn install wildfly:deploy
Finally, we need to enable CORS on WildFly. Enabling CORS (Cross-Origin Resource Sharing) is necessary when you want to allow your web application, running on a different domain or port, to make HTTP requests to your WildFly server. For this purpose, you can follow the instructions available in this article: How to configure CORS on WildFly
Coding the Angular Frontend
Angular is a popular open-source web application framework. It allows building dynamic, modern web applications with a strong focus on a component-based architecture, two-way data binding, and modularity. Angular allows developers to create Single Page Applications (SPAs) that are responsive, fast, and easy to maintain.
Angular CLI (Command Line Interface) is a powerful tool for automating various tasks in Angular development. It provides developers with commands to quickly generate, build, test, and deploy Angular applications, ensuring consistency and best practices across the project.
To get started with Angular, firstly make sure you have the npm
and Node.js
available your system. The following link will guide you through the installation: Downloading and installing Node.js and npm | npm Docs (npmjs.com)
Next, we will install the Angular CLI by typing the following command:
npm install -g @angular/cli
The Angular CLI allows you to use the ng
command to scaffold, build and test your applications. The first command we will learn is ng new
which creates a new Angular application.
>ng new angular-app ? Which stylesheet format would you like to use? CSS [ https://developer.mozilla.org/docs/Web/CSS ] ? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? no . . . . . ⠴ Installing packages (npm)...
In our example, we are creating an Angular project “angular-app” which relies on Mozilla’s CSS. SSR and SSG are valuable options if your Angular application needs faster initial load times and better SEO. However, for our basic example we will not add them.
Building the Angular Model and Service
Angular apps are built using Components, which are reusable, self-contained units of UI and logic. Before creating the Components, we need to define the Model which matches the Customer
Entity. Also, we need a Service to interact with the back-end. Let’s begin with the Model.
In Angular, you can define models in TypeScript, which is a superset of Javascript. From the Command Line, create the Customer
Model with the following command:
ng g class Customer --type=model
Then, edit the file customer.model.ts
to include the fields of the Customer
Entity:
export class Customer { id!: number; firstName!: string; lastName!: string; email!: string; }
To interact with the back-end, we will need an Angular Service which maps the Endpoints available in our WildFly REST Service. You can generate an Angular Service with the following command:
ng g s services/customer
Then, edit the services/customer.service.ts
file to include the following TypeScript Service:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Customer } from '../customer.model'; @Injectable({ providedIn: 'root' }) export class CustomerService { private basUrl = "http://localhost:8080/jaxrs-demo/customer" constructor(private httpClient: HttpClient) { } getCustomerList(): Observable<Customer[]> { return this.httpClient.get<Customer[]>(`${this.basUrl}`); } createCustomer(customer: Customer): Observable<Object> { console.log('Sending customer data:', customer); return this.httpClient.post(`${this.basUrl}`, customer); } getCustomerById(id: number): Observable<Customer>{ return this.httpClient.get<Customer>(`${this.basUrl}/${id}`); } updateCustomer(id:number, customer:Customer): Observable<Object>{ return this.httpClient.put(`${this.basUrl}/${id}`, customer); } deleteCustomer(id:number): Observable<Object>{ return this.httpClient.delete(`${this.basUrl}/${id}`); } }
This Service uses Angular’s HttpClient
to interact with a backend API at http://localhost:8080/jaxrs-demo/customer
, providing methods to get, create, update, and delete customer records. This service abstracts the API communication, making it easy for components to use these methods to interact with the backend without directly handling HTTP logic.
Creating the Angular Components
Next, we will build our Angular Components which contain the Views our application uses to interact with the server side application. A typical Angular front end, requires a set of three views:
ng g c customer-list ng g c customer-create ng g c customer-update
The first command, will create the customer-list
Component which displays the list of Customers in tabular format. The customer-create
contains the Form to insert a new Customer. Finally, the customer-update
also contains a Form which allows updating an existing Customer. All three commands will generate the corresponding .ts, .html, .css, and .spec.ts files.
Once that you have the backbone for all components, let’s start the customization.
Firstly, let’s edit the customer-list
Component, which relies on the customer.service
to fetch data from the back-end and uses the customer.model
to visualize them:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { CustomerService } from '../services/customer.service'; import { Customer } from '../customer.model'; @Component({ selector: 'app-customer-list', templateUrl: './customer-list.component.html', styleUrls: ['./customer-list.component.css'] }) export class CustomerListComponent implements OnInit { customers: Customer[] | undefined; constructor(private customerService: CustomerService, private router: Router) { } ngOnInit(): void { this.getCustomers(); } private getCustomers() { this.customerService.getCustomerList().subscribe(data => { this.customers = data; }); } updateCustomer(id: number) { this.router.navigate(['update-customer', id]); } deleteCustomer(id: number) { this.customerService.deleteCustomer(id).subscribe(data => { console.log(data); this.getCustomers(); }); } }
Then, let’s edit the customer-create
Component as well:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { CustomerService } from '../services/customer.service'; import { Customer } from '../customer.model'; @Component({ selector: 'app-customer-create', templateUrl: './customer-create.component.html', styleUrls: ['./customer-create.component.css'] }) export class CustomerCreateComponent implements OnInit { customer: Customer = new Customer(); constructor(private customerService: CustomerService, private router: Router) { } ngOnInit(): void { } saveCustomer() { this.customerService.createCustomer(this.customer).subscribe({ next: (data) => { console.log(data); this.redirectToCustomerList(); }, error: (e) => { console.log(e); } }); } redirectToCustomerList() { this.router.navigate(['/customers']); } onSubmit() { console.log(this.customer); this.saveCustomer(); } }
This Angular component is responsible for creating a new customer. It includes a form where a user can input customer details. When the user submits the form (onSubmit()
), the component calls saveCustomer()
, which uses the CustomerService
to send the customer data to the backend API. Upon a successful response, the component redirects the user to the customer list page. The ngOnInit()
method is included but doesn’t perform any actions, serving as a placeholder for future initialization logic.
Finally, the customer-update
Component which is responsible to update an existing Customer:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { CustomerService } from '../services/customer.service'; import { Customer } from '../customer.model'; @Component({ selector: 'app-customer-update', templateUrl: './customer-update.component.html', styleUrls: ['./customer-update.component.css'] }) export class CustomerUpdateComponent implements OnInit { id!: number; customer: Customer = new Customer(); constructor(private customerService: CustomerService, private route: ActivatedRoute, private router: Router) { } private getCustomerById() { this.id = this.route.snapshot.params['id']; this.customerService.getCustomerById(this.id).subscribe({ next: (data) => { this.customer = data; }, error: (e) => { console.log(e); } }); } ngOnInit(): void { this.getCustomerById(); } updateCustomer() { this.customerService.updateCustomer(this.id, this.customer).subscribe({ next: (data) => { console.log(data); this.redirectToCustomerList(); }, error: (e) => { console.log(e); } }); } redirectToCustomerList() { this.router.navigate(['/customers']); } onSubmit() { console.log(this.customer); this.updateCustomer(); } }
As you can see, the main difference with the CustomerCreateComponent
is that the CustomerUpdateComponent
performs a getCustomerById()
to retrieve the Customer object. The Component will use the Customer id in the updateCustomer which updates the existing Customer.
Finally, to map the URL of our Angular Components, we will use the AppRoutingModule
which allows for navigation between different parts of the application based on the URL: CustomerListComponent
for displaying the list of customers, CustomerCreateComponent
for creating a new customer, and CustomerUpdateComponent
for updating an existing customer. The default route (''
) redirects to the customer list, ensuring that the CustomerListComponent
is the landing page when the application starts.
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CustomerCreateComponent } from './customer-create/customer-create.component'; import { CustomerListComponent } from './customer-list/customer-list.component'; import { CustomerUpdateComponent } from './customer-update/customer-update.component'; const routes: Routes = [ { path: 'customers', component: CustomerListComponent }, { path: '', redirectTo: 'customers', pathMatch: 'full' }, { path: 'create-customer', component: CustomerCreateComponent }, { path: 'update-customer/:id', component: CustomerUpdateComponent }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Building an running the Angular Application
Assuming you have downloaded the source code for this example from GitHub, before running it the first time you should first run an npm update
command:
npm update
The command npm update
updates the dependencies in your project to the latest versions that satisfy the version ranges specified in your package.json
file.
Then, you are ready to serve the Angular application with:
ng serve --o
By default, the browser will disaply the application on localhost:4200 .As you can see from the landing page, the Customer List will display some customers loaded with the import.sql
script on the server-side.
By clicking on the Create Customer upper link, you will be able to insert a new Customer object in the Database:
Finally, by clicking on the Update and Delete buttons from the Customer List you will be able to perform the specific actions on the Customer Entity.
Conclusion
In this tutorial, we created a simple Jakarta EE 10 application that exposes a RESTful endpoint and an Angular front end that consumes this endpoint. This setup provides a basic yet powerful foundation for building more complex enterprise applications with a modern front end and a robust backend.
Source code: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/angular