Skip to content

QuickStart react

Marco Collovati edited this page Dec 1, 2023 · 2 revisions

Quick start (React)

Setting up and running a Hilla application on Quarkus using quarkus-hilla-react extension.

Introduction

Following this guide you will create a simple Todo List application using Hilla and Quarkus, using React for front-end development.

The application will use Panache to persist items in an in-memory H2 database.

Note
This guide is for Quarkus Hilla 2.x. For Quarkus Hilla 1.x see this guide.

Requirements

  • JDK 17 or later

  • Maven 3.8 or later

Creating a new Quarkus project

You can create a new Quarkus project using Maven from the command line:

mvn io.quarkus.platform:quarkus-maven-plugin:3.5.2:create \
    -DprojectGroupId=com.example.application \
    -DprojectArtifactId=getting-started \
    -Dextensions=quarkus-hilla-react

On the command line we specified that the new project will use the quarkus-hilla-react extension. The Quarkus maven plugin will create a new project for an example Hilla application.

Note
The following commands will use the maven wrapper.

Once the project is created, enter the getting-started directory and add the other required extensions to the project:

./mvnw quarkus:add-extensions \
    -Dextensions="quarkus-hibernate-orm-panache, \
                  quarkus-jdbc-h2, \
                  quarkus-hibernate-validator"

The POM file should now contain the quarkus-hilla-react dependency, the Hilla BOM and the configuration of the hilla-maven-plugin:

<project>
  <dependencies>
    <dependency>
      <groupId>com.github.mcollovati</groupId>
      <artifactId>quarkus-hilla-react</artifactId>
      <version>2.4.0</version>
    </dependency>
  </dependencies>
</project>
<project>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>dev.hilla</groupId>
        <artifactId>hilla-bom</artifactId>
        <version>${hilla.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>
<project>
  <build>
    <plugins>
      <plugin>
        <groupId>dev.hilla</groupId>
        <artifactId>hilla-maven-plugin</artifactId>
        <version>${hilla.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>prepare-frontend</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
Note

If you want to use a specific Hilla version, update the value in the hilla.version maven property.

And this is the all-in-one statement to create the project and add the extensions:

mvn io.quarkus.platform:quarkus-maven-plugin:3.5.2:create \
    -DprojectGroupId=com.example.application \
    -DprojectArtifactId=getting-started \
    -Dextensions="quarkus-hibernate-orm-panache, \
                  quarkus-jdbc-h2, \
                  quarkus-hibernate-validator, \
                  quarkus-hilla-react"

Configure the project

You can now remove the unnecessary generated examples files.
Delete all the contents inside the src/main/java/com/example/application folder,
and the MainView.tsx file in fronted/views folder.

Next, you should configure the persistence settings in application.properties

quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:rest-crud
quarkus.datasource.jdbc.driver=org.h2.Driver
quarkus.datasource.jdbc.max-size=8
quarkus.datasource.jdbc.min-size=2

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

To add some sample data, edit the file import.sql in src/main/resources/ with the following content:

INSERT INTO todo(id, task, done) VALUES (nextval('Todo_SEQ'), 'Introduction to Quarkus', true);
INSERT INTO todo(id, task, done) VALUES (nextval('Todo_SEQ'), 'Hibernate with Panache', false);
INSERT INTO todo(id, task, done) VALUES (nextval('Todo_SEQ'), 'Visit Quarkus website', false);
INSERT INTO todo(id, task, done) VALUES (nextval('Todo_SEQ'), 'Start Quarkus project', false);

Create the application

Now that the project is set up, you can start coding the application.

Define an entity

The Todo item will be modeled as a JPA entity class. Create the file Todo.java in src/main/resources/com/example/application with the following content:

package com.example.application;

import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;
import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Todo extends PanacheEntity {
    private boolean done = false;
    @NotBlank
    private String task;

    public Todo() {
    }

    public Todo(String task) {
        this.task = task;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }
}

Create a Repository

Create a repository class to access the database. By extending PanacheRepository you will get the methods for the most common persistence operations. Create the TodoRepository.java file in src/main/resources/com/example/application with the following content:

package com.example.application;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.orm.panache.PanacheRepository;

@ApplicationScoped
public class TodoRepository implements PanacheRepository<Todo> {
}

Create a Typed Server Endpoint

To be able to invoke server side operations from the frontend, you will use a Hilla endpoint. Endpoints are annotated classes, for which Hilla is able to generate a TypeScript interface to be used in the front-end code.

Create a new TodoEndpoint.java file in src/main/java/com/example/application with the following content:

package com.example.application;

import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import java.util.List;
import dev.hilla.BrowserCallable;
import dev.hilla.Nonnull;
import com.vaadin.flow.server.auth.AnonymousAllowed;

@BrowserCallable
@AnonymousAllowed
public class TodoEndpoint {
    private TodoRepository repository;

    public TodoEndpoint(TodoRepository repository) {
        this.repository = repository;
    }

    public @Nonnull List<@Nonnull Todo> findAll() {
        return repository.listAll();
    }

    @Transactional
    public Todo create(@Valid Todo todo) {
        repository.persist(todo);
        return todo;
    }

    @Transactional
    public Todo update(@Valid Todo todo) {
        Todo entity = repository.findById(todo.getId());
        entity.setDone(todo.isDone());
        entity.setTask(todo.getTask());
        return entity;
    }
}

The @BrowserCallable annotation marks the class as a Hilla endpoint; @AnonymousAllowed means that methods can be accessed by not authenticated users. @Transactional is required for operations that modify the database.

See the Hilla documentation for further information.

Build the Todo View

Now, start the application, for example by typing mvn quarkus:dev on the command line, and let Hilla generate the TypeScript code for you.

Create a new file TodoView.tsx in the frontend/views directory, with the following content:

import {useEffect, useState} from "react";
import {useForm} from '@hilla/react-form';
import {Button} from "@hilla/react-components/Button.js";
import {TextField} from "@hilla/react-components/TextField.js";
import {Checkbox} from "@hilla/react-components/Checkbox.js";
import Todo from "Frontend/generated/com/example/application/Todo";
import {TodoEndpoint} from "Frontend/generated/endpoints.js";
import TodoModel from "Frontend/generated/com/example/application/TodoModel";

export default function TodoView() {
    const [todos, setTodos] = useState<Todo[]>([]);
    const {model, field, invalid, dirty, submit, clear} = useForm(TodoModel, {
        onSubmit: async (todo) => {
            todo.id = NaN;
            const newTodo = await TodoEndpoint.create(todo);
            setTodos([...todos, newTodo]);
            clear();
        }
    });
    const updateTodoState = async (todo: Todo, done: boolean) => {
        if (todo.done !== done) {
            todo.done = done;
            const updatedTodo = await TodoEndpoint.update(todo);
            setTodos(todos.map(t => t.id === updatedTodo.id ? updatedTodo : t));
        }
    }

    useEffect(() => {
        TodoEndpoint.findAll().then(setTodos);
    }, []);

    return (
        <>
            <div className={'form'}>
                <TextField
                    label="Task"
                    {...field(model.task)}
                />
                <Button onClick={submit} disabled={!dirty || invalid} theme={'primary'}>Add</Button>
            </div>

            <div className={'todos'}>
                {todos.map(todo => (
                    <div className={"todo"} key={todo.id}>
                        <Checkbox checked={todo.done}
                                  onCheckedChanged={(event) => updateTodoState(todo, event.detail.value)}
                                  label={todo.task}></Checkbox>
                    </div>
                ))}
            </div>
        </>
    );
}

In the frontend directory edit the routes.tsx file, to configure the React router in order to show the Todo view.

import TodoView from "Frontend/views/TodoView.js";
import {
    createBrowserRouter,
    RouteObject
} from "react-router-dom";

export const routes: readonly RouteObject[] = [
  { path: "/", element: <TodoView /> },
];

export const router = createBrowserRouter([...routes], {basename: new URL(document.baseURI).pathname });

Run the application

Restart the server, open a browser and navigate to http://localhost:8080.

You should now have a working Todo application.

app

Build for production

The project is already configure with a production profile that triggers the build-frontend goal of the hilla-maven-plugin.

<profile>
    <!-- Production mode is activated using -Pproduction -->
    <id>production</id>
    <build>
        <plugins>
            <plugin>
                <groupId>dev.hilla</groupId>
                <artifactId>hilla-maven-plugin</artifactId>
                <version>${hilla.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-frontend</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Now you can build the application with the following maven command

./mvnw -Pproduction package

And finally start the application by typing

java -jar target/quarkus-app/quarkus-run.jar

Complete source code can be found at https://github.com/mcollovati/quarkus-hilla-todo-example