User management with Quarkus and Angular on Kubernetes - Part 1

The first part of a User management tutorial with the backend on Quarkus

This is the first of several posts that will run a Web Application in a Kubernetes Cluster. We will create a basic User management Application with a Quarkus backend and an Angular Frontend and deploy it on Kubernetes. The user data will be store in a database an expose through REST services. Let’s start with the first part: the creation of the backend on Quarkus

Project setup


To setup the project, please have a look at my previous post A first microservice with Quarkus on GraalVM. You’ll find details about the installation of Gradle, GraalVM and to setup a Quarkus application.

Database


Our service will expose user stored in a database. Let’s create a postgres database in a docker container first. Create a file docker-compose.yml at the root of the project with the following content:

version: '3'
services:
  db:
    image: postgres
    restart: always
    ports:
      - "5433:5432"
    volumes:
      - database-data:/var/lib/postgresql/data/
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: quarkusdb
volumes:
  database-data:

Start the container with the following command:

$ docker compose up -d

The Service


Let’s focus on the service. For the sake of simplicity, we will only save a user with 3 properties:

  • firstname
  • lastname
  • birthdate

JPA Entity

Create a User class with the following content:

@Entity
@Table(name = "USER_MGMT")
@Getter
@Setter
@ToString(callSuper=true)
@NoArgsConstructor
@AllArgsConstructor
@SequenceGenerator(name=User.USER_SEQ, sequenceName = User.USER_SEQ, allocationSize = 1)
public class User implements Serializable {
    public static final String USER_SEQ = "USER_SEQ";

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator=USER_SEQ)
    @Setter(AccessLevel.PRIVATE)
    private Long id;

    @NotNull
    @Column(name = "FIRSTNAME")
    protected String firstname;

    @NotNull
    @Column(name = "LASTNAME")
    protected String lastname;

    @NotNull
    @Column(name = "BIRTHDATE")
    private LocalDate birthdate;
}

Panache repository

Now let’s create our data access layer with a Panache Repository.

@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
}

Rest resource

In the User management Application we will be able to do the following operation:

  • Create a User
  • Read a User
  • Update a User
  • Delete a User

Therefore, we will create the required REST endpoint to execute those operations,

@Path("/api/user/management")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@AllArgsConstructor(onConstructor = @__(@Inject))
public class UserResource {

    private final UserRepository repository;

    @GET
    @Path("/all")
    public List<User> all() {
        return repository.findAll().list();
    }

    @GET
    @Path("/{id}")
    public User findById(@PathParam("id") long id) {
        return repository.findById(id);
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    public boolean deleteById(@PathParam("id") long id) {
        return repository.deleteById(id);
    }

    @POST
    @Path("/save")
    @Transactional
    public void save(final User user) {
        final User domain = user.getId() == null ? new User() : repository.findById(user.getId());
        domain.setBirthdate(user.getBirthdate());
        domain.setFirstname(user.getFirstname());
        domain.setLastname(user.getLastname());
        if (!repository.isPersistent(domain)) {
            repository.persistAndFlush(domain);
        }
    }
}

Bootstrap

To easily test our application, we will pre-populate the database with some random data. Create a file called import.sql and put it into src/resources.

INSERT INTO public.user_mgmt (id, firstname, lastname, birthdate) VALUES (NEXTVAL('USER_SEQ'), 'Michael', 'Smith', '1984-03-14');
INSERT INTO public.user_mgmt (id, firstname, lastname, birthdate) VALUES (NEXTVAL('USER_SEQ'), 'Bob', 'Kean', '1974-07-23');
INSERT INTO public.user_mgmt (id, firstname, lastname, birthdate) VALUES (NEXTVAL('USER_SEQ'), 'Don', 'Johnson', '1990-12-03');

Configuration

Database

The database connection needs to be configured in our application. This can be done in the application.yml file. Use the same credentials you defined earlier in the docker-compose.yml. The property sql-load-script enables our bootstrap script to be executed on the database at startup.

quarkus:
  datasource:
    db-kind: postgresql
    username: postgres
    password: postgres
    jdbc:
      url: jdbc:postgresql://localhost:5433/quarkusdb
  hibernate-orm:
    dialect: org.hibernate.dialect.PostgreSQLDialect
    database:
      generation: drop-and-create
    sql-load-script:
      import.sql

CORS configuration

The browser’s same-origin policy (CORS) blocks reading a resource from a different origin. This mechanism stops a malicious site from reading another site’s data, but it also prevents legitimate uses. To be able to access our Backend with our Angular Frontend we need to allow requests coming from it. To simplify the configuration, we will in our case allow every request. Add the following configuration in your application.yml:

quarkus:
  http:
    cors:
      ~: true
      methods: "*"
      origins: "*"

Testing

Everything is now set, and we are ready to test our application. Execute the following command to start the application:

$ gradle quarkusDev

Now execute the following command to test that all endpoints are working correcty.

Retrieve all users

curl -X GET --location "http://localhost:8080/api/user/management/all" \
    -H "Accept: application/json"

Get User with id 1

curl -X GET --location "http://localhost:8080/api/user/management/1" \
    -H "Accept: application/json"

Save a new user

curl -X POST --location "http://localhost:8080/api/user/management/save" \
    -H "Content-Type: application/json" \
    -d "{
          \"firstname\": \"John\",
          \"lastname\": \"Doe\",
          \"birthdate\": \"1984-03-05\"
        }"

Update existing user

curl -X POST --location "http://localhost:8080/api/user/management/save" \
    -H "Content-Type: application/json" \
    -d "{
          \"id\": \"4\",
          \"firstname\": \"John\",
          \"lastname\": \"Doe\",
          \"birthdate\": \"1984-03-06\"
        }"

Delete a user

curl -X DELETE --location "http://localhost:8080/api/user/management/4" \
    -H "Accept: application/json"

Docker

Now that our service is up and running and fully tested, we are going to run it on docker. For this, we will compile the service natively with a Docker Multistage, For more information about that, have a look at A first microservice with Quarkus on GraalVM. Add the following to your docker-compose.yml:

  usermgmt-service:
    image: native/usermgmt-service
    build:
      context: .
      dockerfile: usermgmt-server/src/main/docker/Dockerfile.multistage
    ports:
      - "8080:8080"
    depends_on:
      - db-test
    environment:
      QUARKUS_DATASOURCE_JDBC_URL: "jdbc:postgresql://db-test:5432/quarkusdb"

Summary


It’s been pretty easy and fast to create a backend service with Quarkus for CRUD operations. In the next post, we will create the UI for our service, and communicate through the exposed REST services we just created.

The source code of the project can be found in github.