
Construyendo una aplicación Full-Stack reactiva con Quarkus y Angular 17
Backend: Java, Quarkus, Hibernate ORM con Panache, Base de datos en memoria (H2). Frontend: Angular 17+, TypeScript, RxJS. --- Genera un nuevo proyecto…
Tecnologías que utilizaremos
- Backend: Java, Quarkus, Hibernate ORM con Panache, Base de datos en memoria (H2).
- Frontend: Angular 17+, TypeScript, RxJS.
Parte 1: El Backend con Quarkus
1. Creación del proyecto y configuración
Genera un nuevo proyecto Quarkus agregando las dependencias de RESTEasy Jackson, Hibernate ORM con Panache y el driver de H2.
En tu archivo src/main/resources/application.properties, configura la base de datos en memoria:
# Configuración de Base de Datos H2
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:clientes_db
quarkus.hibernate-orm.database.generation=drop-and-create
2. La Entidad y el Repositorio
Creamos nuestra entidad Cliente usando JPA clásico.
package com.miempresa.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "clientes")
public class Cliente {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String nombre;
public String email;
public String tipoProblema;
// Genera aquí tus Getters y Setters...
}
Luego, aprovechamos la magia de Panache para crear el repositorio sin escribir consultas SQL:
package com.miempresa.repository;
import com.miempresa.entity.Cliente;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ClienteRepository implements PanacheRepository<Cliente> {
}
3. La Capa de Servicio
Aquí manejamos la lógica de negocio y las transacciones de base de datos.
package com.miempresa.service;
import com.miempresa.entity.Cliente;
import com.miempresa.repository.ClienteRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.util.List;
@ApplicationScoped
public class ClienteService {
@Inject
ClienteRepository clienteRepository;
@Transactional
public Cliente registrarCliente(Cliente cliente) {
clienteRepository.persist(cliente);
return cliente;
}
public List<Cliente> obtenerTodos() {
return clienteRepository.listAll();
}
}
4. El Controlador REST y el Filtro CORS
Para exponer nuestra API al mundo, creamos el Resource.
package com.miempresa.resource;
import com.miempresa.entity.Cliente;
import com.miempresa.service.ClienteService;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/api/v1/clientes")
public class ClienteResource {
@Inject
ClienteService clienteService;
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response crearCliente(Cliente cliente) {
Cliente clienteGuardado = clienteService.registrarCliente(cliente);
return Response.status(Response.Status.CREATED).entity(clienteGuardado).build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response listarClientes() {
return Response.ok(clienteService.obtenerTodos()).build();
}
}
Pro-Tip (Solución de CORS): Para evitar los dolores de cabeza de los bloqueos de CORS entre Angular y Quarkus en desarrollo, creamos un interceptor global:
package com.miempresa.config;
import jakarta.ws.rs.container.*;
import jakarta.ws.rs.ext.Provider;
@Provider
public class CorsFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
}
Levanta tu servidor con ./mvnw compile quarkus:dev ¡y tu backend estará listo!
Parte 2: El Frontend con Angular
1. Habilitar HttpClient
En las nuevas versiones de Angular con Componentes Standalone, configuramos el cliente HTTP en el app.config.ts:
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideHttpClient()]
};
2. El Servicio de Comunicación
Creamos una interfaz para el tipado fuerte y el servicio que conectará con Quarkus (cliente.service.ts):
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Cliente {
id?: number;
nombre: string;
email: string;
tipoProblema: string;
}
@Injectable({
providedIn: 'root'
})
export class ClienteService {
private http = inject(HttpClient);
private apiUrl = 'http://localhost:8080/api/v1/clientes';
registrarCliente(cliente: Cliente): Observable<Cliente> {
return this.http.post<Cliente>(this.apiUrl, cliente);
}
listarClientes(): Observable<Cliente[]> {
return this.http.get<Cliente[]>(this.apiUrl);
}
}
3. El Componente (TypeScript)
Aquí implementaremos formularios reactivos y un pequeño truco con ChangeDetectorRef para asegurar que nuestra interfaz se actualice al instante de recibir la respuesta de la red.
import { Component, inject, OnInit, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { Cliente, ClienteService } from '../../services/cliente.service';
@Component({
selector: 'app-registro-cliente',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './registro-cliente.html'
})
export class RegistroCliente implements OnInit {
private formbuilder = inject(FormBuilder);
private clienteService = inject(ClienteService);
private cdr = inject(ChangeDetectorRef);
mensajeExito = "";
mensajeError = "";
listaClientes: Cliente[] = [];
clienteForm = this.formbuilder.group({
nombre: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
tipoProblema: ['', Validators.required]
});
ngOnInit(): void {
this.cargarClientes();
}
cargarClientes(){
this.clienteService.listarClientes().subscribe({
next: (datos: Cliente[]) => {
this.listaClientes = datos;
this.cdr.detectChanges(); // Forzamos la actualización de la UI
},
error: (error: any) => console.error('Error al listar', error)
});
}
onSubmit(){
if(this.clienteForm.valid){
const nuevoCliente = this.clienteForm.value as Cliente;
this.clienteService.registrarCliente(nuevoCliente).subscribe({
next: (respuesta: Cliente) => {
this.mensajeExito = `Cliente ${respuesta.nombre} registrado con el ID: ${respuesta.id}`;
this.clienteForm.reset();
this.cargarClientes();
this.cdr.detectChanges();
},
error: (error: any) => console.error('Error de conexión', error)
});
}
}
}
4. La Vista (HTML)
Usaremos el nuevo Control Flow de Angular (@if, @for) que hace el código mucho más legible y rápido que las directivas estructurales antiguas.
<div style="max-width: 500px; margin: 40px auto; font-family: sans-serif;">
<h2>Atención al Cliente</h2>
@if (mensajeExito) {
<div style="background: #d4edda; color: #155724; padding: 10px; margin-bottom: 15px; border-radius: 5px;">
{{ mensajeExito }}
</div>
}
<form [formGroup]="clienteForm" (ngSubmit)="onSubmit()" style="display: flex; flex-direction: column; gap: 15px;">
<div>
<label>Nombre completo:</label><br>
<input type="text" formControlName="nombre" style="width: 100%; padding: 8px;">
</div>
<div>
<label>Email:</label><br>
<input type="email" formControlName="email" style="width: 100%; padding: 8px;">
</div>
<div>
<label>Tipo de Problema:</label><br>
<select formControlName="tipoProblema" style="width: 100%; padding: 8px;">
<option value="" disabled selected>Seleccione un área</option>
<option value="Facturacion">Facturación y pagos</option>
<option value="Soporte">Soporte Técnico</option>
<option value="Hardware">Garantía de Hardware</option>
</select>
</div>
<button type="submit" [disabled]="clienteForm.invalid" style="padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 5px;">
Registrar e informar
</button>
</form>
<hr style="margin: 30px 0;">
<h3>Clientes Registrados</h3>
<div style="border: 1px solid #ccc; padding: 15px; border-radius: 5px;">
@for (c of listaClientes; track c.id) {
<div style="padding: 10px; border-bottom: 1px solid #eee;">
<strong>{{ c.nombre }}</strong> ({{ c.email }}) <br>
<span style="color: #666; font-size: 0.9em;">Problema: {{ c.tipoProblema }}</span>
</div>
} @empty {
<p style="color: #666;">No hay clientes en la base de datos.</p>
}
</div>
</div>
Finalmente, importa y coloca el selector <app-registro-cliente></app-registro-cliente> en tu app.component.html, ejecuta ng serve y tendrás una aplicación empresarial totalmente reactiva.
Comentarios (0)